{"id":49255930,"url":"https://github.com/sembeimx/nori","last_synced_at":"2026-04-30T22:01:04.373Z","repository":{"id":349495342,"uuid":"1202551078","full_name":"sembeimx/nori","owner":"sembeimx","description":"A batteries-included async Python web framework built on Starlette and Tortoise ORM.","archived":false,"fork":false,"pushed_at":"2026-04-27T07:09:36.000Z","size":657,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T07:23:24.023Z","etag":null,"topics":["async","jinja2","mvc","python","starlette","tortoise-orm","web-framework"],"latest_commit_sha":null,"homepage":"https://nori.sembei.mx","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sembeimx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security.md","support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-06T06:21:59.000Z","updated_at":"2026-04-27T07:08:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sembeimx/nori","commit_stats":null,"previous_names":["sembeimx/nori"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/sembeimx/nori","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sembeimx%2Fnori","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sembeimx%2Fnori/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sembeimx%2Fnori/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sembeimx%2Fnori/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sembeimx","download_url":"https://codeload.github.com/sembeimx/nori/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sembeimx%2Fnori/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32478162,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"ssl_error","status_checked_at":"2026-04-30T13:12:06.837Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","jinja2","mvc","python","starlette","tortoise-orm","web-framework"],"created_at":"2026-04-25T03:52:50.815Z","updated_at":"2026-04-30T22:01:04.366Z","avatar_url":"https://github.com/sembeimx.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nori\n\n[![Tests](https://github.com/sembeimx/nori/actions/workflows/tests.yml/badge.svg)](https://github.com/sembeimx/nori/actions/workflows/tests.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org)\n[![Version](https://img.shields.io/github/v/release/sembeimx/nori)](https://github.com/sembeimx/nori/releases)\n\n**The async Python framework where server-rendered pages and JSON APIs are first-class peers.**\n\nBuilt on Starlette and Tortoise ORM. Nori is for apps that live in both shapes at once — a SaaS dashboard with an HTMX UI and a JSON endpoint for the mobile client, an admin panel and a public API, a marketing site and a webhook handler. One auth system, one ORM, one validation layer, one testing setup. Nothing to stitch together.\n\n---\n\n## Quick Start\n\n\u003e **Platform**: Nori is tested on Linux and macOS. Windows users should run inside [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install).\n\n```bash\ncurl -fsSL https://nori.sembei.mx/install.py | python3 - my-project\ncd my-project\nsource .venv/bin/activate\npython3 nori.py migrate:init     # one-time: generate framework + user tables for your engine\npython3 nori.py serve\n```\n\nOpen `http://localhost:8000`.\n\nThe installer pulls the latest release, copies only what belongs in a fresh project (no framework dev artifacts), creates a `.venv`, installs dependencies, copies `.env`, and runs `git init`. Pass `--no-venv` or `--no-install` to opt out, or `--version 1.10.0` to pin a release.\n\n\u003e **Contributing to Nori itself?** Clone the repo: `git clone https://github.com/sembeimx/nori.git`. The installer is for project creation, not framework development.\n\n### Configuration (.env)\n\n**SQLite** (quickest for development):\n\n```env\nDEBUG=true\nSECRET_KEY=my-secret-key\nDB_ENGINE=sqlite\nDB_NAME=db.sqlite3\n```\n\n**MySQL / PostgreSQL:** see the [deployment guide](https://nori.sembei.mx/deployment/).\n\n### Docker\n\n```bash\ncp .env.example rootsystem/application/.env\n# Set DB_ENGINE=mysql and DB_HOST=db in .env\ndocker compose up -d --build\n```\n\n---\n\n## CLI\n\nAll commands run from the project root. Built-in commands live in `core/cli.py` (updated with `framework:update`). Custom commands go in `commands/*.py` — they survive updates.\n\n| Command | Description |\n|---------|-------------|\n| `python3 nori.py serve` | Dev server with hot reload |\n| `python3 nori.py shell` | Async REPL with Tortoise + registered models loaded |\n| `python3 nori.py make:controller Name` | Scaffold a controller |\n| `python3 nori.py make:model Name` | Scaffold a model |\n| `python3 nori.py make:seeder Name` | Scaffold a seeder |\n| `python3 nori.py migrate:init` | Initialize migration system |\n| `python3 nori.py migrate:make \u003cname\u003e` | Create a migration |\n| `python3 nori.py migrate:upgrade` | Run pending migrations |\n| `python3 nori.py migrate:downgrade` | Roll back last migration |\n| `python3 nori.py db:seed` | Run database seeders |\n| `python3 nori.py queue:work` | Start the job queue worker |\n| `python3 nori.py audit:purge` | Purge old audit log entries |\n| `python3 nori.py framework:update` | Update Nori core from GitHub |\n| `python3 nori.py framework:version` | Show current version |\n\n---\n\n## Project Structure\n\n```\nnori/\n├── nori.py                          ← CLI bootstrap (delegates to core/cli.py)\n├── requirements.txt\n├── tests/\n└── rootsystem/\n    ├── static/                      ← CSS, JS, images\n    ├── templates/                   ← Jinja2 views\n    └── application/\n        ├── asgi.py                  ← ASGI entry point and middleware stack\n        ├── settings.py              ← Configuration via environment variables\n        ├── routes.py                ← Named routes with Mount grouping\n        ├── commands/                ← Custom CLI commands (survive framework:update)\n        ├── models/                  ← Application models\n        │   └── framework/           ← Framework-owned models (AuditLog, Job)\n        ├── modules/                 ← Controllers\n        ├── seeders/                 ← Database seeders\n        └── core/                    ← Framework engine (updated via framework:update)\n            ├── cli.py               ← CLI commands + plugin loader\n            ├── conf.py              ← Config provider\n            ├── registry.py          ← Model registry (IoC)\n            ├── testing.py           ← Test utilities (client, factories, auth)\n            ├── collection.py        ← NoriCollection\n            ├── pagination.py        ← Async paginator\n            ├── queue.py             ← Job queue (memory/database/redis drivers)\n            ├── queue_worker.py      ← Queue worker with retry and dead letters\n            ├── auth/                ← Sessions, JWT, CSRF, OAuth2, ACL decorators\n            ├── http/                ← @inject DI, declarative validation\n            └── mixins/              ← NoriModelMixin, NoriSoftDeletes, NoriTreeMixin\n```\n\n---\n\n## Documentation\n\nFull documentation at **[nori.sembei.mx](https://nori.sembei.mx)**.\n\n- [Routing](https://nori.sembei.mx/routing/) — HTTP verbs, path params, reverse routing\n- [Controllers](https://nori.sembei.mx/controllers/) — Class-based handlers, `@inject` DI\n- [Database](https://nori.sembei.mx/database/) — Tortoise ORM, migrations, soft deletes, tree models\n- [Templates](https://nori.sembei.mx/templates/) — Jinja2 views, layouts, static files\n- [Authentication](https://nori.sembei.mx/authentication/) — Sessions, JWT, OAuth2, ACL\n- [Validation](https://nori.sembei.mx/forms_validation/) — Declarative pipe-separated rules\n- [Collections](https://nori.sembei.mx/collections/) — NoriCollection with filtering, sorting, pagination\n- [Security](https://nori.sembei.mx/security/) — CSRF, rate limiting, security headers\n- [Background Tasks](https://nori.sembei.mx/background_tasks/) — Volatile tasks and persistent job queues (database + Redis)\n- [Services](https://nori.sembei.mx/services/) — Email, file storage, search, audit logging\n- [WebSockets](https://nori.sembei.mx/websockets/) — Real-time with session/JWT auth\n- [Testing](https://nori.sembei.mx/testing/) — Test client, factories, auth helpers, assertions\n- [Deployment](https://nori.sembei.mx/deployment/) — Gunicorn, systemd, Nginx, Docker, Redis\n- [CLI Reference](https://nori.sembei.mx/cli/) — All commands and plugin system\n\n---\n\n## Testing\n\n```bash\npip install -r requirements-dev.txt\nDEBUG=true pytest tests/ -v\n```\n\nTests boot the full ASGI app against an in-memory SQLite database using `httpx.AsyncClient`. No external services required. Nori includes `core.testing` with helpers for testing your own app — see the [testing docs](https://nori.sembei.mx/testing/).\n\n---\n\n## Updating\n\nProjects built with Nori can update the framework core with a single command:\n\n```bash\npython3 nori.py framework:update\n```\n\nThis downloads the latest release from GitHub, backs up the current core, and replaces it. See the [CLI docs](https://nori.sembei.mx/cli/) for options.\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsembeimx%2Fnori","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsembeimx%2Fnori","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsembeimx%2Fnori/lists"}