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.
Files changed (140) hide show
  1. eitohforge-0.1.0.dist-info/METADATA +72 -0
  2. eitohforge-0.1.0.dist-info/RECORD +140 -0
  3. eitohforge-0.1.0.dist-info/WHEEL +4 -0
  4. eitohforge-0.1.0.dist-info/entry_points.txt +2 -0
  5. eitohforge_cli/__init__.py +2 -0
  6. eitohforge_cli/commands/__init__.py +2 -0
  7. eitohforge_cli/commands/create.py +87 -0
  8. eitohforge_cli/commands/db.py +99 -0
  9. eitohforge_cli/commands/dev.py +161 -0
  10. eitohforge_cli/main.py +37 -0
  11. eitohforge_cli/template_parts/__init__.py +2 -0
  12. eitohforge_cli/template_parts/application_templates.py +231 -0
  13. eitohforge_cli/template_parts/cache_templates.py +271 -0
  14. eitohforge_cli/template_parts/core_templates.py +15 -0
  15. eitohforge_cli/template_parts/core_templates_auth.py +723 -0
  16. eitohforge_cli/template_parts/core_templates_platform.py +699 -0
  17. eitohforge_cli/template_parts/core_templates_runtime.py +1142 -0
  18. eitohforge_cli/template_parts/core_templates_security.py +282 -0
  19. eitohforge_cli/template_parts/core_templates_validation.py +391 -0
  20. eitohforge_cli/template_parts/crud_templates.py +322 -0
  21. eitohforge_cli/template_parts/external_api_templates.py +188 -0
  22. eitohforge_cli/template_parts/jobs_templates.py +191 -0
  23. eitohforge_cli/template_parts/messaging_templates.py +64 -0
  24. eitohforge_cli/template_parts/notification_templates.py +236 -0
  25. eitohforge_cli/template_parts/search_templates.py +315 -0
  26. eitohforge_cli/template_parts/socket_templates.py +400 -0
  27. eitohforge_cli/template_parts/storage_templates.py +525 -0
  28. eitohforge_cli/template_parts/webhook_templates.py +221 -0
  29. eitohforge_cli/templates.py +1668 -0
  30. eitohforge_sdk/__init__.py +2 -0
  31. eitohforge_sdk/application/__init__.py +14 -0
  32. eitohforge_sdk/application/dto/__init__.py +40 -0
  33. eitohforge_sdk/application/dto/error.py +41 -0
  34. eitohforge_sdk/application/dto/repository.py +116 -0
  35. eitohforge_sdk/application/dto/response.py +55 -0
  36. eitohforge_sdk/application/query_spec_support.py +19 -0
  37. eitohforge_sdk/application/services/__init__.py +6 -0
  38. eitohforge_sdk/application/services/validation.py +68 -0
  39. eitohforge_sdk/core/__init__.py +267 -0
  40. eitohforge_sdk/core/abac.py +147 -0
  41. eitohforge_sdk/core/api_version_deprecation.py +39 -0
  42. eitohforge_sdk/core/api_versioning.py +29 -0
  43. eitohforge_sdk/core/audit.py +105 -0
  44. eitohforge_sdk/core/auth/__init__.py +82 -0
  45. eitohforge_sdk/core/auth/jwt.py +292 -0
  46. eitohforge_sdk/core/auth/session.py +241 -0
  47. eitohforge_sdk/core/auth/sso.py +179 -0
  48. eitohforge_sdk/core/auth/sso_adapters.py +147 -0
  49. eitohforge_sdk/core/capabilities.py +201 -0
  50. eitohforge_sdk/core/config.py +448 -0
  51. eitohforge_sdk/core/deployment.py +35 -0
  52. eitohforge_sdk/core/error_middleware.py +46 -0
  53. eitohforge_sdk/core/error_registry.py +76 -0
  54. eitohforge_sdk/core/feature_catalog.py +77 -0
  55. eitohforge_sdk/core/feature_flags.py +110 -0
  56. eitohforge_sdk/core/forge_application.py +282 -0
  57. eitohforge_sdk/core/forge_toggles.py +63 -0
  58. eitohforge_sdk/core/health.py +104 -0
  59. eitohforge_sdk/core/idempotency.py +159 -0
  60. eitohforge_sdk/core/observability.py +186 -0
  61. eitohforge_sdk/core/performance.py +58 -0
  62. eitohforge_sdk/core/plugins.py +49 -0
  63. eitohforge_sdk/core/rate_limit.py +89 -0
  64. eitohforge_sdk/core/request_signing.py +154 -0
  65. eitohforge_sdk/core/secret_factory.py +29 -0
  66. eitohforge_sdk/core/secrets.py +154 -0
  67. eitohforge_sdk/core/security.py +114 -0
  68. eitohforge_sdk/core/security_context.py +62 -0
  69. eitohforge_sdk/core/security_hardening.py +65 -0
  70. eitohforge_sdk/core/tenant.py +142 -0
  71. eitohforge_sdk/core/validation/__init__.py +45 -0
  72. eitohforge_sdk/core/validation/context.py +29 -0
  73. eitohforge_sdk/core/validation/contracts.py +23 -0
  74. eitohforge_sdk/core/validation/engine.py +51 -0
  75. eitohforge_sdk/core/validation/errors.py +45 -0
  76. eitohforge_sdk/core/validation/hooks.py +116 -0
  77. eitohforge_sdk/core/validation/rules.py +167 -0
  78. eitohforge_sdk/domain/__init__.py +20 -0
  79. eitohforge_sdk/domain/repositories/__init__.py +6 -0
  80. eitohforge_sdk/domain/repositories/contracts.py +56 -0
  81. eitohforge_sdk/domain/value_objects/__init__.py +16 -0
  82. eitohforge_sdk/domain/value_objects/contact.py +23 -0
  83. eitohforge_sdk/domain/value_objects/errors.py +6 -0
  84. eitohforge_sdk/domain/value_objects/identifiers.py +43 -0
  85. eitohforge_sdk/domain/value_objects/time.py +19 -0
  86. eitohforge_sdk/infrastructure/__init__.py +179 -0
  87. eitohforge_sdk/infrastructure/cache/__init__.py +19 -0
  88. eitohforge_sdk/infrastructure/cache/contracts.py +33 -0
  89. eitohforge_sdk/infrastructure/cache/factory.py +37 -0
  90. eitohforge_sdk/infrastructure/cache/invalidation.py +101 -0
  91. eitohforge_sdk/infrastructure/cache/memory.py +44 -0
  92. eitohforge_sdk/infrastructure/cache/redis.py +48 -0
  93. eitohforge_sdk/infrastructure/cache/tenant_scoped.py +43 -0
  94. eitohforge_sdk/infrastructure/database/__init__.py +24 -0
  95. eitohforge_sdk/infrastructure/database/factory.py +47 -0
  96. eitohforge_sdk/infrastructure/database/providers.py +150 -0
  97. eitohforge_sdk/infrastructure/database/registry.py +33 -0
  98. eitohforge_sdk/infrastructure/database/transaction.py +53 -0
  99. eitohforge_sdk/infrastructure/external_api/__init__.py +26 -0
  100. eitohforge_sdk/infrastructure/external_api/client.py +112 -0
  101. eitohforge_sdk/infrastructure/external_api/contracts.py +79 -0
  102. eitohforge_sdk/infrastructure/jobs/__init__.py +20 -0
  103. eitohforge_sdk/infrastructure/jobs/contracts.py +76 -0
  104. eitohforge_sdk/infrastructure/jobs/memory.py +118 -0
  105. eitohforge_sdk/infrastructure/messaging/__init__.py +18 -0
  106. eitohforge_sdk/infrastructure/messaging/contracts.py +32 -0
  107. eitohforge_sdk/infrastructure/messaging/dispatcher.py +36 -0
  108. eitohforge_sdk/infrastructure/messaging/redis_bridge.py +49 -0
  109. eitohforge_sdk/infrastructure/notifications/__init__.py +36 -0
  110. eitohforge_sdk/infrastructure/notifications/contracts.py +52 -0
  111. eitohforge_sdk/infrastructure/notifications/gateway.py +70 -0
  112. eitohforge_sdk/infrastructure/notifications/template_engine.py +119 -0
  113. eitohforge_sdk/infrastructure/repositories/__init__.py +6 -0
  114. eitohforge_sdk/infrastructure/repositories/sqlalchemy_repository.py +373 -0
  115. eitohforge_sdk/infrastructure/search/__init__.py +23 -0
  116. eitohforge_sdk/infrastructure/search/contracts.py +65 -0
  117. eitohforge_sdk/infrastructure/search/factory.py +42 -0
  118. eitohforge_sdk/infrastructure/search/memory.py +88 -0
  119. eitohforge_sdk/infrastructure/search/opensearch.py +147 -0
  120. eitohforge_sdk/infrastructure/sockets/__init__.py +28 -0
  121. eitohforge_sdk/infrastructure/sockets/auth.py +61 -0
  122. eitohforge_sdk/infrastructure/sockets/contracts.py +72 -0
  123. eitohforge_sdk/infrastructure/sockets/hub.py +145 -0
  124. eitohforge_sdk/infrastructure/sockets/realtime_redis.py +67 -0
  125. eitohforge_sdk/infrastructure/sockets/realtime_router.py +285 -0
  126. eitohforge_sdk/infrastructure/sockets/redis_hub.py +214 -0
  127. eitohforge_sdk/infrastructure/storage/__init__.py +47 -0
  128. eitohforge_sdk/infrastructure/storage/cdn.py +64 -0
  129. eitohforge_sdk/infrastructure/storage/contracts.py +44 -0
  130. eitohforge_sdk/infrastructure/storage/factory.py +50 -0
  131. eitohforge_sdk/infrastructure/storage/local.py +46 -0
  132. eitohforge_sdk/infrastructure/storage/policy.py +185 -0
  133. eitohforge_sdk/infrastructure/storage/s3.py +117 -0
  134. eitohforge_sdk/infrastructure/storage/tenant_scoped.py +78 -0
  135. eitohforge_sdk/infrastructure/transactions/__init__.py +20 -0
  136. eitohforge_sdk/infrastructure/transactions/saga.py +132 -0
  137. eitohforge_sdk/infrastructure/webhooks/__init__.py +30 -0
  138. eitohforge_sdk/infrastructure/webhooks/contracts.py +83 -0
  139. eitohforge_sdk/infrastructure/webhooks/dispatcher.py +113 -0
  140. 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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ eitohforge = eitohforge_cli.main:run
@@ -0,0 +1,2 @@
1
+ """EitohForge CLI package."""
2
+
@@ -0,0 +1,2 @@
1
+ """CLI command groups."""
2
+
@@ -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
+
@@ -0,0 +1,2 @@
1
+ """Template fragment modules."""
2
+