Skip to main content

pyTincture

Overview

pyTincture is a Python framework designed to leverage the capabilities of Pyodide, enabling developers to create sophisticated and user-friendly GUI libraries. This project aims to bridge the gap between Python's powerful backend and intuitive, interactive frontend interfaces.

Features

  • Pyodide Integration: Seamlessly bring Python to the web via Pyodide.
  • GUI Library Support: Simplify the creation and management of GUI components in Python.
  • Dynamic Code Packaging: Generate in-memory ZIP packages for frontend consumption.
  • Widgetset Stub Generation: Automatically generate frontend stub classes using the @backend_for_frontend decorator.
  • Streaming BFF Calls: Enable true streaming responses with the @bff_stream decorator.
  • Authentication & Sessions: Supports Google and Microsoft OAuth2, SAML 2.0 SSO, and email/password authentication with compact signed sessions that work across replicas.
  • Redis Integration: Optionally expose the legacy shared USER_SESSION_DICT through Upstash; authentication does not require Redis.
  • Cross-Platform Compatibility: Works on any platform where Pyodide is supported.
  • Easy to Use: Provides a user-friendly API to streamline GUI development.
  • Production Launcher: Includes a uvicorn-based launcher for deploying the service.
  • PyPI Distribution: Easily installable via pip from PyPI.

Installation

From PyPI:

pip install pytincture

From Source:

  1. Clone the repository:
git clone https://github.com/yourusername/pyTincture.git
cd pyTincture
  1. Install dependencies:
pip install .

(Alternatively, follow the instructions in pyproject.toml.)

