eitohforge 0.1.0__py3-none-any.whl
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.
- eitohforge-0.1.0.dist-info/METADATA +72 -0
- eitohforge-0.1.0.dist-info/RECORD +140 -0
- eitohforge-0.1.0.dist-info/WHEEL +4 -0
- eitohforge-0.1.0.dist-info/entry_points.txt +2 -0
- eitohforge_cli/__init__.py +2 -0
- eitohforge_cli/commands/__init__.py +2 -0
- eitohforge_cli/commands/create.py +87 -0
- eitohforge_cli/commands/db.py +99 -0
- eitohforge_cli/commands/dev.py +161 -0
- eitohforge_cli/main.py +37 -0
- eitohforge_cli/template_parts/__init__.py +2 -0
- eitohforge_cli/template_parts/application_templates.py +231 -0
- eitohforge_cli/template_parts/cache_templates.py +271 -0
- eitohforge_cli/template_parts/core_templates.py +15 -0
- eitohforge_cli/template_parts/core_templates_auth.py +723 -0
- eitohforge_cli/template_parts/core_templates_platform.py +699 -0
- eitohforge_cli/template_parts/core_templates_runtime.py +1142 -0
- eitohforge_cli/template_parts/core_templates_security.py +282 -0
- eitohforge_cli/template_parts/core_templates_validation.py +391 -0
- eitohforge_cli/template_parts/crud_templates.py +322 -0
- eitohforge_cli/template_parts/external_api_templates.py +188 -0
- eitohforge_cli/template_parts/jobs_templates.py +191 -0
- eitohforge_cli/template_parts/messaging_templates.py +64 -0
- eitohforge_cli/template_parts/notification_templates.py +236 -0
- eitohforge_cli/template_parts/search_templates.py +315 -0
- eitohforge_cli/template_parts/socket_templates.py +400 -0
- eitohforge_cli/template_parts/storage_templates.py +525 -0
- eitohforge_cli/template_parts/webhook_templates.py +221 -0
- eitohforge_cli/templates.py +1668 -0
- eitohforge_sdk/__init__.py +2 -0
- eitohforge_sdk/application/__init__.py +14 -0
- eitohforge_sdk/application/dto/__init__.py +40 -0
- eitohforge_sdk/application/dto/error.py +41 -0
- eitohforge_sdk/application/dto/repository.py +116 -0
- eitohforge_sdk/application/dto/response.py +55 -0
- eitohforge_sdk/application/query_spec_support.py +19 -0
- eitohforge_sdk/application/services/__init__.py +6 -0
- eitohforge_sdk/application/services/validation.py +68 -0
- eitohforge_sdk/core/__init__.py +267 -0
- eitohforge_sdk/core/abac.py +147 -0
- eitohforge_sdk/core/api_version_deprecation.py +39 -0
- eitohforge_sdk/core/api_versioning.py +29 -0
- eitohforge_sdk/core/audit.py +105 -0
- eitohforge_sdk/core/auth/__init__.py +82 -0
- eitohforge_sdk/core/auth/jwt.py +292 -0
- eitohforge_sdk/core/auth/session.py +241 -0
- eitohforge_sdk/core/auth/sso.py +179 -0
- eitohforge_sdk/core/auth/sso_adapters.py +147 -0
- eitohforge_sdk/core/capabilities.py +201 -0
- eitohforge_sdk/core/config.py +448 -0
- eitohforge_sdk/core/deployment.py +35 -0
- eitohforge_sdk/core/error_middleware.py +46 -0
- eitohforge_sdk/core/error_registry.py +76 -0
- eitohforge_sdk/core/feature_catalog.py +77 -0
- eitohforge_sdk/core/feature_flags.py +110 -0
- eitohforge_sdk/core/forge_application.py +282 -0
- eitohforge_sdk/core/forge_toggles.py +63 -0
- eitohforge_sdk/core/health.py +104 -0
- eitohforge_sdk/core/idempotency.py +159 -0
- eitohforge_sdk/core/observability.py +186 -0
- eitohforge_sdk/core/performance.py +58 -0
- eitohforge_sdk/core/plugins.py +49 -0
- eitohforge_sdk/core/rate_limit.py +89 -0
- eitohforge_sdk/core/request_signing.py +154 -0
- eitohforge_sdk/core/secret_factory.py +29 -0
- eitohforge_sdk/core/secrets.py +154 -0
- eitohforge_sdk/core/security.py +114 -0
- eitohforge_sdk/core/security_context.py +62 -0
- eitohforge_sdk/core/security_hardening.py +65 -0
- eitohforge_sdk/core/tenant.py +142 -0
- eitohforge_sdk/core/validation/__init__.py +45 -0
- eitohforge_sdk/core/validation/context.py +29 -0
- eitohforge_sdk/core/validation/contracts.py +23 -0
- eitohforge_sdk/core/validation/engine.py +51 -0
- eitohforge_sdk/core/validation/errors.py +45 -0
- eitohforge_sdk/core/validation/hooks.py +116 -0
- eitohforge_sdk/core/validation/rules.py +167 -0
- eitohforge_sdk/domain/__init__.py +20 -0
- eitohforge_sdk/domain/repositories/__init__.py +6 -0
- eitohforge_sdk/domain/repositories/contracts.py +56 -0
- eitohforge_sdk/domain/value_objects/__init__.py +16 -0
- eitohforge_sdk/domain/value_objects/contact.py +23 -0
- eitohforge_sdk/domain/value_objects/errors.py +6 -0
- eitohforge_sdk/domain/value_objects/identifiers.py +43 -0
- eitohforge_sdk/domain/value_objects/time.py +19 -0
- eitohforge_sdk/infrastructure/__init__.py +179 -0
- eitohforge_sdk/infrastructure/cache/__init__.py +19 -0
- eitohforge_sdk/infrastructure/cache/contracts.py +33 -0
- eitohforge_sdk/infrastructure/cache/factory.py +37 -0
- eitohforge_sdk/infrastructure/cache/invalidation.py +101 -0
- eitohforge_sdk/infrastructure/cache/memory.py +44 -0
- eitohforge_sdk/infrastructure/cache/redis.py +48 -0
- eitohforge_sdk/infrastructure/cache/tenant_scoped.py +43 -0
- eitohforge_sdk/infrastructure/database/__init__.py +24 -0
- eitohforge_sdk/infrastructure/database/factory.py +47 -0
- eitohforge_sdk/infrastructure/database/providers.py +150 -0
- eitohforge_sdk/infrastructure/database/registry.py +33 -0
- eitohforge_sdk/infrastructure/database/transaction.py +53 -0
- eitohforge_sdk/infrastructure/external_api/__init__.py +26 -0
- eitohforge_sdk/infrastructure/external_api/client.py +112 -0
- eitohforge_sdk/infrastructure/external_api/contracts.py +79 -0
- eitohforge_sdk/infrastructure/jobs/__init__.py +20 -0
- eitohforge_sdk/infrastructure/jobs/contracts.py +76 -0
- eitohforge_sdk/infrastructure/jobs/memory.py +118 -0
- eitohforge_sdk/infrastructure/messaging/__init__.py +18 -0
- eitohforge_sdk/infrastructure/messaging/contracts.py +32 -0
- eitohforge_sdk/infrastructure/messaging/dispatcher.py +36 -0
- eitohforge_sdk/infrastructure/messaging/redis_bridge.py +49 -0
- eitohforge_sdk/infrastructure/notifications/__init__.py +36 -0
- eitohforge_sdk/infrastructure/notifications/contracts.py +52 -0
- eitohforge_sdk/infrastructure/notifications/gateway.py +70 -0
- eitohforge_sdk/infrastructure/notifications/template_engine.py +119 -0
- eitohforge_sdk/infrastructure/repositories/__init__.py +6 -0
- eitohforge_sdk/infrastructure/repositories/sqlalchemy_repository.py +373 -0
- eitohforge_sdk/infrastructure/search/__init__.py +23 -0
- eitohforge_sdk/infrastructure/search/contracts.py +65 -0
- eitohforge_sdk/infrastructure/search/factory.py +42 -0
- eitohforge_sdk/infrastructure/search/memory.py +88 -0
- eitohforge_sdk/infrastructure/search/opensearch.py +147 -0
- eitohforge_sdk/infrastructure/sockets/__init__.py +28 -0
- eitohforge_sdk/infrastructure/sockets/auth.py +61 -0
- eitohforge_sdk/infrastructure/sockets/contracts.py +72 -0
- eitohforge_sdk/infrastructure/sockets/hub.py +145 -0
- eitohforge_sdk/infrastructure/sockets/realtime_redis.py +67 -0
- eitohforge_sdk/infrastructure/sockets/realtime_router.py +285 -0
- eitohforge_sdk/infrastructure/sockets/redis_hub.py +214 -0
- eitohforge_sdk/infrastructure/storage/__init__.py +47 -0
- eitohforge_sdk/infrastructure/storage/cdn.py +64 -0
- eitohforge_sdk/infrastructure/storage/contracts.py +44 -0
- eitohforge_sdk/infrastructure/storage/factory.py +50 -0
- eitohforge_sdk/infrastructure/storage/local.py +46 -0
- eitohforge_sdk/infrastructure/storage/policy.py +185 -0
- eitohforge_sdk/infrastructure/storage/s3.py +117 -0
- eitohforge_sdk/infrastructure/storage/tenant_scoped.py +78 -0
- eitohforge_sdk/infrastructure/transactions/__init__.py +20 -0
- eitohforge_sdk/infrastructure/transactions/saga.py +132 -0
- eitohforge_sdk/infrastructure/webhooks/__init__.py +30 -0
- eitohforge_sdk/infrastructure/webhooks/contracts.py +83 -0
- eitohforge_sdk/infrastructure/webhooks/dispatcher.py +113 -0
- eitohforge_sdk/infrastructure/webhooks/signing.py +36 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eitohforge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Enterprise backend SDK and CLI for FastAPI systems.
|
|
5
|
+
Project-URL: Homepage, https://github.com/eitoh-brand/EitohForge
|
|
6
|
+
Project-URL: Repository, https://github.com/eitoh-brand/EitohForge
|
|
7
|
+
Project-URL: Issues, https://github.com/eitoh-brand/EitohForge/issues
|
|
8
|
+
Author: EitohTech
|
|
9
|
+
License: Proprietary
|
|
10
|
+
Keywords: cli,enterprise,fastapi,scaffold,sdk
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: alembic>=1.16.0
|
|
21
|
+
Requires-Dist: fastapi>=0.116.0
|
|
22
|
+
Requires-Dist: jinja2>=3.1.0
|
|
23
|
+
Requires-Dist: opentelemetry-api>=1.0.0
|
|
24
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.0.0
|
|
25
|
+
Requires-Dist: opentelemetry-sdk>=1.0.0
|
|
26
|
+
Requires-Dist: prometheus-client>=0.20.0
|
|
27
|
+
Requires-Dist: psycopg[binary]>=3.2.0
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.10.0
|
|
29
|
+
Requires-Dist: pydantic>=2.11.0
|
|
30
|
+
Requires-Dist: pymysql>=1.1.0
|
|
31
|
+
Requires-Dist: redis>=5.0.0
|
|
32
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
33
|
+
Requires-Dist: typer>=0.16.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: httpx>=0.28.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: mypy>=1.16.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-cov>=6.2.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.3.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.12.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: uvicorn>=0.32.0; extra == 'dev'
|
|
42
|
+
Provides-Extra: release
|
|
43
|
+
Requires-Dist: build>=1.2.0; extra == 'release'
|
|
44
|
+
Requires-Dist: cyclonedx-bom>=4.0.0; extra == 'release'
|
|
45
|
+
Requires-Dist: pip-audit>=2.8.0; extra == 'release'
|
|
46
|
+
Requires-Dist: pip-licenses>=4.4.0; extra == 'release'
|
|
47
|
+
Requires-Dist: twine>=5.1.0; extra == 'release'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# EitohForge
|
|
51
|
+
|
|
52
|
+
EitohForge is an enterprise-focused backend SDK and CLI framework for bootstrapping FastAPI services with strong architecture, security, and operations defaults.
|
|
53
|
+
|
|
54
|
+
## Current State
|
|
55
|
+
|
|
56
|
+
This repository contains an actively implemented enterprise SDK/CLI baseline through Phase 14.
|
|
57
|
+
|
|
58
|
+
## Planned Command
|
|
59
|
+
|
|
60
|
+
`eitohforge create project <name>`
|
|
61
|
+
|
|
62
|
+
## Key Guides
|
|
63
|
+
|
|
64
|
+
- `docs/guides/usage-complete.md`
|
|
65
|
+
- `docs/guides/cookbook.md`
|
|
66
|
+
- `docs/guides/enterprise-readiness-checklist.md`
|
|
67
|
+
- `docs/releases/v0.1.0-rc.md`
|
|
68
|
+
|
|
69
|
+
## Reference examples
|
|
70
|
+
|
|
71
|
+
- `examples/example-minimal/` — smallest SDK-backed app (health + capabilities).
|
|
72
|
+
- `examples/example-enterprise/` — middleware stack, health family, tenant, feature flags.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
eitohforge_cli/__init__.py,sha256=2Qw5wxP9n_YMwm2-P8mxAIyFrMn5Qq_LTrd1_8A4DGI,31
|
|
2
|
+
eitohforge_cli/main.py,sha256=f_-DI_5BBKvQ3PiWQpBZKkFW6baSZ62VyF5XpD6vM5A,753
|
|
3
|
+
eitohforge_cli/templates.py,sha256=EqRz-328LnEsLXA52qe-lFFhtLeoS0U_6kK2rPDFi8o,58666
|
|
4
|
+
eitohforge_cli/commands/__init__.py,sha256=8UhFLtjsbXglNTf90b5gflyijI2j6b7CtUEcgo753Wo,27
|
|
5
|
+
eitohforge_cli/commands/create.py,sha256=o9dmjWGm-aJ59pFMbrs2pk_AZ-hbBu8tR8LH7zScndc,3256
|
|
6
|
+
eitohforge_cli/commands/db.py,sha256=l1smTfcjc7zFhxaSTTDa1_uAkY8vAUQ-hOrY3iMhtZ4,3717
|
|
7
|
+
eitohforge_cli/commands/dev.py,sha256=LLxXz8kUjZ6bgbUBbyST7VJm3Y04h1rdh2_IV8Ro_6s,6091
|
|
8
|
+
eitohforge_cli/template_parts/__init__.py,sha256=0KrdK7Uzn-62gDwk5sSPIFBOAW2Typ1V_4G9aj6njoY,34
|
|
9
|
+
eitohforge_cli/template_parts/application_templates.py,sha256=yldENxqRPRLPPwiVUhkD73oOS6fDEP6EV0X1jv3pNzw,6567
|
|
10
|
+
eitohforge_cli/template_parts/cache_templates.py,sha256=uSHzNL4nN23XjVoc_isBlnjHJMoSK2Yz4WN8yEuk-j8,9136
|
|
11
|
+
eitohforge_cli/template_parts/core_templates.py,sha256=Y0YEN0-amYToQvKORiqWfv0hecqSrRoem6vo6lQuz2Y,736
|
|
12
|
+
eitohforge_cli/template_parts/core_templates_auth.py,sha256=RCtBz-X_irEcN3WkQEUK6hk_NdXU85fc-QVSWuqoGYk,24371
|
|
13
|
+
eitohforge_cli/template_parts/core_templates_platform.py,sha256=SDJ_kFKxcup2T420VN2rkvby3p0E9TaSLO5qOR_G9mM,24871
|
|
14
|
+
eitohforge_cli/template_parts/core_templates_runtime.py,sha256=4I7PIMr9Wzkc8RfoFsxKJpSHUOomrDy8kDbfwgoPhIQ,42499
|
|
15
|
+
eitohforge_cli/template_parts/core_templates_security.py,sha256=B5JyozFCl0ZpZ-LlS5gTAgMJWVdQYcWhks-jIOlXkhc,9902
|
|
16
|
+
eitohforge_cli/template_parts/core_templates_validation.py,sha256=HOp8SZ4Kh2lXziBguEUwD0fyGkU19BdqUKrOvvRixw4,12833
|
|
17
|
+
eitohforge_cli/template_parts/crud_templates.py,sha256=sfvA3Rbbs1mtTunn3hq4ByoD2n3aTzEnvfcdfRK_7B8,11668
|
|
18
|
+
eitohforge_cli/template_parts/external_api_templates.py,sha256=KLoq-nYA0osLAh7YbtnuLUxvnuR6zoOWpHnfYHU2Xjs,6251
|
|
19
|
+
eitohforge_cli/template_parts/jobs_templates.py,sha256=fB6KaNCB3B4VTdfIEM8w7_yJwl3lKH25tOI76UtBsBM,6082
|
|
20
|
+
eitohforge_cli/template_parts/messaging_templates.py,sha256=Mv9OIGrlPa1hRx0IWNb-tglm-ETUR011_TJxUef53hU,2284
|
|
21
|
+
eitohforge_cli/template_parts/notification_templates.py,sha256=_fmgO9e2Hu7_4swu1M-_pECkCugc1PWrjCnmv3gWdz8,7832
|
|
22
|
+
eitohforge_cli/template_parts/search_templates.py,sha256=gKpZpMWpTxKYS7eh9EeWk6HZYjIk66Hue-R8mUN14Ig,10826
|
|
23
|
+
eitohforge_cli/template_parts/socket_templates.py,sha256=VlbqJKG8J_KEhqCavVwg7IYTFmtcnk89v7vL7RK_8F4,14705
|
|
24
|
+
eitohforge_cli/template_parts/storage_templates.py,sha256=HO4T3bg9xw1tuITzXZr4sigaQCTmpWSIicu3XlvvqZY,18477
|
|
25
|
+
eitohforge_cli/template_parts/webhook_templates.py,sha256=WAdupT1ID7NPlnDpBot_ddWEEDNbSdGV4zK3UIh-H4s,7212
|
|
26
|
+
eitohforge_sdk/__init__.py,sha256=YHz_a5kYqHW1T2w46Mj7W1UJz8rq_V0faTulfzbdfl0,31
|
|
27
|
+
eitohforge_sdk/application/__init__.py,sha256=TlLrYrk4N2CisZ3QQ4oVq_vyloEf08-P2AOZvvoyMlM,403
|
|
28
|
+
eitohforge_sdk/application/query_spec_support.py,sha256=Ou28Tezg3CMNSub5PjQwGzDg9mvZOw_ibvj_oZgO-bM,876
|
|
29
|
+
eitohforge_sdk/application/dto/__init__.py,sha256=5pPPMMTf03XbCJT6uZY6_h0TGciD_Mz6O4yvGwZQEaI,851
|
|
30
|
+
eitohforge_sdk/application/dto/error.py,sha256=Zo9n5putsOn1xJJU8WjAs8SbTjdLiecde_FTzRfOa5g,950
|
|
31
|
+
eitohforge_sdk/application/dto/repository.py,sha256=g3gghh2__NUJZ3jmUpbVNL4ss_PWjdPeTxD8h5ErGuw,3047
|
|
32
|
+
eitohforge_sdk/application/dto/response.py,sha256=3XN7vKZ8g7ed6ggjKwjMpO4kD3XQf2LeNBtFkPWl6sM,1428
|
|
33
|
+
eitohforge_sdk/application/services/__init__.py,sha256=FTJ7WLe-rNagT_z7Vzy1t8wJ7KTUFnL7DqHhTq4KZ-I,157
|
|
34
|
+
eitohforge_sdk/application/services/validation.py,sha256=xD9WrfmTluPwH5l7ez4czM6m26ikLzQ8rxuSr1fUthg,2844
|
|
35
|
+
eitohforge_sdk/core/__init__.py,sha256=Bl9gecQf9Rs3mTML7EwPw7urlGpcQjVnhjUI7oWnV0g,7562
|
|
36
|
+
eitohforge_sdk/core/abac.py,sha256=RbBckFsgLTsIAGunjHQiXPii0L8_sFGbGqrlFzX8qNc,5370
|
|
37
|
+
eitohforge_sdk/core/api_version_deprecation.py,sha256=tnt21ybaSGTGWjG8Yr7NKj6wOEj5TsiqxesiCkEHYsQ,1424
|
|
38
|
+
eitohforge_sdk/core/api_versioning.py,sha256=sBBdAf4kQ3pWDOngzXQCwPPKVMOvsdb4cnjdTmqPQgg,824
|
|
39
|
+
eitohforge_sdk/core/audit.py,sha256=iuRDKmhD8S1-MUWgLmyDI2eG4PXYYHBx-AtCowE_O7A,3219
|
|
40
|
+
eitohforge_sdk/core/capabilities.py,sha256=_f2s-fYCNtte6bZLXmtVhriUwLNTLqpPeC0-VvdQyb4,8412
|
|
41
|
+
eitohforge_sdk/core/config.py,sha256=vSS738k6ceoGTgG9gsNsg6NKjFNcydtrZfEEFEnMAEo,15958
|
|
42
|
+
eitohforge_sdk/core/deployment.py,sha256=b7ld-QloREoib6z3i92GDRqBdP0-TG6xvGdaUO9Yw_I,1103
|
|
43
|
+
eitohforge_sdk/core/error_middleware.py,sha256=MbeCt0_kF_e9IU2KNztPpqnhJnVof9cAMOLukkB9Pj8,1882
|
|
44
|
+
eitohforge_sdk/core/error_registry.py,sha256=wJTRpg6NDWctP-smQMRDz5sSAbJJz-uUS-R_n3AeXMc,2277
|
|
45
|
+
eitohforge_sdk/core/feature_catalog.py,sha256=x7jGl9iD93NHYNvY3JZI6CBHOSdqaKRJs5Bq-qL-2xI,4651
|
|
46
|
+
eitohforge_sdk/core/feature_flags.py,sha256=zwF7uYFWAiH5Am2KQKmEgNNKa-gZ1hI0D6w9wr4CUsY,3648
|
|
47
|
+
eitohforge_sdk/core/forge_application.py,sha256=5nz-_C04CfFjoO_ZHYLiBgtIEN6hslP8BcEbZnpezDk,12700
|
|
48
|
+
eitohforge_sdk/core/forge_toggles.py,sha256=tUjpMeA_-BrxMYQD-D9Jon106Ze92Mdex--55H-kgt4,2230
|
|
49
|
+
eitohforge_sdk/core/health.py,sha256=5RoKwbNzGNihJa_KwCECUiHOVLnF_961y7iYYaKRs4M,3596
|
|
50
|
+
eitohforge_sdk/core/idempotency.py,sha256=w37o2FmS7AOGaJ5qZpLP4qVizIY9h2b6BTjCxPQLyrA,5332
|
|
51
|
+
eitohforge_sdk/core/observability.py,sha256=fdiARKSu8-5NWA9dFN1K-CrkvezsiWLxw_t-yqKAhHE,6516
|
|
52
|
+
eitohforge_sdk/core/performance.py,sha256=GbRh_aZLl9Ucq80o8ctsS5Dwu7C91wbiXWvNAIQq5Zo,1788
|
|
53
|
+
eitohforge_sdk/core/plugins.py,sha256=pyyyZyK2P7EdghDsQ9RToaECMSQoVXAIXFeRUQaBw3A,1599
|
|
54
|
+
eitohforge_sdk/core/rate_limit.py,sha256=zBFiUe6MIIama2Zv28qvee-7nTUlIHQLwSWAugI7LkA,3094
|
|
55
|
+
eitohforge_sdk/core/request_signing.py,sha256=kJFn4LtwKwwRD-pNEyVOXAYGkFkHGSfIPuo10HbXNL0,5521
|
|
56
|
+
eitohforge_sdk/core/secret_factory.py,sha256=drS6ExlRnswXHF7DZknRbrBuO-jEpa4NrrfkEnl0Vjk,882
|
|
57
|
+
eitohforge_sdk/core/secrets.py,sha256=UYkyw6WBQolMf5OV6N_LdeKeudqxwTtd1u35ZzfLcnQ,4880
|
|
58
|
+
eitohforge_sdk/core/security.py,sha256=-ns-eR5_ZG0UhaufhBShtXLZdqGCmVOZHXTZgWxGNqk,4082
|
|
59
|
+
eitohforge_sdk/core/security_context.py,sha256=RaVmsAj1jEB6xujA4hIoDkptQVhvT7y7jU1NffGosF4,2104
|
|
60
|
+
eitohforge_sdk/core/security_hardening.py,sha256=DMLC7J97zXhI2Y1NxwJ6oy6CWOVX1Petfj_hKem1E1g,2602
|
|
61
|
+
eitohforge_sdk/core/tenant.py,sha256=dNb6sLrTrRDKnda9d7KvYqCeogC44Rfl-UXSFFR6c9M,4887
|
|
62
|
+
eitohforge_sdk/core/auth/__init__.py,sha256=YPnAFvJ1snhhr6ksEKp-3_zt5IqgOalj3odr27DnD8o,1811
|
|
63
|
+
eitohforge_sdk/core/auth/jwt.py,sha256=PPNQbvt_HVH7nQgPW9LIeABpgmGRIxiGgrzN9CjrTvk,9646
|
|
64
|
+
eitohforge_sdk/core/auth/session.py,sha256=zmoWC95vbpxTB3rKospIXP2PV-QBbUP5HssxSloKKYs,8522
|
|
65
|
+
eitohforge_sdk/core/auth/sso.py,sha256=1VG_Y20YZx-Yib9pa-1cNO3KES43wSKdmAvcF8li224,5618
|
|
66
|
+
eitohforge_sdk/core/auth/sso_adapters.py,sha256=ViBY0fJJZG0yPPuWR7btjpmS3eLIaaUKvWn1mRZ5IQ0,5371
|
|
67
|
+
eitohforge_sdk/core/validation/__init__.py,sha256=PMhYFY92Iu4ZXKrhLL3F72LiXEtCSZgcY7BWPmmEYJw,1254
|
|
68
|
+
eitohforge_sdk/core/validation/context.py,sha256=JHpUcHa-z4pGdK8VqDQKl-WmI-pGww7LqpBCbSVuNmo,704
|
|
69
|
+
eitohforge_sdk/core/validation/contracts.py,sha256=8ZMsP2XOcwdgw5IuCG58q2dLRNHTH6v7Zb3Lg7ehqxg,606
|
|
70
|
+
eitohforge_sdk/core/validation/engine.py,sha256=UEpEs0mSddW7vNAZ5a1ZwzSxSMQaZIst9RMh7EOP4cM,1886
|
|
71
|
+
eitohforge_sdk/core/validation/errors.py,sha256=Lg0Z5Jfw5jdPqxI8kEKpiAkCw3fCPKWhh_M4hC5Xt6M,1173
|
|
72
|
+
eitohforge_sdk/core/validation/hooks.py,sha256=2lPJZGUJcPIg77VRJ86_iiZ0pCtBkY8bcJO9IuKt4h0,3878
|
|
73
|
+
eitohforge_sdk/core/validation/rules.py,sha256=d9wVAOrfvGrRwcVU4ipilshrB736SAYkJ8JH0WYkAsY,5665
|
|
74
|
+
eitohforge_sdk/domain/__init__.py,sha256=dPr2X_wtIPjGeAvsrERkROxAYK01d1r2MFwTCTXUnO4,349
|
|
75
|
+
eitohforge_sdk/domain/repositories/__init__.py,sha256=XI1LQNlGB0zopfQs0bGr8SyK6SSI0pgNtzpeZxaN744,189
|
|
76
|
+
eitohforge_sdk/domain/repositories/contracts.py,sha256=k3USu1aSTBb4lnvI-nhFQV7_l08DeyOPOAKMsKp531E,1996
|
|
77
|
+
eitohforge_sdk/domain/value_objects/__init__.py,sha256=VPar4FEdJSTNnwkSIlBS18kWjRRsRQpCCHcww3UMkSU,480
|
|
78
|
+
eitohforge_sdk/domain/value_objects/contact.py,sha256=1QJhLWcKrXHV8tY2GZCHah_iGirZ7jBp-7JAyGnmp2k,620
|
|
79
|
+
eitohforge_sdk/domain/value_objects/errors.py,sha256=__aNQM4ZbsY9VPiIGTSNMeRhcftwkYpjlqb3ezA42Vw,137
|
|
80
|
+
eitohforge_sdk/domain/value_objects/identifiers.py,sha256=Ae9JYRPHiBeIejTcmJZKnXVV_8NhvL166uIEFuzFdKI,1049
|
|
81
|
+
eitohforge_sdk/domain/value_objects/time.py,sha256=LhJO0kIjdjSXjBjPzEh8n6iwempuRMKhxzW92HSZIes,451
|
|
82
|
+
eitohforge_sdk/infrastructure/__init__.py,sha256=zvYR1VmJHNNhstr74d_Hsn9gVgh0wGvs7d9aUxnMyJs,4498
|
|
83
|
+
eitohforge_sdk/infrastructure/cache/__init__.py,sha256=GWmju3S_x_2hqi40SZTqYJe0-15YjqK8qX8viPIEp2g,717
|
|
84
|
+
eitohforge_sdk/infrastructure/cache/contracts.py,sha256=-fhX5np95s4bREV93eIVjbq2iHF2KX91iVPGQBDxDzI,640
|
|
85
|
+
eitohforge_sdk/infrastructure/cache/factory.py,sha256=iukiU_xe-4aixQmVb9BfyAl0Ep5jSN1_2Bv7xOyJSO8,1263
|
|
86
|
+
eitohforge_sdk/infrastructure/cache/invalidation.py,sha256=BILQA43euCz2nbydXlzdLlKI_HcGdJQXBF6UnnwOwXw,3324
|
|
87
|
+
eitohforge_sdk/infrastructure/cache/memory.py,sha256=Vsa0ysBziaaE8Pox2l-58dJHp6Ti4Wg9ZM3wFz2viSc,1340
|
|
88
|
+
eitohforge_sdk/infrastructure/cache/redis.py,sha256=Y3YhciI4eGcREV0q3iK1Z6G1M7PyfEhLgv9lDOaF3nI,1684
|
|
89
|
+
eitohforge_sdk/infrastructure/cache/tenant_scoped.py,sha256=hTvk8seqGHZZa_YcUtpLbhq2EGsjYoZ-tIfJE_htpUo,1364
|
|
90
|
+
eitohforge_sdk/infrastructure/database/__init__.py,sha256=PJi6GHrhx14cKA_F0upNXmCHcJ-oTmidNlzudSlRo0E,713
|
|
91
|
+
eitohforge_sdk/infrastructure/database/factory.py,sha256=QMSSsVSbsktwKAb-SyyWOB0JjzsRaxv76gnvLyRK_-4,1697
|
|
92
|
+
eitohforge_sdk/infrastructure/database/providers.py,sha256=F5VaIazRgq9BeN26T-f53sOmZzlpMcruunJEyjb80DM,4478
|
|
93
|
+
eitohforge_sdk/infrastructure/database/registry.py,sha256=n7r5Cwt-8jmiM8gnqWfZPyrWA8kwzP8IlvTYVpHr9mU,1093
|
|
94
|
+
eitohforge_sdk/infrastructure/database/transaction.py,sha256=25RWpBWswcTZYXGX85uGykpElyBXOnM2gtPncPpKl8o,1545
|
|
95
|
+
eitohforge_sdk/infrastructure/external_api/__init__.py,sha256=7KMxNqDABfaquRz6yO3D7UUF6oD0UfawlTsaY-PSGLo,605
|
|
96
|
+
eitohforge_sdk/infrastructure/external_api/client.py,sha256=aPkkBb69cF9xASruJM9N3DfLMtPii3woHlfzvEzRlaY,4038
|
|
97
|
+
eitohforge_sdk/infrastructure/external_api/contracts.py,sha256=Z4GwOzuCXCe3xRsQvVH-0CJD5GnH0cW4uWUr-FBO_II,2215
|
|
98
|
+
eitohforge_sdk/infrastructure/jobs/__init__.py,sha256=PEt5LuM-NhRxYEpSxadihj3UNp5rYLzHWdXyOHbYPe0,429
|
|
99
|
+
eitohforge_sdk/infrastructure/jobs/contracts.py,sha256=DeUll-s9hGQyjLMRLvIj4gNdjscgHsQAnRuO0owY5GM,2031
|
|
100
|
+
eitohforge_sdk/infrastructure/jobs/memory.py,sha256=0PRgsMJYczK-irXLHepilFWYsLw_ZOLwH2ctb9_Cmw8,4121
|
|
101
|
+
eitohforge_sdk/infrastructure/messaging/__init__.py,sha256=6gUkNBs2L_E0AcNiswtDLH22fsBT9llYijQ311KImi0,528
|
|
102
|
+
eitohforge_sdk/infrastructure/messaging/contracts.py,sha256=XizLqDw-KpziylfbL9hxPz3aCgoxlTsdLtQaqWkwwg8,866
|
|
103
|
+
eitohforge_sdk/infrastructure/messaging/dispatcher.py,sha256=uR4RR-bFgU-v5_6AeC4cPTSrN-dh2DicRlWd1F18U90,1300
|
|
104
|
+
eitohforge_sdk/infrastructure/messaging/redis_bridge.py,sha256=extLXIzqvUA1oVuOaLYFhN_iJmcnU8f_AfXDMnEc3ds,1765
|
|
105
|
+
eitohforge_sdk/infrastructure/notifications/__init__.py,sha256=xLyymFzndR--GYO6xK8ySTR-BWbm0PvFHNHM_k00nQQ,933
|
|
106
|
+
eitohforge_sdk/infrastructure/notifications/contracts.py,sha256=t032SyfBpz0Pl5wvMdp1f-_kVW4azpdKlX8NlApm2tk,1524
|
|
107
|
+
eitohforge_sdk/infrastructure/notifications/gateway.py,sha256=brszi8KIRBNYc5_nPAUV70I7UgbwN-ENyUly234jYXw,2433
|
|
108
|
+
eitohforge_sdk/infrastructure/notifications/template_engine.py,sha256=ibIggwIclH8vZpccMVyRMsXDLAWjRzK9oVIbzvqxg5k,3904
|
|
109
|
+
eitohforge_sdk/infrastructure/repositories/__init__.py,sha256=JdyAv0DQO5-UEISuCuwr6anU4R8UrFDd_KoFOp5_aNc,178
|
|
110
|
+
eitohforge_sdk/infrastructure/repositories/sqlalchemy_repository.py,sha256=UMWUE5HJRR8_tILQeVBen_XrlbmpNmIShO7KOr7AhsA,16358
|
|
111
|
+
eitohforge_sdk/infrastructure/search/__init__.py,sha256=fgIFKXfSods79KTt-MEHqjLoCBE41Y7brf3glV00dfU,625
|
|
112
|
+
eitohforge_sdk/infrastructure/search/contracts.py,sha256=0W0N-_1hwiCV5tuhny_3oOWIiLGKIMOJQv1VQX9ZBpQ,1362
|
|
113
|
+
eitohforge_sdk/infrastructure/search/factory.py,sha256=XW07sVTInjOWtQIXXP6reByslcbrEkxgLeGR_slBPk0,1463
|
|
114
|
+
eitohforge_sdk/infrastructure/search/memory.py,sha256=7IgB6d3X6qM2pIigEusNFff88tBdLdoiTLG5Ut8cs6o,2953
|
|
115
|
+
eitohforge_sdk/infrastructure/search/opensearch.py,sha256=pD75ko9me3J5zr7-fpAlFaMJf5RmGa9P2Wr3RHEo3uI,5172
|
|
116
|
+
eitohforge_sdk/infrastructure/sockets/__init__.py,sha256=BJMATpgBYlkfIcwY-OvUQm615ahy6CuWwarJpxXisK8,793
|
|
117
|
+
eitohforge_sdk/infrastructure/sockets/auth.py,sha256=KPbsVQp3y9doQfq4muEieESALdaNDpHOR_wmmf3Vwdo,2105
|
|
118
|
+
eitohforge_sdk/infrastructure/sockets/contracts.py,sha256=29W67WFAn-jNWlNxMG6uCifCuFmSo0WuheTti1XL_j4,1945
|
|
119
|
+
eitohforge_sdk/infrastructure/sockets/hub.py,sha256=rZJZ3_X5yc2zd6zMI8O7kIzK3hyYU2-_SJsdczp2-VQ,5554
|
|
120
|
+
eitohforge_sdk/infrastructure/sockets/realtime_redis.py,sha256=u6515yOcF6tsBpd2OfwcfzHeIDMyL2L-nUMbzgkah94,2306
|
|
121
|
+
eitohforge_sdk/infrastructure/sockets/realtime_router.py,sha256=AoaVWKG-8VnLK9Zop7bMztIs2IbNftANJDSTjaEGxDg,10860
|
|
122
|
+
eitohforge_sdk/infrastructure/sockets/redis_hub.py,sha256=IXCrIex5MLgaUlT8e_JPprpvkf5lIlyHrOdaBP0GFkM,7317
|
|
123
|
+
eitohforge_sdk/infrastructure/storage/__init__.py,sha256=D6i7YVsm3Hgm2P7NtAE0AoXWLLD6arT4N3ukOfHEWiU,1478
|
|
124
|
+
eitohforge_sdk/infrastructure/storage/cdn.py,sha256=ut3YgA78zy2Le87MrnfzvXbngnO5bzai5wUQcXTkPFk,2359
|
|
125
|
+
eitohforge_sdk/infrastructure/storage/contracts.py,sha256=iUSd_oLZZc_rMPbY7C_chEMIYdK068M6VeETojUzeiU,1020
|
|
126
|
+
eitohforge_sdk/infrastructure/storage/factory.py,sha256=C64CAGFUC_LY3sJwkqkK5Qcr1mMO9rjLYQ3uhhb771M,1818
|
|
127
|
+
eitohforge_sdk/infrastructure/storage/local.py,sha256=iIVg-BU9L8pf6xIvJETabEdPWzH0buYEqG_zbKQ9m74,1559
|
|
128
|
+
eitohforge_sdk/infrastructure/storage/policy.py,sha256=APwVTQZhhEB7EzR2nzjyiieiwcwg4hpdfT81MJVlb5Y,6049
|
|
129
|
+
eitohforge_sdk/infrastructure/storage/s3.py,sha256=VmBQ5WMMg7QfcZ_VE_uvGUVSXCjizOKWX7T3JhOpLGg,3968
|
|
130
|
+
eitohforge_sdk/infrastructure/storage/tenant_scoped.py,sha256=31-I2LhvXAa75xr1OiiK-u81z5ioUbufqHJRhCK-jFI,2690
|
|
131
|
+
eitohforge_sdk/infrastructure/transactions/__init__.py,sha256=ZY7V7Nr_WWLqf_navnbq99vp6fx6z-ZwGfg2GB8t-bE,389
|
|
132
|
+
eitohforge_sdk/infrastructure/transactions/saga.py,sha256=-RX-edo4m9ItSls4jz2IcOSvmoR4PpcBDjrECBleakk,4797
|
|
133
|
+
eitohforge_sdk/infrastructure/webhooks/__init__.py,sha256=7vE5YbIEKF0esPAgeE9_7rfW-JA4eNGsSQ-rWfu-FnM,759
|
|
134
|
+
eitohforge_sdk/infrastructure/webhooks/contracts.py,sha256=OWgASktSjU3iCpuf8aHWcxVb754R-JlaZGbl0KbOmw4,2147
|
|
135
|
+
eitohforge_sdk/infrastructure/webhooks/dispatcher.py,sha256=7wcj662ENqznpxlaCBvN3ZrVk5DNEkZAX-H31OIeZjo,4093
|
|
136
|
+
eitohforge_sdk/infrastructure/webhooks/signing.py,sha256=l4GX3ghWcZvQhwroskmWF06SyJssYGXPQ5EuqKgU31A,1095
|
|
137
|
+
eitohforge-0.1.0.dist-info/METADATA,sha256=eLezD5eKGC_UbNO6kj5xzDlOENrpfop1ASCxiWdm7pU,2624
|
|
138
|
+
eitohforge-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
139
|
+
eitohforge-0.1.0.dist-info/entry_points.txt,sha256=JTYtHW1uTb_zQzF8leKkZ3hZTfABMrlcncBe3sr5KA8,55
|
|
140
|
+
eitohforge-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Create command group."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from eitohforge_cli.template_parts.crud_templates import build_crud_context, render_crud_project_templates
|
|
10
|
+
from eitohforge_cli.templates import build_context, render_project
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
create_app = typer.Typer(help="Generate scaffolded resources.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _validate_project_name(project_name: str) -> None:
|
|
17
|
+
pattern = r"^[A-Za-z][A-Za-z0-9_-]*$"
|
|
18
|
+
if re.fullmatch(pattern, project_name) is None:
|
|
19
|
+
raise typer.BadParameter(
|
|
20
|
+
"Project name must start with a letter and contain only letters, numbers, '-' or '_'."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _validate_module_name(module_name: str) -> None:
|
|
25
|
+
pattern = r"^[A-Za-z][A-Za-z0-9_]*$"
|
|
26
|
+
if re.fullmatch(pattern, module_name) is None:
|
|
27
|
+
raise typer.BadParameter(
|
|
28
|
+
"Module name must start with a letter and contain only letters, numbers, or '_'."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@create_app.command("project")
|
|
33
|
+
def create_project(
|
|
34
|
+
project_name: str,
|
|
35
|
+
path: Path = typer.Option(Path("."), "--path", help="Target parent directory."),
|
|
36
|
+
mode: Literal["sdk", "standalone"] = typer.Option(
|
|
37
|
+
"sdk",
|
|
38
|
+
"--mode",
|
|
39
|
+
help="Scaffold mode: 'sdk' (SDK-first) or 'standalone' (self-contained).",
|
|
40
|
+
),
|
|
41
|
+
profile: Literal["standard", "minimal"] = typer.Option(
|
|
42
|
+
"standard",
|
|
43
|
+
"--profile",
|
|
44
|
+
help="Env defaults: 'standard' (most platform features on) or 'minimal' (opt-in via EITOHFORGE_*).",
|
|
45
|
+
),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Create a new layered backend project scaffold."""
|
|
48
|
+
_validate_project_name(project_name)
|
|
49
|
+
target_parent = path.resolve()
|
|
50
|
+
if not target_parent.exists():
|
|
51
|
+
raise typer.BadParameter(f"Target path does not exist: {target_parent}")
|
|
52
|
+
|
|
53
|
+
project_dir = target_parent / project_name
|
|
54
|
+
if project_dir.exists():
|
|
55
|
+
raise typer.BadParameter(f"Directory already exists: {project_dir}")
|
|
56
|
+
|
|
57
|
+
project_dir.mkdir(parents=True, exist_ok=False)
|
|
58
|
+
render_project(project_dir, build_context(project_name, forge_profile=profile), mode=mode)
|
|
59
|
+
typer.echo(f"Project scaffold created at: {project_dir} (mode={mode}, profile={profile})")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@create_app.command("crud")
|
|
63
|
+
def create_crud_module(
|
|
64
|
+
module_name: str,
|
|
65
|
+
path: Path = typer.Option(Path("."), "--path", help="Target project directory."),
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Generate a CRUD module scaffold inside an existing project."""
|
|
68
|
+
_validate_module_name(module_name)
|
|
69
|
+
context = build_crud_context(module_name)
|
|
70
|
+
project_dir = path.resolve()
|
|
71
|
+
if not project_dir.exists():
|
|
72
|
+
raise typer.BadParameter(f"Project path does not exist: {project_dir}")
|
|
73
|
+
|
|
74
|
+
app_dir = project_dir / "app"
|
|
75
|
+
if not app_dir.exists():
|
|
76
|
+
raise typer.BadParameter(f"Not a generated project (missing app/): {project_dir}")
|
|
77
|
+
|
|
78
|
+
modules_dir = app_dir / "modules" / context.module_name
|
|
79
|
+
if modules_dir.exists():
|
|
80
|
+
raise typer.BadParameter(f"CRUD module already exists: {modules_dir}")
|
|
81
|
+
for relative_path, content in render_crud_project_templates(context).items():
|
|
82
|
+
destination = project_dir / relative_path
|
|
83
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
destination.write_text(content, encoding="utf-8")
|
|
85
|
+
|
|
86
|
+
typer.echo(f"CRUD module scaffold created at: {modules_dir}")
|
|
87
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Database migration commands backed by Alembic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
db_app = typer.Typer(help="Database and migration operations.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _run_alembic(args: list[str], project_path: Path, config_path: Path | None = None) -> None:
|
|
16
|
+
command = [sys.executable, "-m", "alembic"]
|
|
17
|
+
if config_path is not None:
|
|
18
|
+
command.extend(["-c", str(config_path)])
|
|
19
|
+
command.extend(args)
|
|
20
|
+
|
|
21
|
+
result = subprocess.run( # noqa: S603
|
|
22
|
+
command,
|
|
23
|
+
cwd=project_path,
|
|
24
|
+
capture_output=True,
|
|
25
|
+
text=True,
|
|
26
|
+
check=False,
|
|
27
|
+
)
|
|
28
|
+
if result.stdout:
|
|
29
|
+
typer.echo(result.stdout.strip())
|
|
30
|
+
if result.stderr:
|
|
31
|
+
typer.echo(result.stderr.strip(), err=True)
|
|
32
|
+
if result.returncode != 0:
|
|
33
|
+
raise typer.Exit(code=result.returncode)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _resolve_project_context(path: Path, config: str) -> tuple[Path, Path]:
|
|
37
|
+
project_path = path.resolve()
|
|
38
|
+
if not project_path.exists():
|
|
39
|
+
raise typer.BadParameter(f"Project path does not exist: {project_path}")
|
|
40
|
+
config_path = (project_path / config).resolve()
|
|
41
|
+
return project_path, config_path
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@db_app.command("init")
|
|
45
|
+
def init(
|
|
46
|
+
path: Path = typer.Option(Path("."), "--path", help="Project directory path."),
|
|
47
|
+
config: str = typer.Option("alembic.ini", "--config", help="Alembic config filename."),
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Initialize migration files for an existing project."""
|
|
50
|
+
project_path, config_path = _resolve_project_context(path, config)
|
|
51
|
+
migrations_path = project_path / "migrations"
|
|
52
|
+
if config_path.exists() and migrations_path.exists():
|
|
53
|
+
typer.echo("Migration scaffolding already exists.")
|
|
54
|
+
return
|
|
55
|
+
_run_alembic(["init", "migrations"], project_path)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@db_app.command("migrate")
|
|
59
|
+
def migrate(
|
|
60
|
+
message: str = typer.Option(..., "--message", "-m", help="Migration message."),
|
|
61
|
+
path: Path = typer.Option(Path("."), "--path", help="Project directory path."),
|
|
62
|
+
config: str = typer.Option("alembic.ini", "--config", help="Alembic config filename."),
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Create an autogenerated migration revision."""
|
|
65
|
+
project_path, config_path = _resolve_project_context(path, config)
|
|
66
|
+
_run_alembic(["revision", "--autogenerate", "-m", message], project_path, config_path)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@db_app.command("upgrade")
|
|
70
|
+
def upgrade(
|
|
71
|
+
revision: str = typer.Option("head", "--revision", "-r", help="Revision target."),
|
|
72
|
+
path: Path = typer.Option(Path("."), "--path", help="Project directory path."),
|
|
73
|
+
config: str = typer.Option("alembic.ini", "--config", help="Alembic config filename."),
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Upgrade database schema to target revision."""
|
|
76
|
+
project_path, config_path = _resolve_project_context(path, config)
|
|
77
|
+
_run_alembic(["upgrade", revision], project_path, config_path)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@db_app.command("downgrade")
|
|
81
|
+
def downgrade(
|
|
82
|
+
revision: str = typer.Option("-1", "--revision", "-r", help="Revision target."),
|
|
83
|
+
path: Path = typer.Option(Path("."), "--path", help="Project directory path."),
|
|
84
|
+
config: str = typer.Option("alembic.ini", "--config", help="Alembic config filename."),
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Downgrade database schema to target revision."""
|
|
87
|
+
project_path, config_path = _resolve_project_context(path, config)
|
|
88
|
+
_run_alembic(["downgrade", revision], project_path, config_path)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@db_app.command("current")
|
|
92
|
+
def current(
|
|
93
|
+
path: Path = typer.Option(Path("."), "--path", help="Project directory path."),
|
|
94
|
+
config: str = typer.Option("alembic.ini", "--config", help="Alembic config filename."),
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Show current database revision."""
|
|
97
|
+
project_path, config_path = _resolve_project_context(path, config)
|
|
98
|
+
_run_alembic(["current"], project_path, config_path)
|
|
99
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Multi-service local development (several uvicorn processes, one CLI)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
FORGE_DEV_SCHEMA_VERSION = 1
|
|
15
|
+
|
|
16
|
+
dev_app = typer.Typer(
|
|
17
|
+
help="Run multiple FastAPI apps from forge.dev.json (multi-port local dev).",
|
|
18
|
+
invoke_without_command=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _parse_services(data: dict[str, Any]) -> tuple[list[dict[str, Any]], str]:
|
|
23
|
+
if data.get("schema_version") not in (None, FORGE_DEV_SCHEMA_VERSION):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Unsupported forge.dev.json schema_version {data.get('schema_version')!r}; "
|
|
26
|
+
f"expected {FORGE_DEV_SCHEMA_VERSION} or omit."
|
|
27
|
+
)
|
|
28
|
+
services = data.get("services")
|
|
29
|
+
if not isinstance(services, list) or not services:
|
|
30
|
+
raise ValueError("forge.dev.json must contain a non-empty 'services' array.")
|
|
31
|
+
default_host = str(data.get("default_host", "127.0.0.1"))
|
|
32
|
+
return services, default_host
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_service_run(
|
|
36
|
+
*,
|
|
37
|
+
project_root: Path,
|
|
38
|
+
index: int,
|
|
39
|
+
raw: dict[str, Any],
|
|
40
|
+
default_host: str,
|
|
41
|
+
) -> tuple[str, list[str], str, Path]:
|
|
42
|
+
"""Return (label, uvicorn_cmd, display_url, cwd)."""
|
|
43
|
+
name = str(raw.get("name", f"service-{index}"))
|
|
44
|
+
module = raw.get("module")
|
|
45
|
+
if not module or not isinstance(module, str) or ":" not in module:
|
|
46
|
+
raise ValueError(f"Service {name!r} must set 'module' to 'package.module:app'.")
|
|
47
|
+
host = str(raw.get("host", default_host))
|
|
48
|
+
port = int(raw.get("port", 8000 + index))
|
|
49
|
+
env_block = raw.get("env")
|
|
50
|
+
if env_block is not None and not isinstance(env_block, dict):
|
|
51
|
+
raise ValueError(f"Service {name!r}: 'env' must be an object of string keys to string values.")
|
|
52
|
+
cwd = project_root
|
|
53
|
+
wd = raw.get("working_directory")
|
|
54
|
+
if wd:
|
|
55
|
+
cwd = (project_root / str(wd)).resolve()
|
|
56
|
+
if not cwd.is_dir():
|
|
57
|
+
raise ValueError(f"Service {name!r}: working_directory {cwd} is not a directory.")
|
|
58
|
+
cmd = [
|
|
59
|
+
sys.executable,
|
|
60
|
+
"-m",
|
|
61
|
+
"uvicorn",
|
|
62
|
+
module,
|
|
63
|
+
"--host",
|
|
64
|
+
host,
|
|
65
|
+
"--port",
|
|
66
|
+
str(port),
|
|
67
|
+
]
|
|
68
|
+
url = f"http://{host}:{port}"
|
|
69
|
+
return name, cmd, f"{url} ({module})", cwd
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _start_processes(
|
|
73
|
+
*,
|
|
74
|
+
project_root: Path,
|
|
75
|
+
services: list[dict[str, Any]],
|
|
76
|
+
default_host: str,
|
|
77
|
+
) -> list[subprocess.Popen[bytes]]:
|
|
78
|
+
processes: list[subprocess.Popen[bytes]] = []
|
|
79
|
+
for index, raw in enumerate(services):
|
|
80
|
+
if not isinstance(raw, dict):
|
|
81
|
+
raise ValueError("Each entry in 'services' must be an object.")
|
|
82
|
+
name, cmd, display, cwd = _resolve_service_run(
|
|
83
|
+
project_root=project_root, index=index, raw=raw, default_host=default_host
|
|
84
|
+
)
|
|
85
|
+
env = os.environ.copy()
|
|
86
|
+
extra = raw.get("env")
|
|
87
|
+
if isinstance(extra, dict):
|
|
88
|
+
for key, value in extra.items():
|
|
89
|
+
env[str(key)] = str(value)
|
|
90
|
+
typer.echo(f"[eitohforge dev] {name} -> {display}")
|
|
91
|
+
processes.append(subprocess.Popen(cmd, cwd=str(cwd), env=env))
|
|
92
|
+
return processes
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dev_app.callback(invoke_without_command=True)
|
|
96
|
+
def dev_root(
|
|
97
|
+
ctx: typer.Context,
|
|
98
|
+
path: Path = typer.Option(Path("."), "--path", help="Directory containing forge.dev.json."),
|
|
99
|
+
file: str = typer.Option("forge.dev.json", "--file", "-f", help="Manifest file name."),
|
|
100
|
+
) -> None:
|
|
101
|
+
if ctx.invoked_subcommand is not None:
|
|
102
|
+
return
|
|
103
|
+
project_root = path.resolve()
|
|
104
|
+
config_path = project_root / file
|
|
105
|
+
if not config_path.is_file():
|
|
106
|
+
typer.secho(f"Missing {config_path}", fg=typer.colors.RED, err=True)
|
|
107
|
+
raise typer.Exit(code=1)
|
|
108
|
+
try:
|
|
109
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
110
|
+
except json.JSONDecodeError as exc:
|
|
111
|
+
typer.secho(f"Invalid JSON in {config_path}: {exc}", fg=typer.colors.RED, err=True)
|
|
112
|
+
raise typer.Exit(code=1) from exc
|
|
113
|
+
if not isinstance(data, dict):
|
|
114
|
+
typer.secho("forge.dev.json must be a JSON object.", fg=typer.colors.RED, err=True)
|
|
115
|
+
raise typer.Exit(code=1)
|
|
116
|
+
try:
|
|
117
|
+
services, default_host = _parse_services(data)
|
|
118
|
+
processes = _start_processes(project_root=project_root, services=services, default_host=default_host)
|
|
119
|
+
except ValueError as exc:
|
|
120
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
121
|
+
raise typer.Exit(code=1) from exc
|
|
122
|
+
try:
|
|
123
|
+
for proc in processes:
|
|
124
|
+
proc.wait()
|
|
125
|
+
except KeyboardInterrupt:
|
|
126
|
+
typer.echo("\n[eitohforge dev] stopping…")
|
|
127
|
+
for proc in processes:
|
|
128
|
+
proc.terminate()
|
|
129
|
+
for proc in processes:
|
|
130
|
+
proc.wait(timeout=5)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@dev_app.command("validate")
|
|
134
|
+
def dev_validate(
|
|
135
|
+
path: Path = typer.Option(Path("."), "--path"),
|
|
136
|
+
file: str = typer.Option("forge.dev.json", "--file", "-f"),
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Check forge.dev.json without starting servers."""
|
|
139
|
+
project_root = path.resolve()
|
|
140
|
+
config_path = project_root / file
|
|
141
|
+
if not config_path.is_file():
|
|
142
|
+
typer.secho(f"Missing {config_path}", fg=typer.colors.RED, err=True)
|
|
143
|
+
raise typer.Exit(code=1)
|
|
144
|
+
try:
|
|
145
|
+
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
146
|
+
except json.JSONDecodeError as exc:
|
|
147
|
+
typer.secho(f"Invalid JSON in {config_path}: {exc}", fg=typer.colors.RED, err=True)
|
|
148
|
+
raise typer.Exit(code=1) from exc
|
|
149
|
+
if not isinstance(data, dict):
|
|
150
|
+
typer.secho("forge.dev.json must be a JSON object.", fg=typer.colors.RED, err=True)
|
|
151
|
+
raise typer.Exit(code=1)
|
|
152
|
+
try:
|
|
153
|
+
services, default_host = _parse_services(data)
|
|
154
|
+
for index, raw in enumerate(services):
|
|
155
|
+
if not isinstance(raw, dict):
|
|
156
|
+
raise ValueError("Each entry in 'services' must be an object.")
|
|
157
|
+
_resolve_service_run(project_root=project_root, index=index, raw=raw, default_host=default_host)
|
|
158
|
+
except ValueError as exc:
|
|
159
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
160
|
+
raise typer.Exit(code=1) from exc
|
|
161
|
+
typer.secho(f"OK: {len(services)} service(s), default_host={default_host!r}", fg=typer.colors.GREEN)
|
eitohforge_cli/main.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""EitohForge command line entrypoint."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from eitohforge_cli.commands.create import create_app
|
|
6
|
+
from eitohforge_cli.commands.db import db_app
|
|
7
|
+
from eitohforge_cli.commands.dev import dev_app
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
help="EitohForge CLI for generating and operating enterprise FastAPI backends.",
|
|
12
|
+
no_args_is_help=True,
|
|
13
|
+
)
|
|
14
|
+
app.add_typer(create_app, name="create")
|
|
15
|
+
app.add_typer(db_app, name="db")
|
|
16
|
+
app.add_typer(dev_app, name="dev")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@app.callback()
|
|
20
|
+
def root() -> None:
|
|
21
|
+
"""EitohForge root command group."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("version")
|
|
25
|
+
def version() -> None:
|
|
26
|
+
"""Show current CLI version."""
|
|
27
|
+
typer.echo("eitohforge 0.1.0")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run() -> None:
|
|
31
|
+
"""Run the CLI application."""
|
|
32
|
+
app()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
run()
|
|
37
|
+
|