Skip to main content

File-based ASGI backend framework (folder tree = API) with an async-capable secure ORM, DI, WebSockets + pub/sub, cache, OpenAPI, migrations, and service integrations.

Project description

EndoCore

PyPI Python Tests License Discord

Your folder tree is your API. No routers. No decorators. No drift.

Api/v1/User/[id]/Get.py    ->  GET  /v1/user/42        (id="42")
Api/v1/User/Role/Post.py   ->  POST /v1/user/role
Api/v2/User/[id]/Get.py    ->  GET  /v2/user/42         (v1 keeps working, untouched)

Drop a file in the right folder. The endpoint exists — routed, versioned, and showing up in endo routes and /docs — with zero registration code. Delete the file, the endpoint is gone. Routes are discovered directly from the project structure, so there's no separate router table that can drift from what the code actually does.

# Api/v1/User/Role/Post.py  ->  POST /v1/user/role
from endocore import Request, Response

async def handler(request: Request) -> Response:
    data = await request.json()
    return Response.json({"created": data["name"]}, status=201)

That's a real, complete, working endpoint. No app = FastAPI(), no @app.post(...), no import to wire up anywhere. The file's path and name are the whole contract.

📖 Full documentation: https://endocore.readthedocs.io (EN / RU) — every guide below goes into far more depth than this README. 💬 Community: https://discord.gg/jwvGj2M9EX — questions, showcase, help.


The problem this solves

Every growing API codebase eventually drifts: the route table says one thing, the handler code says another, and "which version does this endpoint belong to?" becomes an archaeology exercise. Decorator-based routers make this worse as they scale — the routes live in your head, scattered across files that import each other in whatever order someone wrote them.

EndoCore eliminates that entire class of drift. The Api/ directory is the route table. endo routes doesn't introspect decorators or import your whole app to find them — it just reads the tree, because the tree already is the answer. A new API version (v2) is shutil.copytree with a filter, so v1 shares no router state with it and can't be touched by a v2 change — no if version == 2 branches rotting in a handler, no versioning that only works if everyone remembers the convention.

Application
  → Router (reads Api/ — no decorators, no import-time registration)
    → Middleware chain (logging, CORS, auth, ...)
      → Dependency injection (Depends(), providers, path params)
        → Your handler
          → ORM (optional) → SQLite / PostgreSQL

60 seconds to a running API

pip install endocore
endo new myapp && cd myapp
endo dev                       # http://127.0.0.1:8000/docs

endo new scaffolds a runnable app (Api/, Models/, Services/, Middleware/). endo dev boots it with the file watcher on — edit a handler, save, the route table reloads in-process (no restart). Open /docs for a live Swagger UI generated from your actual endpoints.

What you get, out of the box

One pip install, one process, no assembly required:

Routing & versioning folder tree = routes; vN folders coexist forever
ORM SQLite + PostgreSQL, sync and async (opt-in driver-native async on Postgres), connection pooling, migrations with rollback
Security parameterized SQL only, quoted/validated identifiers, scrypt password hashing, signed sessions, CSRF, rate limiting
Real-time file-based WebSockets (Socket.py) + pub/sub rooms (single-process or Redis-fanned-out)
DI Depends(...), FastAPI-style, nested, per-request cached
Validation optional pydantic (v2) — a typed param is validated from the body, 422 on failure
Testing TestClient — in-process ASGI client (HTTP + WebSocket), stdlib-only, no test server to run
Observability structured logging with secret masking, Prometheus metrics, OpenTelemetry tracing, /openapi.json + Swagger UI, cache (memory/Redis), background tasks
Integrations Redis, Celery, SMTP email — plug in via extensions.py
CLI endo create, endo dev, endo version create, endo makemigrations, endo routes, endo check, endo doctor

Exactly one required dependency: uvicorn. The resolver, loader, Request/Response, middleware chain, ORM and CLI are all standard library — nothing else is on the critical path of "does this framework start."

Proof, not promises

  • 2329 tests in the framework's own suite (routing matrices, ORM dialect parity, SQL-injection tests, migration rollback, DI edge cases), at 99.9%+ statement coverage — and not padding: every test asserts real behavior, status codes, or exceptions, not just "it imports".
  • Three full demo apps in demos/ that don't just run — they're race-tested under real concurrency: a kanban board with live WebSocket updates, a room-booking system where 8 simultaneous requests for the same slot produce exactly one winner, and a shop with idempotent purchases + payment-webhook handling that survives being retried mid-flight or hit by 6 concurrent duplicate requests without double-charging anyone.
  • A connection pool test suite that runs against real PostgreSQL and Redis (tests/orm/test_postgres_pool.py, tests/orm/test_postgres_async_native.py), proving transaction isolation — sync and native-async — under concurrency before you'd trust it in production, not just on SQLite.
  • Cross-platform verified, not just declared — CI runs the full matrix (Python 3.11–3.13) against real Postgres + Redis on Linux; the one Windows-specific line in the whole codebase (an asyncio event-loop choice needed only for local psycopg development on Windows) is isolated to a single test helper, not the framework itself.
  • An adversarial security audit, not just a linter run: response-splitting, a pickle-RCE cache path, cross-site WebSocket hijacking, and two ORM race conditions were found by reproducing the exploit, fixed, and re-verified — see the security guide and changelog. bandit + pip-audit now run in CI on every push, and the request parsers are property-fuzzed with hypothesis.
  • The public API is documented and covered by semver as of 1.0 — see API stability for exactly what's covered going forward.

