Skip to main content

Jodit Connector Application (Python)

Version CI Docs Coverage License: MIT Python 3.14+ Ruff mypy: strict

Python/FastAPI implementation of the Jodit File Browser and Uploader connector.

Technology Stack

  • Python 3.14+ with strict typing (mypy --strict)
  • FastAPI / Starlette for the HTTP API
  • Pydantic 2 for the configuration and API schemas
  • Pillow for image processing and thumbnails
  • httpx for SSRF-safe remote downloads
  • WeasyPrint (PDF), html-for-docx (DOCX), boto3 (S3) as optional extras
  • pytest + Testcontainers for testing (MinIO for S3)
  • uv, Ruff and MkDocs Material for tooling and docs

Installation

pip install "jodit-python[all]"

Python 3.14+. Optional features are extras:

  • [pdf] (generatePdf, needs the Pango system library: brew install pango on macOS, libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0 on Debian/Ubuntu)
  • [docx] (generateDocx)
  • [s3] (S3 storage)
  • [all] installs them all. Without an extra the connector still works and the action that needs it answers 501 naming what to install.

For development: uv (make sync installs everything).

Quick start

make sync   # install dependencies
make run    # http://localhost:8081/ping
make menu   # interactive list of commands

Try it with Jodit

make demo   # opens http://localhost:8080/demo/ (NO_BROWSER=1 to skip)

demo/index.html is the Jodit PRO file browser (from a CDN) talking to the connector configured by demo/config.json (CORS on, files in ./files, served by the same static server as the page). Jodit PRO needs no license key on localhost; on other hosts it shows a "Trial version" notice.

Usage

# main.py
from starlette.requests import Request

from jcpy import create_app


async def check_authentication(request: Request) -> str:
    token = request.headers.get("authorization")
    return "admin" if token == "Bearer secret" else "guest"


app = create_app("config.json", check_authentication=check_authentication)
uv run uvicorn main:app --port 8081

config.json overrides only what differs from the built-in defaults (camelCase keys):

{
  "onlyPOST": true,
  "sources": {
    "uploads": {
      "title": "Uploads",
      "root": "/var/www/uploads",
      "baseurl": "https://example.com/uploads/"
    }
  }
}

Without an explicit path the configuration is read from the CONFIG environment variable (JSON text) or the file named by CONFIG_FILE.

Access rules live in accessControl (the last matching rule wins, unlisted actions are allowed):

{
  "defaultRole": "guest",
  "accessControl": [
    { "role": "guest", "FILE_UPLOAD": false, "FILE_REMOVE": false },
    { "role": "admin", "path": "/private", "FILES": true }
  ]
}

They can also be loaded per check from code, e.g. from a database:

from jcpy import AccessControlRule, create_app


async def load_rules() -> list[AccessControlRule]:
    rows = await db.fetch_rules()
    return [AccessControlRule.model_validate(row) for row in rows]


app = create_app("config.json", access_control=load_rules)

S3 and S3-compatible storage

{
  "sources": {
    "media": {
      "title": "Media",
      "baseurl": "https://my-bucket.s3.eu-central-1.amazonaws.com/media/",
      "storageAdapter": "s3",
      "s3": {"bucket": "my-bucket", "region": "eu-central-1", "prefix": "media"}
    }
  }
}

Without credentials the AWS default chain is used (environment, profile, instance role). MinIO, Cloudflare R2, Yandex Object Storage and others work through endpoint (plus forcePathStyle: true where needed). Other backends implement jcpy.StorageAdapter and are registered with register_storage_adapter("name", factory).

Multi-tenant sources

from starlette.requests import Request

from jcpy import ResolvedSources, create_app


async def resolve_sources(request: Request) -> ResolvedSources | None:
    tenant = await find_tenant(request.headers.get("x-tenant-id"))
    if tenant is None:
        return None  # static "sources" apply
    return ResolvedSources(
        id=f"{tenant.id}:{tenant.updated_at}",
        sources={"files": tenant.source_settings},
    )


app = create_app("config.json", resolve_sources=resolve_sources)

The resolver runs on every request, before authentication; the built sources are cached by id (dynamicSourcesCache: 200 tenants, 60 s by default). Use "sources": {} for an instance that only serves tenants.

Several independent instances can live in one application:

from fastapi import FastAPI

from jcpy import create_router

app = FastAPI()
app.include_router(create_router("public.json"), prefix="/public")
app.include_router(
    create_router("admin.json", check_authentication=admin_auth),
    prefix="/admin",
)

Documentation

Complete Documentation - Full documentation with guides and API reference

Quick Links:

OpenAPI Specification:

