encino-orm 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- encino_orm-0.2.0/.github/workflows/ci.yml +79 -0
- encino_orm-0.2.0/.github/workflows/release.yml +46 -0
- encino_orm-0.2.0/.gitignore +25 -0
- encino_orm-0.2.0/CHANGELOG.md +47 -0
- encino_orm-0.2.0/LICENSE +21 -0
- encino_orm-0.2.0/PKG-INFO +168 -0
- encino_orm-0.2.0/README.md +146 -0
- encino_orm-0.2.0/docs/credits.md +36 -0
- encino_orm-0.2.0/docs/design/0-design.md +453 -0
- encino_orm-0.2.0/docs/design/1-model.md +851 -0
- encino_orm-0.2.0/docs/design/2-constraint.md +278 -0
- encino_orm-0.2.0/docs/design/3-graphql.md +380 -0
- encino_orm-0.2.0/docs/design/4-crud.md +463 -0
- encino_orm-0.2.0/docs/design/5-security.md +422 -0
- encino_orm-0.2.0/docs/design/6-from_db.md +361 -0
- encino_orm-0.2.0/docs/design/7-missing.md +380 -0
- encino_orm-0.2.0/docs/design/8-pk.md +554 -0
- encino_orm-0.2.0/docs/design/9-singleton.md +273 -0
- encino_orm-0.2.0/docs/engines.md +244 -0
- encino_orm-0.2.0/docs/getting-started.md +95 -0
- encino_orm-0.2.0/docs/guide.md +478 -0
- encino_orm-0.2.0/docs/index.md +37 -0
- encino_orm-0.2.0/docs/integrations.md +139 -0
- encino_orm-0.2.0/encino_orm/__init__.py +62 -0
- encino_orm-0.2.0/encino_orm/base.py +162 -0
- encino_orm-0.2.0/encino_orm/cli.py +139 -0
- encino_orm-0.2.0/encino_orm/context.py +61 -0
- encino_orm-0.2.0/encino_orm/engine.py +45 -0
- encino_orm-0.2.0/encino_orm/exceptions.py +22 -0
- encino_orm-0.2.0/encino_orm/graphql/__init__.py +9 -0
- encino_orm-0.2.0/encino_orm/graphql/filters.py +211 -0
- encino_orm-0.2.0/encino_orm/graphql/resolvers.py +65 -0
- encino_orm-0.2.0/encino_orm/graphql/scalars.py +19 -0
- encino_orm-0.2.0/encino_orm/graphql/schema.py +190 -0
- encino_orm-0.2.0/encino_orm/graphql/types.py +72 -0
- encino_orm-0.2.0/encino_orm/http/__init__.py +45 -0
- encino_orm-0.2.0/encino_orm/http/errors.py +22 -0
- encino_orm-0.2.0/encino_orm/http/parsing.py +65 -0
- encino_orm-0.2.0/encino_orm/http/registry.py +32 -0
- encino_orm-0.2.0/encino_orm/http/routes.py +108 -0
- encino_orm-0.2.0/encino_orm/introspection/__init__.py +13 -0
- encino_orm-0.2.0/encino_orm/introspection/codegen.py +106 -0
- encino_orm-0.2.0/encino_orm/introspection/tables.py +11 -0
- encino_orm-0.2.0/encino_orm/introspection/types.py +135 -0
- encino_orm-0.2.0/encino_orm/migration.py +56 -0
- encino_orm-0.2.0/encino_orm/model/__init__.py +111 -0
- encino_orm-0.2.0/encino_orm/model/cache_backend.py +59 -0
- encino_orm-0.2.0/encino_orm/model/cached.py +47 -0
- encino_orm-0.2.0/encino_orm/model/column.py +7 -0
- encino_orm-0.2.0/encino_orm/model/constraint.py +119 -0
- encino_orm-0.2.0/encino_orm/model/domain.py +65 -0
- encino_orm-0.2.0/encino_orm/model/exceptions.py +33 -0
- encino_orm-0.2.0/encino_orm/model/filter.py +200 -0
- encino_orm-0.2.0/encino_orm/model/hooks.py +34 -0
- encino_orm-0.2.0/encino_orm/model/index.py +27 -0
- encino_orm-0.2.0/encino_orm/model/model.py +1009 -0
- encino_orm-0.2.0/encino_orm/model/query_builder.py +286 -0
- encino_orm-0.2.0/encino_orm/model/records.py +22 -0
- encino_orm-0.2.0/encino_orm/model/references.py +32 -0
- encino_orm-0.2.0/encino_orm/model/scope.py +26 -0
- encino_orm-0.2.0/encino_orm/model/types.py +195 -0
- encino_orm-0.2.0/encino_orm/mysql.py +270 -0
- encino_orm-0.2.0/encino_orm/observability.py +149 -0
- encino_orm-0.2.0/encino_orm/pool.py +301 -0
- encino_orm-0.2.0/encino_orm/postgresql.py +258 -0
- encino_orm-0.2.0/encino_orm/query.py +34 -0
- encino_orm-0.2.0/encino_orm/security/__init__.py +33 -0
- encino_orm-0.2.0/encino_orm/security/exceptions.py +20 -0
- encino_orm-0.2.0/encino_orm/security/guard.py +71 -0
- encino_orm-0.2.0/encino_orm/security/jwt.py +56 -0
- encino_orm-0.2.0/encino_orm/security/models.py +62 -0
- encino_orm-0.2.0/encino_orm/security/permissions.py +56 -0
- encino_orm-0.2.0/encino_orm/sql.py +142 -0
- encino_orm-0.2.0/encino_orm/sqlite.py +245 -0
- encino_orm-0.2.0/encino_orm/transfer.py +177 -0
- encino_orm-0.2.0/pyproject.toml +46 -0
- encino_orm-0.2.0/tests/__init__.py +0 -0
- encino_orm-0.2.0/tests/conftest.py +15 -0
- encino_orm-0.2.0/tests/test_aggregates.py +45 -0
- encino_orm-0.2.0/tests/test_base.py +85 -0
- encino_orm-0.2.0/tests/test_bulk_upsert.py +126 -0
- encino_orm-0.2.0/tests/test_cached_model.py +59 -0
- encino_orm-0.2.0/tests/test_cli.py +61 -0
- encino_orm-0.2.0/tests/test_constraint.py +95 -0
- encino_orm-0.2.0/tests/test_crud.py +238 -0
- encino_orm-0.2.0/tests/test_d_recommendations.py +198 -0
- encino_orm-0.2.0/tests/test_decimal_json.py +107 -0
- encino_orm-0.2.0/tests/test_e_recommendations.py +72 -0
- encino_orm-0.2.0/tests/test_engine.py +54 -0
- encino_orm-0.2.0/tests/test_f_recommendations.py +58 -0
- encino_orm-0.2.0/tests/test_from_db.py +223 -0
- encino_orm-0.2.0/tests/test_g_recommendations.py +77 -0
- encino_orm-0.2.0/tests/test_graphql.py +293 -0
- encino_orm-0.2.0/tests/test_has_many.py +121 -0
- encino_orm-0.2.0/tests/test_hooks.py +122 -0
- encino_orm-0.2.0/tests/test_improvements.py +178 -0
- encino_orm-0.2.0/tests/test_index.py +141 -0
- encino_orm-0.2.0/tests/test_issues.py +161 -0
- encino_orm-0.2.0/tests/test_migrations.py +153 -0
- encino_orm-0.2.0/tests/test_model.py +204 -0
- encino_orm-0.2.0/tests/test_mysql.py +253 -0
- encino_orm-0.2.0/tests/test_observability.py +78 -0
- encino_orm-0.2.0/tests/test_pk.py +224 -0
- encino_orm-0.2.0/tests/test_pool.py +323 -0
- encino_orm-0.2.0/tests/test_postgresql.py +256 -0
- encino_orm-0.2.0/tests/test_query_builder.py +126 -0
- encino_orm-0.2.0/tests/test_records.py +60 -0
- encino_orm-0.2.0/tests/test_references.py +110 -0
- encino_orm-0.2.0/tests/test_scalar_types.py +86 -0
- encino_orm-0.2.0/tests/test_scope_softdelete.py +156 -0
- encino_orm-0.2.0/tests/test_security.py +224 -0
- encino_orm-0.2.0/tests/test_singleton.py +115 -0
- encino_orm-0.2.0/tests/test_sql_functions.py +138 -0
- encino_orm-0.2.0/tests/test_sqlite.py +296 -0
- encino_orm-0.2.0/tests/test_transfer.py +238 -0
- encino_orm-0.2.0/tests/test_types.py +64 -0
- encino_orm-0.2.0/uv.lock +641 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: Python ${{ matrix.python-version }}
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 15
|
|
20
|
+
strategy:
|
|
21
|
+
fail-fast: false
|
|
22
|
+
matrix:
|
|
23
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
24
|
+
|
|
25
|
+
services:
|
|
26
|
+
mysql:
|
|
27
|
+
image: mysql:8.0
|
|
28
|
+
env:
|
|
29
|
+
MYSQL_ROOT_PASSWORD: admin
|
|
30
|
+
MYSQL_ROOT_HOST: "%"
|
|
31
|
+
MYSQL_DATABASE: encino_orm_test
|
|
32
|
+
ports:
|
|
33
|
+
- 3306:3306
|
|
34
|
+
options: >-
|
|
35
|
+
--health-cmd="mysqladmin ping -h 127.0.0.1 --password=admin --silent"
|
|
36
|
+
--health-interval=10s
|
|
37
|
+
--health-timeout=5s
|
|
38
|
+
--health-retries=12
|
|
39
|
+
|
|
40
|
+
postgres:
|
|
41
|
+
image: postgres:16-alpine
|
|
42
|
+
env:
|
|
43
|
+
POSTGRES_USER: postgres
|
|
44
|
+
POSTGRES_PASSWORD: admin
|
|
45
|
+
POSTGRES_DB: encino_orm_test
|
|
46
|
+
ports:
|
|
47
|
+
- 5432:5432
|
|
48
|
+
options: >-
|
|
49
|
+
--health-cmd="pg_isready -U postgres"
|
|
50
|
+
--health-interval=10s
|
|
51
|
+
--health-timeout=5s
|
|
52
|
+
--health-retries=12
|
|
53
|
+
|
|
54
|
+
steps:
|
|
55
|
+
- name: Checkout
|
|
56
|
+
uses: actions/checkout@v4
|
|
57
|
+
|
|
58
|
+
- name: Install uv
|
|
59
|
+
uses: astral-sh/setup-uv@v5
|
|
60
|
+
with:
|
|
61
|
+
python-version: ${{ matrix.python-version }}
|
|
62
|
+
enable-cache: true
|
|
63
|
+
|
|
64
|
+
- name: Install dependencies
|
|
65
|
+
run: uv sync --extra http --extra security --extra graphql
|
|
66
|
+
|
|
67
|
+
- name: Run tests
|
|
68
|
+
env:
|
|
69
|
+
ENCINO_ORM_MYSQL_HOST: 127.0.0.1
|
|
70
|
+
ENCINO_ORM_MYSQL_PORT: "3306"
|
|
71
|
+
ENCINO_ORM_MYSQL_USER: root
|
|
72
|
+
ENCINO_ORM_MYSQL_PASSWORD: admin
|
|
73
|
+
ENCINO_ORM_MYSQL_DB: encino_orm_test
|
|
74
|
+
ENCINO_ORM_POSTGRES_HOST: 127.0.0.1
|
|
75
|
+
ENCINO_ORM_POSTGRES_PORT: "5432"
|
|
76
|
+
ENCINO_ORM_POSTGRES_USER: postgres
|
|
77
|
+
ENCINO_ORM_POSTGRES_PASSWORD: admin
|
|
78
|
+
ENCINO_ORM_POSTGRES_DB: encino_orm_test
|
|
79
|
+
run: uv run pytest -q
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
id-token: write # para trusted publishing (OIDC) si se usa
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
name: Build and publish
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v5
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Build
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
# Publica a PyPI usando un token (secret PYPI_API_TOKEN).
|
|
30
|
+
# Alternativa sin token: Trusted Publishing (OIDC):
|
|
31
|
+
# 1. En PyPI > Settings > Publishing, añade el repo hvalles/encinorm
|
|
32
|
+
# con el workflow "release.yml".
|
|
33
|
+
# 2. Sustituye este paso por: run: uv publish --trusted-publishing always
|
|
34
|
+
# y elimina el env con el token.
|
|
35
|
+
- name: Publish to PyPI
|
|
36
|
+
env:
|
|
37
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
|
38
|
+
run: uv publish
|
|
39
|
+
|
|
40
|
+
# Para probar contra TestPyPI de forma manual:
|
|
41
|
+
# gh workflow run "Publish to PyPI" -f ... (o usa el botón "Run workflow")
|
|
42
|
+
# y comenta el paso anterior, descomentando:
|
|
43
|
+
# - name: Publish to TestPyPI
|
|
44
|
+
# env:
|
|
45
|
+
# UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
|
46
|
+
# run: uv publish --publish-url https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Entornos virtuales
|
|
2
|
+
.venv/
|
|
3
|
+
venv/
|
|
4
|
+
env/
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
build/
|
|
12
|
+
dist/
|
|
13
|
+
|
|
14
|
+
# Pruebas y cobertura
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
|
|
19
|
+
# Entornos / editores
|
|
20
|
+
.env
|
|
21
|
+
.idea/
|
|
22
|
+
.vscode/
|
|
23
|
+
|
|
24
|
+
# Notas internas (no distribuir)
|
|
25
|
+
prompts/
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todos los cambios notables del proyecto se documentan en este archivo.
|
|
4
|
+
|
|
5
|
+
El formato sigue [Keep a Changelog](https://keepachangelog.com/es/1.1.0/) y el
|
|
6
|
+
proyecto usa [Versionado Semántico](https://semver.org/lang/es/). Mientras esté
|
|
7
|
+
en `0.x`, **no hay garantía de estabilidad** (ver `README.md`).
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Añadido
|
|
12
|
+
|
|
13
|
+
- `Model.cursor(db=None, **values)`: instancia de consulta sin validación de
|
|
14
|
+
pydantic, para operaciones de lectura (`load`/`search`/`count`/`paginate`) y
|
|
15
|
+
de esquema (`create_table`) sobre modelos con campos `required=True`, sin
|
|
16
|
+
recurrir a `model_construct()` manual.
|
|
17
|
+
|
|
18
|
+
### Corregido
|
|
19
|
+
|
|
20
|
+
- Ejemplos de `README.md` y `docs/getting-started.md` que requerían definir un
|
|
21
|
+
helper `_cursor()` manual: ahora usan el classmethod `cursor()`.
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - 2026-08-30
|
|
24
|
+
|
|
25
|
+
### Añadido
|
|
26
|
+
|
|
27
|
+
- Núcleo ORM asíncrono para SQLite, MySQL y PostgreSQL (`Db`, `PoolDb`,
|
|
28
|
+
`session`, `create_db`).
|
|
29
|
+
- `Model` declarativo (basado en `pydantic`) con restricciones reutilizables
|
|
30
|
+
(`make_constraint` y presets `STR_*`, `INT`, `CURRENCY`, `DATETIME`,
|
|
31
|
+
`DECIMAL`, `JSON`, …).
|
|
32
|
+
- CRUD completo: `insert`, `save`, `upsert`, `load`, `update`, `delete`,
|
|
33
|
+
`search`, `count`, `paginate`, `insert_many` (bulk).
|
|
34
|
+
- Claves primarias simples (auto-incremental), naturales y **compuestas**, y
|
|
35
|
+
claves foráneas compuestas.
|
|
36
|
+
- Relaciones 1:1 y 1:N con carga por lotes (`batch_reference`,
|
|
37
|
+
`batch_has_many`).
|
|
38
|
+
- `Filter` componible y `QueryBuilder` con `join`, agregados y subconsultas.
|
|
39
|
+
- Migraciones versionadas (`Migration`, `migrations_from_dir`), `create_table`,
|
|
40
|
+
`diff_schema` y `sync_schema`.
|
|
41
|
+
- Conexión implícita (`set_default_db`, `bind`, `resolve_db`).
|
|
42
|
+
- Capas opcionales: REST (FastAPI), GraphQL (Strawberry), seguridad (RBAC + JWT)
|
|
43
|
+
y codegen/CLI (`encino_orm generate models`).
|
|
44
|
+
- Observabilidad (`trace_id`, `QueryTracer`) y caché (`CachedModel` +
|
|
45
|
+
`CacheBackend`).
|
|
46
|
+
- Documentación de usuario (`README.md` y `docs/`), guía para agregar motores y
|
|
47
|
+
licencia MIT.
|
encino_orm-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 encino_orm contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: encino-orm
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Librería asíncrona de interfaz unificada para SQLite, MySQL y PostgreSQL
|
|
5
|
+
Project-URL: Repository, https://github.com/hvalles/encinorm
|
|
6
|
+
Author-email: Hector <valles.hector@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: aiomysql<0.3.2,>=0.2
|
|
11
|
+
Requires-Dist: aiosqlite
|
|
12
|
+
Requires-Dist: asyncpg>=0.31.0
|
|
13
|
+
Requires-Dist: pydantic>=2.13.4
|
|
14
|
+
Provides-Extra: graphql
|
|
15
|
+
Requires-Dist: strawberry-graphql>=0.200; extra == 'graphql'
|
|
16
|
+
Provides-Extra: http
|
|
17
|
+
Requires-Dist: fastapi>=0.110; extra == 'http'
|
|
18
|
+
Provides-Extra: security
|
|
19
|
+
Requires-Dist: fastapi>=0.110; extra == 'security'
|
|
20
|
+
Requires-Dist: pyjwt<2.13,>=2.8; extra == 'security'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# encino_orm · v0.2.0
|
|
24
|
+
|
|
25
|
+
ORM asíncrono de interfaz unificada para **SQLite**, **MySQL** y **PostgreSQL**,
|
|
26
|
+
construido sobre `pydantic`. Proporciona un modelo de datos declarativo, CRUD
|
|
27
|
+
tipado, validación, relaciones (1:1 y 1:N), consultas con filtros y agregados,
|
|
28
|
+
migraciones, y capas opcionales de producto: REST (FastAPI), GraphQL
|
|
29
|
+
(Strawberry), seguridad (RBAC + JWT) y generación de código desde la base de
|
|
30
|
+
datos.
|
|
31
|
+
|
|
32
|
+
> **Estado: experimental (v0.2.0).** encino_orm se encuentra en **fase
|
|
33
|
+
> experimental**: la API pública y su comportamiento pueden cambiar **sin previo
|
|
34
|
+
> aviso** en versiones posteriores, **sin garantía de compatibilidad
|
|
35
|
+
> retroactiva**. El núcleo ORM está probado sobre los tres motores (más de 350
|
|
36
|
+
> pruebas, con integraciones reales de MySQL y PostgreSQL), pero no se recomienda
|
|
37
|
+
> depender de una API estable en producción. Documentación de usuario en `docs/`;
|
|
38
|
+
> estado de preparación en `prompts/analisys-07.md`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Características
|
|
43
|
+
|
|
44
|
+
- **Tres motores** con un único API: `SqliteDb`, `MysqlDb`, `PostgresDb`, y un
|
|
45
|
+
pool de conexiones `PoolDb` con transacciones atómicas por `contextvar`.
|
|
46
|
+
- **Modelos declarativos** basados en `pydantic`, con restricciones reutilizables
|
|
47
|
+
(`STR_100`, `INT_POS`, `CURRENCY`, `DATETIME`, `DECIMAL`, `JSON`, …).
|
|
48
|
+
- **CRUD completo**: `insert`, `save`, `upsert`, `load`, `update`, `delete`,
|
|
49
|
+
`search`, `count`, `paginate`, `insert_many` (bulk).
|
|
50
|
+
- **Claves primarias flexibles**: `id` auto-incremental (por defecto), clave
|
|
51
|
+
natural simple o **clave compuesta**, y claves foráneas compuestas.
|
|
52
|
+
- **Relaciones**: referencias 1:1, colecciones 1:N (`has_many`), y carga por
|
|
53
|
+
lotes (`batch_reference`/`batch_has_many`) para evitar N+1.
|
|
54
|
+
- **Consultas**: `Filter` componible y `QueryBuilder` con `join`, `group_by`,
|
|
55
|
+
agregados (`sum`/`avg`/`min`/`max`/`count`) y subconsultas.
|
|
56
|
+
- **Esquema**: `create_table`, migraciones versionadas, `diff_schema` y
|
|
57
|
+
`sync_schema`.
|
|
58
|
+
- **Hooks** de ciclo de vida y **caché** (`CachedModel` + `CacheBackend`).
|
|
59
|
+
- **Conexión implícita**: `set_default_db`, `bind` y `session` eliminan la
|
|
60
|
+
necesidad de pasar `db` a cada instancia.
|
|
61
|
+
- **Observabilidad**: `trace_id` por request y `QueryTracer` con métricas.
|
|
62
|
+
- **Capas opcionales**: REST (`create_crud`), GraphQL (`build_schema`),
|
|
63
|
+
seguridad (`emit_token`, `require`, RBAC tri-estado) y CLI/codegen
|
|
64
|
+
(`encino_orm generate models`).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Instalación
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# núcleo (SQLite + MySQL + PostgreSQL)
|
|
72
|
+
pip install -e .
|
|
73
|
+
|
|
74
|
+
# con extras opcionales
|
|
75
|
+
pip install -e ".[http,security,graphql]"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Extras disponibles:
|
|
79
|
+
|
|
80
|
+
| Extra | Incluye |
|
|
81
|
+
|------------|------------------------------------------------------|
|
|
82
|
+
| `http` | `fastapi` (REST CRUD) |
|
|
83
|
+
| `security` | `fastapi` + `PyJWT` (RBAC + JWT) |
|
|
84
|
+
| `graphql` | `strawberry-graphql` (GraphQL) |
|
|
85
|
+
|
|
86
|
+
Requiere **Python 3.10+**.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Inicio rápido
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import asyncio
|
|
94
|
+
from encino_orm import create_db
|
|
95
|
+
from encino_orm.model import Model, Filter, STR_100, INT_POS
|
|
96
|
+
|
|
97
|
+
class User(Model):
|
|
98
|
+
_table = "users"
|
|
99
|
+
name: STR_100(required=True)
|
|
100
|
+
age: INT_POS()
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
db = await create_db("sqlite", database=":memory:")
|
|
104
|
+
|
|
105
|
+
await User.cursor(db).create_table() # genera el DDL y lo aplica
|
|
106
|
+
|
|
107
|
+
u = User(db, name="Ana", age=30)
|
|
108
|
+
await u.insert() # INSERT (id auto-incremental)
|
|
109
|
+
|
|
110
|
+
found = await User.cursor(db, id=u.id).load() # SELECT por clave primaria
|
|
111
|
+
assert found.name == "Ana"
|
|
112
|
+
|
|
113
|
+
adults = await User.cursor(db).search(Filter.ge("age", 18)) # SELECT ... WHERE age >= 18
|
|
114
|
+
assert len(adults) == 1
|
|
115
|
+
|
|
116
|
+
await db.close()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> Ejemplo completo (incluidos `Filter` y las capas REST/GraphQL) en
|
|
120
|
+
> `docs/getting-started.md`.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Documentación
|
|
125
|
+
|
|
126
|
+
| Documento | Contenido |
|
|
127
|
+
|-----------|-----------|
|
|
128
|
+
| [Getting started](docs/getting-started.md) | Instalación y primer modelo en 5 minutos. |
|
|
129
|
+
| [Guía de uso](docs/guide.md) | Modelos, restricciones, CRUD, filtros, relaciones, claves primarias y foráneas. |
|
|
130
|
+
| [Integraciones](docs/integrations.md) | REST, GraphQL, seguridad, codegen/CLI y observabilidad. |
|
|
131
|
+
| [Agregar un motor](docs/engines.md) | Guía para desarrolladores: cómo añadir un nuevo motor de base de datos. |
|
|
132
|
+
| [Créditos](docs/credits.md) | Herramientas y tecnologías utilizadas. |
|
|
133
|
+
| [Diseño](docs/) | Documentos de diseño (`design_*.md`) de la arquitectura interna. |
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Pruebas
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
uv run pytest # suite completa (SQLite + MySQL + PostgreSQL)
|
|
141
|
+
uv run pytest -m "not integration" # (si los servidores no están disponibles)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Las integraciones de MySQL y PostgreSQL se omiten automáticamente si el servidor
|
|
145
|
+
correspondiente no está disponible.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Licencia
|
|
150
|
+
|
|
151
|
+
Distribuido bajo la licencia [MIT](LICENSE). Consulta el archivo `LICENSE`
|
|
152
|
+
para el texto íntegro.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Versionado
|
|
157
|
+
|
|
158
|
+
El proyecto sigue [Versionado Semántico](https://semver.org). Mientras esté en
|
|
159
|
+
`0.x`, **no hay garantía de estabilidad**: cada versión *minor* puede introducir
|
|
160
|
+
cambios incompatibles. La estabilidad de la API se declarará a partir de `1.0.0`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Créditos
|
|
165
|
+
|
|
166
|
+
Desarrollado con la asistencia de **[OpenCode](https://opencode.ai)** y el modelo
|
|
167
|
+
**[DeepSeek V4 Pro](https://www.deepseek.com)**. Véase
|
|
168
|
+
[docs/credits.md](docs/credits.md) para el detalle completo.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# encino_orm · v0.2.0
|
|
2
|
+
|
|
3
|
+
ORM asíncrono de interfaz unificada para **SQLite**, **MySQL** y **PostgreSQL**,
|
|
4
|
+
construido sobre `pydantic`. Proporciona un modelo de datos declarativo, CRUD
|
|
5
|
+
tipado, validación, relaciones (1:1 y 1:N), consultas con filtros y agregados,
|
|
6
|
+
migraciones, y capas opcionales de producto: REST (FastAPI), GraphQL
|
|
7
|
+
(Strawberry), seguridad (RBAC + JWT) y generación de código desde la base de
|
|
8
|
+
datos.
|
|
9
|
+
|
|
10
|
+
> **Estado: experimental (v0.2.0).** encino_orm se encuentra en **fase
|
|
11
|
+
> experimental**: la API pública y su comportamiento pueden cambiar **sin previo
|
|
12
|
+
> aviso** en versiones posteriores, **sin garantía de compatibilidad
|
|
13
|
+
> retroactiva**. El núcleo ORM está probado sobre los tres motores (más de 350
|
|
14
|
+
> pruebas, con integraciones reales de MySQL y PostgreSQL), pero no se recomienda
|
|
15
|
+
> depender de una API estable en producción. Documentación de usuario en `docs/`;
|
|
16
|
+
> estado de preparación en `prompts/analisys-07.md`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Características
|
|
21
|
+
|
|
22
|
+
- **Tres motores** con un único API: `SqliteDb`, `MysqlDb`, `PostgresDb`, y un
|
|
23
|
+
pool de conexiones `PoolDb` con transacciones atómicas por `contextvar`.
|
|
24
|
+
- **Modelos declarativos** basados en `pydantic`, con restricciones reutilizables
|
|
25
|
+
(`STR_100`, `INT_POS`, `CURRENCY`, `DATETIME`, `DECIMAL`, `JSON`, …).
|
|
26
|
+
- **CRUD completo**: `insert`, `save`, `upsert`, `load`, `update`, `delete`,
|
|
27
|
+
`search`, `count`, `paginate`, `insert_many` (bulk).
|
|
28
|
+
- **Claves primarias flexibles**: `id` auto-incremental (por defecto), clave
|
|
29
|
+
natural simple o **clave compuesta**, y claves foráneas compuestas.
|
|
30
|
+
- **Relaciones**: referencias 1:1, colecciones 1:N (`has_many`), y carga por
|
|
31
|
+
lotes (`batch_reference`/`batch_has_many`) para evitar N+1.
|
|
32
|
+
- **Consultas**: `Filter` componible y `QueryBuilder` con `join`, `group_by`,
|
|
33
|
+
agregados (`sum`/`avg`/`min`/`max`/`count`) y subconsultas.
|
|
34
|
+
- **Esquema**: `create_table`, migraciones versionadas, `diff_schema` y
|
|
35
|
+
`sync_schema`.
|
|
36
|
+
- **Hooks** de ciclo de vida y **caché** (`CachedModel` + `CacheBackend`).
|
|
37
|
+
- **Conexión implícita**: `set_default_db`, `bind` y `session` eliminan la
|
|
38
|
+
necesidad de pasar `db` a cada instancia.
|
|
39
|
+
- **Observabilidad**: `trace_id` por request y `QueryTracer` con métricas.
|
|
40
|
+
- **Capas opcionales**: REST (`create_crud`), GraphQL (`build_schema`),
|
|
41
|
+
seguridad (`emit_token`, `require`, RBAC tri-estado) y CLI/codegen
|
|
42
|
+
(`encino_orm generate models`).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Instalación
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# núcleo (SQLite + MySQL + PostgreSQL)
|
|
50
|
+
pip install -e .
|
|
51
|
+
|
|
52
|
+
# con extras opcionales
|
|
53
|
+
pip install -e ".[http,security,graphql]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Extras disponibles:
|
|
57
|
+
|
|
58
|
+
| Extra | Incluye |
|
|
59
|
+
|------------|------------------------------------------------------|
|
|
60
|
+
| `http` | `fastapi` (REST CRUD) |
|
|
61
|
+
| `security` | `fastapi` + `PyJWT` (RBAC + JWT) |
|
|
62
|
+
| `graphql` | `strawberry-graphql` (GraphQL) |
|
|
63
|
+
|
|
64
|
+
Requiere **Python 3.10+**.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Inicio rápido
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
from encino_orm import create_db
|
|
73
|
+
from encino_orm.model import Model, Filter, STR_100, INT_POS
|
|
74
|
+
|
|
75
|
+
class User(Model):
|
|
76
|
+
_table = "users"
|
|
77
|
+
name: STR_100(required=True)
|
|
78
|
+
age: INT_POS()
|
|
79
|
+
|
|
80
|
+
async def main():
|
|
81
|
+
db = await create_db("sqlite", database=":memory:")
|
|
82
|
+
|
|
83
|
+
await User.cursor(db).create_table() # genera el DDL y lo aplica
|
|
84
|
+
|
|
85
|
+
u = User(db, name="Ana", age=30)
|
|
86
|
+
await u.insert() # INSERT (id auto-incremental)
|
|
87
|
+
|
|
88
|
+
found = await User.cursor(db, id=u.id).load() # SELECT por clave primaria
|
|
89
|
+
assert found.name == "Ana"
|
|
90
|
+
|
|
91
|
+
adults = await User.cursor(db).search(Filter.ge("age", 18)) # SELECT ... WHERE age >= 18
|
|
92
|
+
assert len(adults) == 1
|
|
93
|
+
|
|
94
|
+
await db.close()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> Ejemplo completo (incluidos `Filter` y las capas REST/GraphQL) en
|
|
98
|
+
> `docs/getting-started.md`.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Documentación
|
|
103
|
+
|
|
104
|
+
| Documento | Contenido |
|
|
105
|
+
|-----------|-----------|
|
|
106
|
+
| [Getting started](docs/getting-started.md) | Instalación y primer modelo en 5 minutos. |
|
|
107
|
+
| [Guía de uso](docs/guide.md) | Modelos, restricciones, CRUD, filtros, relaciones, claves primarias y foráneas. |
|
|
108
|
+
| [Integraciones](docs/integrations.md) | REST, GraphQL, seguridad, codegen/CLI y observabilidad. |
|
|
109
|
+
| [Agregar un motor](docs/engines.md) | Guía para desarrolladores: cómo añadir un nuevo motor de base de datos. |
|
|
110
|
+
| [Créditos](docs/credits.md) | Herramientas y tecnologías utilizadas. |
|
|
111
|
+
| [Diseño](docs/) | Documentos de diseño (`design_*.md`) de la arquitectura interna. |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Pruebas
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv run pytest # suite completa (SQLite + MySQL + PostgreSQL)
|
|
119
|
+
uv run pytest -m "not integration" # (si los servidores no están disponibles)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Las integraciones de MySQL y PostgreSQL se omiten automáticamente si el servidor
|
|
123
|
+
correspondiente no está disponible.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Licencia
|
|
128
|
+
|
|
129
|
+
Distribuido bajo la licencia [MIT](LICENSE). Consulta el archivo `LICENSE`
|
|
130
|
+
para el texto íntegro.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Versionado
|
|
135
|
+
|
|
136
|
+
El proyecto sigue [Versionado Semántico](https://semver.org). Mientras esté en
|
|
137
|
+
`0.x`, **no hay garantía de estabilidad**: cada versión *minor* puede introducir
|
|
138
|
+
cambios incompatibles. La estabilidad de la API se declarará a partir de `1.0.0`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Créditos
|
|
143
|
+
|
|
144
|
+
Desarrollado con la asistencia de **[OpenCode](https://opencode.ai)** y el modelo
|
|
145
|
+
**[DeepSeek V4 Pro](https://www.deepseek.com)**. Véase
|
|
146
|
+
[docs/credits.md](docs/credits.md) para el detalle completo.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Créditos
|
|
2
|
+
|
|
3
|
+
Este proyecto fue desarrollado con la asistencia de las siguientes herramientas
|
|
4
|
+
de inteligencia artificial:
|
|
5
|
+
|
|
6
|
+
## OpenCode
|
|
7
|
+
|
|
8
|
+
- **Sitio:** <https://opencode.ai>
|
|
9
|
+
- **Rol:** agente de codificación en terminal utilizado para explorar el
|
|
10
|
+
repositorio, redactar e implementar los cambios del núcleo ORM, las capas de
|
|
11
|
+
producto (REST, GraphQL, seguridad, codegen), la documentación de usuario y los
|
|
12
|
+
documentos de diseño.
|
|
13
|
+
|
|
14
|
+
## DeepSeek V4 Pro
|
|
15
|
+
|
|
16
|
+
- **Modelo:** `deepseek/deepseek-v4-pro`
|
|
17
|
+
- **Rol:** modelo de lenguaje que impulsa la generación de código, el análisis de
|
|
18
|
+
los documentos `prompts/analisys-*.md` y la redacción de `docs/*.md`.
|
|
19
|
+
|
|
20
|
+
## Dependencias y ecosistema
|
|
21
|
+
|
|
22
|
+
El proyecto se apoya en las siguientes bibliotecas de código abierto:
|
|
23
|
+
|
|
24
|
+
| Biblioteca | Uso |
|
|
25
|
+
|----------------------|--------------------------------------------|
|
|
26
|
+
| `pydantic` | Validación y modelos de datos. |
|
|
27
|
+
| `aiosqlite` | Driver asíncrono de SQLite. |
|
|
28
|
+
| `aiomysql` | Driver asíncrono de MySQL. |
|
|
29
|
+
| `asyncpg` | Driver asíncrono de PostgreSQL. |
|
|
30
|
+
| `fastapi` | Capa REST (extra `http`/`security`). |
|
|
31
|
+
| `PyJWT` | Tokens JWT (extra `security`). |
|
|
32
|
+
| `strawberry-graphql` | Capa GraphQL (extra `graphql`). |
|
|
33
|
+
| `pytest` | Suite de pruebas (dev). |
|
|
34
|
+
| `httpx` | Cliente de pruebas ASGI (dev). |
|
|
35
|
+
|
|
36
|
+
Gracias a los mantenedores de estas herramientas por su trabajo.
|