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, 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
DI Depends(...), FastAPI-style, nested, per-request cached
Validation optional pydantic — a typed param is validated from the body, 422 on failure
Ops structured logging with secret masking, /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

  • 1696 tests in the framework's own suite (routing matrices, ORM dialect parity, SQL-injection tests, migration rollback, DI edge cases).
  • 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 (tests/orm/test_postgres_pool.py), proving transaction isolation under concurrency before you'd trust it in production — not just on SQLite.

A quick look at the ORM

from endocore.orm import Model, fields, configure, create_all, 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 endocore.orm.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.

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, Beta (0.8.0b1). Client-usable today; stabilizing toward 1.0.

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-0.8.0b1.tar.gz (120.4 kB view details)

Uploaded Source

Built Distribution

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

endocore-0.8.0b1-py3-none-any.whl (115.2 kB view details)

Uploaded Python 3

File details

Details for the file endocore-0.8.0b1.tar.gz.

File metadata

  • Download URL: endocore-0.8.0b1.tar.gz
  • Upload date:
  • Size: 120.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for endocore-0.8.0b1.tar.gz
Algorithm Hash digest
SHA256 2f0ef96dace6c3579cac78eff170af522c6905a27599c6d1786f66c93de7e04c
MD5 12e67d59f68064673a4c73ed95beaa13
BLAKE2b-256 66b09e5c5840b81d17784f71f54e7b91e63458ba770f5b84e2e706e21c9fc13a

See more details on using hashes here.

File details

Details for the file endocore-0.8.0b1-py3-none-any.whl.

File metadata

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

File hashes

Hashes for endocore-0.8.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 2b8b4486b8d3194958c6aec2d8074798f36f91efbf53ae196c2f9b5ff23f32b5
MD5 421fafbe8aa4dcdf16a6e3261faf27cf
BLAKE2b-256 2c802849356ce9a08046b636d59b806f31c58e42d53ab2bd251c495e3071b457

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