The site is built from docs/ (make docs serves it locally, make docs-build builds it). The OpenAPI 3.1 document is generated from Pydantic models (make openapi); CI fails when it is stale. The connector itself does not serve /docs or /openapi.json: every path is an action name.

Key Features

  • Full file management - browse, upload (also from a URL), download, rename, move, copy, delete
  • Folder operations - create, rename, move, copy, delete, tree view
  • Image processing - resize, crop, save from the image editor, thumbnails
  • Document generation - PDF and DOCX from HTML (optional extras)
  • Access control - rules by role, path and extension; static, computed or loaded at runtime
  • Authentication - a per-request callback: cookies, JWT, sessions
  • Security - SSRF-safe remote downloads, confinement to the source root (symlinks included), POST-only mode, CORS allowlist
  • FastAPI integration - standalone app or router, several isolated instances in one application
  • Storage - local filesystem or AWS S3 / S3-compatible out of the box, custom adapters registered by name
  • Multi-tenant - sources resolved per request, one instance for many tenants
  • OpenAPI - OpenAPI 3.1 and Swagger UI generated from the schemas
  • Typed - mypy --strict, ships py.typed
  • Testing - pytest suite with 100% coverage
  • Docker - multi-stage, non-root, linux/amd64 + linux/arm64 image

Implemented Functions

  • files - get list of files
  • folders - get folder tree
  • permissions - get permissions
  • fileUpload - upload files
  • fileUploadRemote - upload file from remote URL
  • fileRemove - remove files
  • fileMove - move files and folders
  • fileCopy - copy files
  • fileRename - rename files
  • fileDownload - download file
  • getLocalFileByUrl - resolve local file by URL
  • folderCreate - create folders
  • folderRemove - remove folders
  • folderMove - move folders
  • folderCopy - copy folders
  • folderRename - rename folders
  • imageResize - resize images
  • imageCrop - crop images
  • imageSave - save an image edited in the browser
  • imageLoad - read an image as a data URL
  • generatePdf - generate PDF documents from HTML
  • generateDocx - generate DOCX documents from HTML
  • ping - health check

Examples

Runnable programs in examples/ (start them from the repository root, e.g. uv run python examples/basic.py):

Example Shows
basic.py Standalone connector from a JSON config
cookie_auth.py Role from a cookie
jwt_auth.py Role from a signed JWT (PyJWT)
session_auth.py Role in a server-signed session with login routes
custom_svg.py Custom thumbnail icons
multi_instance.py Two isolated connectors in one application
s3.py Files in an S3 bucket
multi_tenant.py Per-request (tenant) sources
custom_storage.py Custom storage adapter registered by name

Development

make check  # lint, format, mypy --strict, OpenAPI and docs checks, tests

All caches (uv, ruff, mypy, pytest, coverage, bytecode) live in .cache/. The Makefile and the dev container set PYTHONPYCACHEPREFIX=.cache/pycache; export it yourself when running uv run ... directly.

Docker

make dev-up     # dev container with hot reload on http://localhost:8081
make dev-shell  # shell inside it (make check works there too)
make dev-down

make prod-up    # production image (non-root, tini, healthcheck)
make prod-down

PORT overrides the published host port, e.g. PORT=9000 make prod-up. Files served by the production container live in ./files.

Dev Containers

Open the folder in VS Code (or any IDE supporting Dev Containers) and choose Reopen in Container. The container is built from the dev stage of the Dockerfile; the virtualenv lives in a named volume, so it does not clash with a local .venv. Start the server with make dev.

License

MIT

Release files for jodit-python 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jodit-python 0.1.0
File Size Uploaded
jodit_python-0.1.0.tar.gz 85.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jodit-python 0.1.0
File Interpreter ABI Platform
jodit_python-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 211.4 kB

Release files / jodit_python-0.1.0.tar.gz

Download URL jodit_python-0.1.0.tar.gz
Size 85.3 kB
Tags Source
SHA-256 checksum
How to use checksums
90696efa284aa2bbd67d27b438c53db07408d3f79f579ef98e16b513c318b8f4
BLAKE2b-256 checksum
How to use checksums
e7f702ff330d67b6a1143cfdd3ac11ee7d2005f7249c28ac2aa82e785bd6af1a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / jodit_python-0.1.0-py3-none-any.whl

Download URL jodit_python-0.1.0-py3-none-any.whl
Size 126.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e6f5a40433c88e90532a6482504677784029121b08d5060d5f992bba9401f7ef
BLAKE2b-256 checksum
How to use checksums
ec1b4f9ee504361e2c1207a5176b23562f859853de6d63a6fc5d948ff66eb420
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.1.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page