Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

banner

MinVersion PyVersions readthedocs CI coverage pre-commit Ruff mypy PyPI License

Overview

Author: dan@1howardcapital.com

Summary: Zero-dependency Python framework for object-oriented services. Declare an Object once; ft3 derives validation, JSON serialization, a REST API with an OpenAPI document, and structured logging from it.

ft3 is built for agents as its only users. Every rule it enforces is written down in AGENTS.md, every error names the fix, and ft3 check tells you whether a package is correct before you serve it. Every Python block in this file is executed by the test suite, so the examples cannot drift.

Getting started

pip install ft3
ft3 check my_pkg      # validate a package: routes, errors, per-field notices
ft3 openapi my_pkg    # write openapi.json (a build artifact)
ft3 api my_pkg        # serve locally with the stdlib server

Declare an Object

import ft3


class Pet(ft3.Object):
    """A pet. The docstring is the resource description."""

    id_: ft3.Field[str]
    name: ft3.Field[str]
    type_: ft3.Field[str] = ft3.Field(default='dog', enum=['cat', 'dog'])
    is_tail_wagging: ft3.Field[bool] = True


pet = Pet({'id': 'abc123', 'name': 'Bob', 'type': 'cat'})

assert pet.id_ == 'abc123' and pet.type_ == 'cat'
assert pet.as_response == {
    'id': 'abc123',
    'name': 'Bob',
    'type': 'cat',
    'isTailWagging': True,
}

Field names are snake_case; trailing underscores are stripped on the wire, and camelCase keys are accepted on input. A field with no default and no Optional type is required.

Strict and lenient validation

Plain construction is lenient, so stored records always hydrate: constraint violations are kept and logged once per field. Request input is always strict. A class opts in to strict construction with strict=True.

import ft3


class Order(ft3.Object, strict=True):
    """Strict classes raise instead of warning."""

    order_id: ft3.Field[str]
    quantity: ft3.Field[int] = ft3.Field(default=1, minimum=1, maximum=10)


try:
    Order(order_id='o1', quantity=99)
except ft3.objects.exc.ConstraintViolationError as error:
    assert error.code == 'constraint_violation_error'
    assert (error.field, error.constraint, error.limit) == ('quantity', 'maximum', 10)
else:
    raise AssertionError('strict classes reject out-of-range values')

try:
    Order()
except ft3.objects.exc.MissingRequiredFieldError as error:
    assert error.field == 'order_id'
else:
    raise AssertionError('strict classes require required fields')

assert Order.quantity.parse('3', strict=True) == 3

try:
    Order.quantity.parse(3.9, strict=True)
except ft3.objects.exc.ConstraintViolationError as error:
    assert error.constraint == 'lossless'
else:
    raise AssertionError('strict parsing only allows lossless coercion')

Every ft3 exception is an ordinary Exception with a stable code.

Serve an API

Register an Object, attach handlers, and the routes, request parsing, error responses, and OpenAPI document follow. The bundled ft3.template package is a complete example.

import ft3

api = ft3.api.api_from_package(
    'ft3.template', 'v1', '/', include_version_prefix=True
)
client = ft3.api.Client(api)

created = client.post('/v1/petWithPets', body={'name': 'Rex', 'type': 'dog'})
assert created.status_code == 201

rejected = client.post('/v1/petWithPets', body={'name': 'Rex', 'type': 'turtle'})
assert rejected.status_code == 400
assert rejected.body['errorRef'] == 'constraint_violation_error'
assert 'enum' in rejected.body['errorMessage']

assert client.get('/v1/nothing').status_code == 404
assert client.get('/v1/healthz').status_code == 200

ft3.api.Handler(api=api)(request) is the same entry point for a gateway event. ft3 api wraps it in the stdlib HTTP server for local development.

Query generation

Comparisons against class-level fields build a database-agnostic query document.

import ft3


class Pet(ft3.Object):
    """A pet."""

    id_: ft3.Field[str]
    name: ft3.Field[str]
    type_: ft3.Field[str] = ft3.Field(default='dog', enum=['cat', 'dog'])


query = ((Pet.type_ == 'dog') & (Pet.name == 'Fido')) | Pet.name % ('fido', 0.75)
query += 'name'

assert query.to_dict() == {
    'limit': None,
    'or': [
        {
            'and': [
                {'eq': 'dog', 'field': 'type', 'limit': None, 'sorting': []},
                {'eq': 'Fido', 'field': 'name', 'limit': None, 'sorting': []},
            ],
            'limit': None,
            'sorting': [],
        },
        {
            'field': 'name',
            'like': 'fido',
            'limit': None,
            'sorting': [],
            'threshold': 0.75,
        },
    ],
    'sorting': [{'direction': 'asc', 'field': 'name'}],
}

Logging

ft3.log emits one valid JSON document per record and redacts values that should never be logged. print() calls become INFO records instead of reaching stdout.

import ft3

ft3.log.info({'api_key': 'sk-123', 'note': 'hello'})
# >>> {"level": "INFO", "timestamp": "2026-09-29T15:26:20.083Z", "logger": "ft3",
#      "message": {"content": {"api_key": "[ REDACTED :: API_KEY_TOKEN ]", "note": "hello"}}}

Upgrading from 1.x

ft3 2.0 changes validation, error, serialization, routing, and logging semantics. V2.md lists every change. Set FT3_LEGACY_WIRE=true to keep all 1.x wire behavior during the upgrade, run ft3 check to get the per-field migration list, then turn the switch off.

Acknowledgments

  • @sol.courtney

    • Teaching me the difference between chicken-scratch, duct tape, and bubble gum versus actual engineering, and why it matters.

Metadata

Release files for ft3 2.0.0rc1

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

Source distribution (sdist)

Source distribution for ft3 2.0.0rc1
File Size Uploaded Provenance
ft3-2.0.0rc1.tar.gz 321.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for ft3 2.0.0rc1
File Interpreter ABI Platform
ft3-2.0.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 660.0 kB

Release files / ft3-2.0.0rc1.tar.gz

Download URL ft3-2.0.0rc1.tar.gz
Size 321.2 kB
Tags Source
SHA-256 checksum
How to use checksums
b4fa63b5c8abb2f3528002388d22407885c84e34adc566ab6c9198af9019792c
BLAKE2b-256 checksum
How to use checksums
84419ebef86a2066a4d7544cca9f5c554ba58a328ca6b5121237db0670f34387
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Release files / ft3-2.0.0rc1-py3-none-any.whl

Download URL ft3-2.0.0rc1-py3-none-any.whl
Size 338.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6279dfbeff3e651ba4767872f7487cc0596d2f99c9210a4802f53afb2881c61f
BLAKE2b-256 checksum
How to use checksums
e87189e6a37d1ee77d100c93d0b24ddf307dd3fe8e1e260ad703598795a1579f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

Security for ft3 2.0.0rc1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ft3-2.0.0rc1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 29, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release
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