A quick look at the ORM

from endocore.orm import Model, fields, configure, create_all, aatomic, Q, F

class User(Model):
    name   = fields.CharField(max_length=100)
    age    = fields.IntegerField(default=0)
    active = fields.BooleanField(default=True)

configure(backend="sqlite", database="app.db")   # or backend="postgres", pool_size=10, ...
create_all(User)

User.objects.create(name="Ada", age=36)
User.objects.filter(age__gte=18).order_by("-age")             # lazy QuerySet
User.objects.filter(Q(age__lt=18) | Q(name__icontains="a"))   # Q objects
User.objects.filter(age__gte=18).update(active=True)          # bulk update
User.objects.filter(pk=1).update(age=F("age") + 1)            # atomic F() expression

# non-blocking, for ASGI handlers:
user = await User.objects.aget(pk=1)
async with aatomic():
    await user.asave()

Every value is bound through the driver (never string-formatted into SQL), every identifier is validated and quoted, only a fixed whitelist of lookups produces SQL, and LIMIT/OFFSET are coerced to int. This isn't a layer you opt into — it's the only way the ORM knows how to build a query.

→ Full ORM reference: docs/orm/ — fields, relations, aggregates, transactions, migrations, encrypted files.

Testing your app

from endocore import TestClient
from endocore.core.application import Application

client = TestClient(Application(app_dir="."))
resp = client.post("/v1/user", json={"name": "Ada"})
assert resp.status_code == 201

with TestClient(Application(app_dir=".")) as client:   # runs on_startup/on_shutdown for real
    with client.websocket_connect("/v1/chat") as ws:
        ws.send_text("hi")
        assert ws.receive_text() == "echo: hi"

No test server, no extra dependency — TestClient drives the same ASGI protocol uvicorn does, over in-memory queues.

→ Full testing guide: docs/guide/testing.md

How it compares

EndoCore FastAPI Django
Routing file path = route decorators decorators (urls.py)
Versioning vN folders, built in manual manual (separate apps)
ORM built in (sync + async) none (bring your own) built in (sync)
Migrations built in, with rollback Alembic (separate) built in
Core dependencies 1 (uvicorn) Starlette + pydantic none (own stack)
Codebase size small enough to read in an afternoon large very large

→ Full comparison with trade-offs: docs/comparison.md

Coming from FastAPI?

The mental model carries over directly — same ASGI deployment, a similar Request/Response shape, the same Depends(...) pattern. What changes is where a route lives: instead of a decorator wherever you happened to put it, POST /v1/user/role is the file Api/v1/User/Role/Post.py. You can migrate incrementally, endpoint by endpoint, since nothing about one route's file has anything to do with another's.

Everything else is one link away

The docs cover every corner in depth — routing rules and edge cases, dependency injection resolution order, middleware ordering, every ORM field and lookup, transactions and connection pooling, migrations, WebSockets and pub/sub, caching, service integrations, security hardening, deployment, and a full step-by-step tutorial:

📖 https://endocore.readthedocs.io (English / Russian, kept in sync)

py -3 -m pip install -e ".[dev]"      # clone + install for local development
pytest -q                              # the framework's own test suite
cd example && endo dev                  # run the bundled example app

Run the framework's own tests with pytest; run an app's tests with endo test.


EndoCore is built around one idea: your directory tree should already describe your API. No duplicated routing tables, no hidden registration, no drift between what the code does and what the docs say it does — just files, folders, and behavior you can predict by looking at where things live.

License

MIT. See LICENSE. Contributions: docs/contributing.md. Changelog: CHANGELOG.md.

Personal / sporting-interest project, 1.0.0. The public API is now covered by semver — see API stability.

Project details


Download files

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

Source Distribution

endocore-1.0.0.tar.gz (170.7 kB view details)

Uploaded Source

Built Distribution

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

endocore-1.0.0-py3-none-any.whl (134.2 kB view details)

Uploaded Python 3

File details

Details for the file endocore-1.0.0.tar.gz.

File metadata

  • Download URL: endocore-1.0.0.tar.gz
  • Upload date:
  • Size: 170.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for endocore-1.0.0.tar.gz
Algorithm Hash digest
SHA256 5227cf4b72cea6da832efbff5f35b0b51309aea7656a53fadb15fd452ba70571
MD5 34948bd1f8e252f31c926ddb3823f052
BLAKE2b-256 bfff5f92f2d5288da9239ac0e3cddaafa579721279eba5c3167f78e816372708

See more details on using hashes here.

File details

Details for the file endocore-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: endocore-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 134.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for endocore-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a1e0fc83e87f4f0977d3e84905c04f0736f01241993480a93af8b07081300a47
MD5 aa27871c39dfd5724aef8ccbd84e9fd6
BLAKE2b-256 dcf9b330f073fd85f63af39d68af83f04fcc45bed6d00443400caabb930ee707

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page