Environment Variables

  • MODULES_PATH: Directory containing module files used for dynamic packaging. This is set automatically from modules_folder when launch_service starts; overriding it via env vars is usually unnecessary.
  • USE_REDIS_INSTANCE: Set to "true" to back the legacy USER_SESSION_DICT with Upstash. Authentication does not read or write this dictionary.
  • ALLOWED_EMAILS: A comma-separated list of authorized email addresses. example: "some@email.com,joe@email.com"
  • ENABLE_GOOGLE_AUTH: Enable the respective authentication mechanisms. example: "true"
  • ENABLE_MICROSOFT_AUTH: Enable Microsoft OAuth2 authentication using the shared common tenant endpoint. example: "true"
  • GOOGLE_CLIENT_ID: OAuth client ID for Google.
  • GOOGLE_CLIENT_SECRET: OAuth client secret for Google.
  • MICROSOFT_CLIENT_ID: OAuth client ID for Microsoft Azure AD / Microsoft identity platform.
  • MICROSOFT_CLIENT_SECRET: OAuth client secret for Microsoft Azure AD / Microsoft identity platform.
  • ENABLE_SAML_AUTH: Enable SAML 2.0 authentication. example: "true"
  • SAML_EMAIL_ATTRIBUTE: Attribute name used to extract the user email from the SAML assertion. example: "email"
  • SAML_NAME_ATTRIBUTE: Optional attribute for the display name. example: "givenName"
  • SAML_LOGIN_LABEL: Optional label for the single-provider SAML login button. example: "Login with Contoso"
  • SAML_LOGO_URL: Optional image URL for the single-provider SAML login button. example: "/appcode/contoso-logo.svg"
  • SAML_SECRET_KEY: Primary signing key for stateless authentication cookies and SAML RelayState. Use at least 32 random characters, keep it stable across deployments, and provide the same value to every replica. When configured, secure cookies default to enabled.
  • AUTH_SESSION_MAX_AGE_SECONDS: Signed authentication cookie lifetime in seconds. Defaults to 28800 (8 hours).
  • AUTH_SESSION_HTTPS_ONLY: Set to true to require HTTPS for the authentication cookie. Defaults to true when SAML_SECRET_KEY is configured and false only for legacy/local compatibility.
  • SAML_RELAY_STATE_TTL_SECONDS: Maximum SAML login handshake age in seconds. Defaults to 600 (10 minutes).
  • SAML_DEFAULT_REDIRECT: Optional redirect path or URL template after SAML login (defaults to /{application} when unset). example: "/{application}"
  • SAML_SP_ENTITY_ID: Optional template for the SP entity ID (supports {application}, {base_url}, {host}); defaults to /{application}/auth/saml/metadata.
  • SAML_SP_ASSERTION_CONSUMER_SERVICE_URL: Optional template for the ACS endpoint (supports placeholders like {application}).
  • SAML_SP_X509_CERT: Service Provider certificate in PEM format if signing/encryption is required.
  • SAML_SP_PRIVATE_KEY: Service Provider private key in PEM format matching the SP certificate.
  • SAML_IDP_ENTITY_ID: Identity Provider entity ID.
  • SAML_IDP_SSO_URL: Identity Provider SSO URL.
  • SAML_IDP_SLO_URL: Optional Identity Provider SLO URL.
  • SAML_IDP_X509_CERT: Identity Provider certificate in PEM format.
  • SAML_PROVIDERS: Optional JSON configuration for multiple named SAML providers. When set, the login page renders one SAML button per provider and uses provider-specific login routes such as /{application}/auth/saml/{provider_id}/login. By default, providers share the standard SP entity ID and ACS URLs (/{application}/auth/saml/metadata and /{application}/auth/saml/acs) so existing IdP app registrations do not need new reply URLs. Provider entries may override these with sp_entity_id and sp_assertion_consumer_service_url when per-provider SP URLs are required. example:
    [
       {
          "id": "company-a",
          "label": "Login with Company A",
          "logo_url": "/appcode/company-a.svg",
          "idp_entity_id": "https://idp-a.example.com/metadata",
          "idp_sso_url": "https://idp-a.example.com/sso",
          "idp_x509_cert": "-----BEGIN CERTIFICATE-----..."
       },
       {
          "id": "company-b",
          "label": "Login with Company B",
          "logo_url": "/appcode/company-b.svg",
          "idp_entity_id": "https://idp-b.example.com/metadata",
          "idp_sso_url": "https://idp-b.example.com/sso",
          "idp_x509_cert": "-----BEGIN CERTIFICATE-----..."
       }
    ]
    
    Provider entries may also override `sp_entity_id`, `sp_assertion_consumer_service_url`, `sp_x509_cert`, `sp_private_key`, `idp_slo_url`, `default_redirect`, `allowed_roles`, and `role_attribute_keys`. If `SAML_PROVIDERS` is not set, the existing single-provider `SAML_*` variables continue to work.
    
  • SAML_DEBUG: Enable verbose SAML logging.
  • ALLOWED_NOAUTH_CLASSCALLS example: [{"file": "somefile.py", "class": "SomeClass", "function": "somefunction"}]
  • GOOGLE_CLIENT_ID
  • GOOGLE_CLIENT_SECRET
  • SECRET_KEY: Legacy fallback signing key used only when SAML_SECRET_KEY is unset.
  • REDIS_UPSTASH_INSTANCE_URL: Url for upstash redis instance example: "http://127.0.0.1:16379"
  • REDIS_UPSTASH_INSTANCE_TOKEN: Redis Upstash token
  • DATABASE_URL: Database connection string example: "sqlite:////absolute/path/to/database.db"

Authenticated browser cookies contain only stable identity claims such as email, name, provider, roles, and picture. Passwords, complete SAML attributes, SAML assertions, and changing SAML session indexes are not stored in the cookie. BFF classes and policy hooks continue to receive the compact authenticated identity.

Running the Service with your application


Development Mode:

Use the service from your application:

if __name__=="__main__":
    from pytincture import launch_service
    launch_service(
        modules_folder=".",  # point to your modules directly
        default_application="py_ui",  # optional: redirect / to /py_ui
        favicon_folder="branding/favicon",  # optional; relative to modules_folder
        env_vars={
            "ALLOWED_EMAILS": []
        }
    )

For one application, place the complete favicon set in a conventional favicon directory under modules_folder:

favicon/
  favicon.ico
  favicon-16x16.png
  favicon-32x32.png
  apple-touch-icon.png
  android-chrome-192x192.png
  android-chrome-512x512.png
  site.webmanifest

pyTincture scans the directory and emits the icon, size, Apple touch icon, mask icon, and web-manifest declarations browsers need. Browsers do not enumerate favicon directories themselves.

