Skip to main content

Jodit Connector Application (Python)

PyPI Docker Pulls CI Docs Coverage License: MIT Python 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), azure-storage-blob (Azure), paramiko (SFTP) as optional extras
  • pytest + Testcontainers for testing (MinIO, Azurite, vsftpd, OpenSSH, Apache, rclone)
  • uv, Ruff and MkDocs Material for tooling and docs

Installation

pip install "jodit-python[all]"
# or run the Docker image
docker run --rm -p 8081:8081 -v $(pwd)/files:/app/files w2fb/jodit-python

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)
  • [azure] (Azure Blob storage)
  • [sftp] (SFTP storage; FTP needs no extra)
  • [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).

FTP, SFTP and WebDAV servers

{
  "sources": {
    "site": {
      "title": "Website files",
      "baseurl": "https://www.example.com/uploads/",
      "storageAdapter": "sftp",
      "sftp": {
        "host": "files.example.com",
        "username": "editor",
        "privateKeyFile": "/run/secrets/editor_ed25519",
        "hostKey": "ssh-ed25519 AAAA...",
        "directory": "/var/www/uploads"
      }
    }
  }
}

storageAdapter: "ftp" with an ftp block works the same way (tls: true for FTPS), and so does "webdav" with a webdav block (url, username, password) for Apache, nginx, Nextcloud and other WebDAV servers. The SFTP host key is always checked (hostKey, knownHostsFile or the system known_hosts); get it with ssh-keyscan. Writes are atomic (a temporary file renamed over the target), and connections are pooled and reopened when they drop.

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, AWS S3 / S3-compatible, Azure Blob, FTP / FTPS, SFTP and WebDAV 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

Metadata

Release files for jodit-python 0.3.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.3.0
File Size Uploaded Provenance Actions
jodit_python-0.3.0.tar.gz 114.2 kB Without provenance Details for jodit_python-0.3.0.tar.gz

Built distribution (wheel)

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

Total release size: 275.4 kB

Release files / jodit_python-0.3.0.tar.gz

Download URL jodit_python-0.3.0.tar.gz
Size 114.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e0f414920718fc00a300cbdfd25968832759298fc254f288514221c56070ea9f
BLAKE2b-256 checksum
How to use checksums
9c1681ab7ff4ce2efd6e4a4259c6d8b7c865ddb613a19a73e9c76e74ea4ecb0d
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.3.0-py3-none-any.whl

Download URL jodit_python-0.3.0-py3-none-any.whl
Size 161.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3a85bb594b59eeae58875b28196fda8cb714d3ea154060512b25201f62126322
BLAKE2b-256 checksum
How to use checksums
df887ef302d6baaa57e95c173b4db616309a0995b59aa5bb976c9142ab9bbcc3
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}

Security for jodit-python 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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