Set favicon_folder on launch_service to use a different directory. Relative paths are resolved from modules_folder, and absolute paths are supported, including paths outside the modules directory.

For multiple applications, use one directory per application, such as favicon/py_ui/ and favicon/admin/. This also works under a launcher-configured directory. An application can override both the launcher setting and the convention with either APP_FAVICON = "branding/icons" or APP_CONFIG = {"favicon": "branding/icons"}; the value may point to a directory or a single icon file under modules_folder.

Testing

Tests are written using pytest and cover endpoints, helper functions, and the launcher. Run all tests with:

python -m pytest

Tests include:

  • tests/test_app.py: Endpoint tests and service logic.
  • tests/test_dataclass.py: Tests for stub generation, decorators, and helper functions.
  • tests/test_launcher.py: Tests for the uvicorn launcher and process management.

Docker Quick Start Example built from https://github.com/pytincture/pytincture_example

Run the docker image directly from Dockerhub

docker run -p8070:8070 -i pytincture/pytincture:latest

Load url in browser

http://localhost:8070/py_ui

Standalone pytincture.js / CDN Build

The file under pytincture/frontend/pytincture.js can be bundled and published as a standalone runtime for demos that only need a <script> tag plus embedded Python.

Building the bundle

  1. Install the JS tooling once:
    cd pytincture/frontend
    npm install
    
  2. Produce distributable artifacts (this automatically syncs package.json's version to the Python framework’s pytincture/__init__.py):
    npm run build
    
    The dist/ folder will contain:
    • pytincture.js (IIFE build for script tags)
    • pytincture.min.js (minified IIFE)
    • pytincture.esm.js (ES module build)

You can run npm run build:watch while editing pytincture/frontend/pytincture.js to regenerate the bundles automatically.

Publishing to a CDN

The frontend directory is wired like a normal npm package (name: @pytincture/runtime). After bumping the version in pytincture/frontend/package.json:

cd pytincture/frontend
npm run build
npm publish --access public

The publish script reuses the synchronized version, so npm releases always match the Python __version__. Alternatively, you can run the helper script from the repo root and let it handle version syncing, bundling, and publishing (it skips publishing if that version already exists on npm):

bash scripts/publish_runtime.sh

Once published to npm, CDNs such as jsDelivr and UNPKG will expose the runtime automatically, e.g.:

<script src="https://cdn.jsdelivr.net/npm/@pytincture/runtime@0.1.0/dist/pytincture.min.js"></script>

You can also point jsDelivr at a Git tag (https://cdn.jsdelivr.net/gh/<org>/<repo>@<tag>/pytincture/frontend/dist/pytincture.min.js) if you prefer GitHub releases.

Using pytincture.js standalone

With the CDN script on the page, pytincture auto-detects any <script type="text/python"> blocks and runs them once Pyodide is ready. Optional helpers:

  • Add window.pytinctureAutoStartConfig = { widgetlib: "dhxpyt", libsSelector: "#micropip-libs" } before loading the script to override defaults.
  • Set window.pytinctureAutoStartDisabled = true if you prefer to call runTinctureApp({...}) manually.
  • Extra Python wheels can be listed in <script type="text/json" id="micropip-libs">["faker"]</script>.

Errors are rendered inside #maindiv (if present) and logged to the console, making it easy to host pure-static demos without the full framework.

Backend-for-Frontend access policies

Every @backend_for_frontend class exposes its methods via /classcall, but you can now gate each method without modifying pytincture core:

  1. Tag the method with @bff_policy(...) to describe whatever metadata you need (roles, scopes, tenants, etc.):
    from pytincture.dataclass import backend_for_frontend, bff_policy
    
    @backend_for_frontend
    class Reports:
        @bff_policy(role="manager", scopes=["reports:view"])
        def export(self):
            ...
    
  2. Register a server-side hook that runs before every call. The hook receives the authenticated user (from OAuth/SAML/local login), the policy metadata, the class/method names, and the request. It can raise HTTPException to block the call:
    from fastapi import HTTPException
    from pytincture.backend.app import set_bff_policy_hook
    
    def my_policy_hook(user, policy, **kwargs):
        roles = set(user.get("roles", []))
        required = policy.get("role")
        if required and required not in roles:
            raise HTTPException(status_code=403, detail="Forbidden")
    
    set_bff_policy_hook(my_policy_hook)
    

Because the authorization decision lives on the server, even an authenticated user who opens the browser console can’t call methods they don’t have rights to. The hook is optional—if you don’t register one, bff_policy metadata is ignored.

CI/CD release flow

Publishing a GitHub release (or manually triggering the Publish to PyPI workflow) now runs the following automatically:

  1. Build and upload the Python package to PyPI via twine.
  2. Build the frontend runtime bundles and publish them to npm (using the same version as pytincture/__init__.__version__).

Required GitHub secrets:

  • PYPI_PASSWORD: a PyPI API token (formatted pypi-***) with publish rights to pytincture.
  • NPM_TOKEN: an npm access token with publish rights to @pytincture/runtime.

Both secrets must be configured at the repo (or org) level for the workflow to succeed.

License

pyTincture is licensed under the MIT License.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytincture-0.9.41.tar.gz (5.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytincture-0.9.41-py3-none-any.whl (5.7 MB view details)

Uploaded Python 3

File details

Details for the file pytincture-0.9.41.tar.gz.

File metadata

  • Download URL: pytincture-0.9.41.tar.gz
  • Upload date:
  • Size: 5.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for pytincture-0.9.41.tar.gz
Algorithm Hash digest
SHA256 1de603099161316552dd12139a19d110a9b3c071525aa6ed32c176f9da8e367f
MD5 7327ca5f58e7d9a72ea8196e892a8d3c
BLAKE2b-256 b83805071155574f262d63a321aeb1ad5779bbdf9e9bb5025cce3e23356c6de7

See more details on using hashes here.

File details

Details for the file pytincture-0.9.41-py3-none-any.whl.

File metadata

  • Download URL: pytincture-0.9.41-py3-none-any.whl
  • Upload date:
  • Size: 5.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for pytincture-0.9.41-py3-none-any.whl
Algorithm Hash digest
SHA256 47edca2126bbefde0eb792b6e98bd0384d0714030431523f782f4e00065c1a06
MD5 774e4e409a8964339718cca784694904
BLAKE2b-256 55c770aa4f323ec62d28eea4b9b84a727f554272da850eba28f255e07850feeb

See more details on using hashes here.

Release history Release notifications | RSS feed

0.10.6

2 files

0.10.5

2 files

0.10.4

2 files

0.10.3

2 files

0.10.2

2 files

0.10.1

2 files

0.10.0

2 files

This release

0.9.41 This release

2 files

0.9.40

2 files

0.9.39

2 files

0.9.38

2 files

0.9.37

2 files

0.9.36

2 files

0.9.35

2 files

0.9.34

2 files

0.9.33

2 files

0.9.32

2 files

0.9.31

2 files

0.9.29

2 files

0.9.28

2 files

0.9.27

2 files

0.9.26

2 files

0.9.25

2 files

0.9.24

2 files

0.9.23

2 files

0.9.22

2 files

0.9.20

2 files

0.9.19

2 files

0.9.18

2 files

0.9.17

2 files

0.9.16

2 files

0.9.15

2 files

0.9.14

2 files

0.9.13

2 files

0.9.12

2 files

0.9.11

2 files

0.9.10

2 files

0.9.9

2 files

0.9.8

2 files

0.9.7

2 files

0.9.6

2 files

0.9.5

2 files

0.9.4

2 files

0.9.3

2 files

0.9.2

2 files

0.9.1

2 files

0.9.0

2 files

0.8.9

2 files

0.8.8

2 files

0.8.7

2 files

0.8.6

2 files

0.8.5

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.9

2 files

0.7.8

2 files

0.7.7

2 files

0.7.6

2 files

0.7.5

2 files

0.7.4

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.9

2 files

0.6.8

2 files

0.6.7

2 files

0.6.6

2 files

0.6.5

2 files

0.6.4

2 files

0.6.3

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.9

2 files

0.5.7

2 files

0.5.6

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.5

2 files

0.4.4

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.3.0

2 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