alms-cli 0.1.1__tar.gz → 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alms-cli
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: ALMS CLI - Beautiful project scaffolding tool for AI-first backends
5
5
  Project-URL: Homepage, https://github.com/KJ-AIML/alms
6
6
  Project-URL: Repository, https://github.com/KJ-AIML/alms
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "alms-cli"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "ALMS CLI - Beautiful project scaffolding tool for AI-first backends"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -2,7 +2,6 @@
2
2
 
3
3
  from pathlib import Path
4
4
  from typing import Optional
5
- import shutil
6
5
 
7
6
  class TemplateGenerator:
8
7
  """Generate ALMS project structure from templates."""
@@ -10,12 +9,11 @@ class TemplateGenerator:
10
9
  def __init__(self, project_name: str, project_path: Path):
11
10
  self.project_name = project_name
12
11
  self.project_path = project_path
13
- self.template_dir = Path(__file__).parent / "base"
12
+ self.features = []
14
13
 
15
14
  def generate(self, features: Optional[list[str]] = None) -> int:
16
15
  """Generate project structure and return file count."""
17
- if features is None:
18
- features = []
16
+ self.features = features or []
19
17
 
20
18
  self._create_base_structure()
21
19
  self._create_config_files()
@@ -23,6 +21,10 @@ class TemplateGenerator:
23
21
 
24
22
  return self._count_files()
25
23
 
24
+ def _has_feature(self, keyword: str) -> bool:
25
+ """Check if a feature is selected."""
26
+ return any(keyword.lower() in f.lower() for f in self.features)
27
+
26
28
  def _create_base_structure(self):
27
29
  """Create base directory structure."""
28
30
  dirs = [
@@ -38,8 +40,6 @@ class TemplateGenerator:
38
40
  "src/providers/ai",
39
41
  "src/providers/cache",
40
42
  "src/providers/vectordb",
41
- "src/database/migrations",
42
- "src/database/repositories",
43
43
  "src/core",
44
44
  "src/config",
45
45
  "src/models",
@@ -51,14 +51,24 @@ class TemplateGenerator:
51
51
  "src/tests/v1",
52
52
  "src/tests/integration",
53
53
  "src/tests/e2e",
54
- "alembic",
55
54
  "rules",
56
55
  "docs/changelogs",
57
- ".github/workflows",
58
- ".github/ISSUE_TEMPLATE",
59
56
  "assets/images",
60
57
  ]
61
58
 
59
+ if self._has_feature("database"):
60
+ dirs.extend([
61
+ "src/database/migrations",
62
+ "src/database/repositories",
63
+ "alembic",
64
+ ])
65
+
66
+ if self._has_feature("ci/cd") or self._has_feature("github"):
67
+ dirs.extend([
68
+ ".github/workflows",
69
+ ".github/ISSUE_TEMPLATE",
70
+ ])
71
+
62
72
  for dir_path in dirs:
63
73
  (self.project_path / dir_path).mkdir(parents=True, exist_ok=True)
64
74
 
@@ -70,9 +80,6 @@ class TemplateGenerator:
70
80
  ".env.example": self._env_example(),
71
81
  ".gitignore": self._gitignore(),
72
82
  "pytest.ini": self._pytest_ini(),
73
- "alembic.ini": self._alembic_ini(),
74
- "docker-compose.yml": self._docker_compose(),
75
- "Dockerfile": self._dockerfile(),
76
83
  "rules/project_rules.md": self._project_rules(),
77
84
  "docs/01-System-Design.md": "# System Design\n",
78
85
  "docs/02-Design-Patterns.md": "# Design Patterns\n",
@@ -82,16 +89,31 @@ class TemplateGenerator:
82
89
  "docs/06-API-Documentation.md": "# API Documentation\n",
83
90
  "docs/07-Setup-Installation.md": "# Setup & Installation\n",
84
91
  "docs/08-Contribution-Guide.md": "# Contribution Guide\n",
85
- ".github/workflows/ci.yml": self._ci_workflow(),
86
- ".github/dependabot.yml": self._dependabot(),
87
- ".github/ISSUE_TEMPLATE/bug_report.yml": self._bug_template(),
88
- ".github/ISSUE_TEMPLATE/feature_request.yml": self._feature_template(),
89
- ".github/pull_request_template.md": self._pr_template(),
90
- "alembic/README": "Alembic migrations\n",
91
- "alembic/env.py": self._alembic_env(),
92
- "alembic/script.py.mako": self._alembic_mako(),
93
92
  }
94
93
 
94
+ if self._has_feature("database"):
95
+ templates.update({
96
+ "alembic.ini": self._alembic_ini(),
97
+ "alembic/README": "Alembic migrations\n",
98
+ "alembic/env.py": self._alembic_env(),
99
+ "alembic/script.py.mako": self._alembic_mako(),
100
+ })
101
+
102
+ if self._has_feature("docker"):
103
+ templates.update({
104
+ "docker-compose.yml": self._docker_compose(),
105
+ "Dockerfile": self._dockerfile(),
106
+ })
107
+
108
+ if self._has_feature("ci/cd") or self._has_feature("github"):
109
+ templates.update({
110
+ ".github/workflows/ci.yml": self._ci_workflow(),
111
+ ".github/dependabot.yml": self._dependabot(),
112
+ ".github/ISSUE_TEMPLATE/bug_report.yml": self._bug_template(),
113
+ ".github/ISSUE_TEMPLATE/feature_request.yml": self._feature_template(),
114
+ ".github/pull_request_template.md": self._pr_template(),
115
+ })
116
+
95
117
  for file_path, content in templates.items():
96
118
  full_path = self.project_path / file_path
97
119
  full_path.parent.mkdir(parents=True, exist_ok=True)
@@ -107,7 +129,6 @@ class TemplateGenerator:
107
129
  "src/api/endpoints/v1/__init__.py": "",
108
130
  "src/api/endpoints/v1/dependencies.py": self._dependencies_py(),
109
131
  "src/api/endpoints/v1/health.py": self._health_py(),
110
- "src/api/endpoints/v1/metrics.py": self._metrics_py(),
111
132
  "src/api/endpoints/v1/routers.py": self._routers_py(),
112
133
  "src/api/endpoints/v1/sample_agent.py": self._sample_agent_py(),
113
134
  "src/api/endpoints/v1/sample_di.py": self._sample_di_py(),
@@ -117,7 +138,6 @@ class TemplateGenerator:
117
138
  "src/api/middlewares/__init__.py": "",
118
139
  "src/api/middlewares/error_handler.py": self._error_handler_py(),
119
140
  "src/api/middlewares/logging.py": self._logging_middleware_py(),
120
- "src/api/middlewares/observability.py": self._observability_middleware_py(),
121
141
  "src/api/middlewares/security.py": self._security_middleware_py(),
122
142
  "src/api/router/__init__.py": "",
123
143
  "src/api/router/routers.py": self._router_py(),
@@ -138,15 +158,6 @@ class TemplateGenerator:
138
158
  "src/providers/ai/langchain_model_loader.py": self._model_loader_py(),
139
159
  "src/providers/cache/.gitkeep": "",
140
160
  "src/providers/vectordb/.gitkeep": "",
141
- "src/observability/__init__.py": self._observability_init_py(),
142
- "src/observability/metrics.py": self._metrics_module_py(),
143
- "src/observability/tracing.py": self._tracing_py(),
144
- "src/database/__init__.py": "",
145
- "src/database/connection.py": self._db_connection_py(),
146
- "src/database/migrations/.gitkeep": "",
147
- "src/database/repositories/__init__.py": "",
148
- "src/database/repositories/base.py": self._repository_base_py(),
149
- "src/database/repositories/sqlalchemy_repository.py": self._sqlalchemy_repo_py(),
150
161
  "src/core/__init__.py": "",
151
162
  "src/core/exceptions.py": self._exceptions_py(),
152
163
  "src/config/__init__.py": "",
@@ -164,17 +175,36 @@ class TemplateGenerator:
164
175
  "src/tests/v1/__init__.py": "",
165
176
  "src/tests/v1/test_health.py": self._test_health_py(),
166
177
  "src/tests/v1/test_agent.py": self._test_agent_py(),
167
- "src/tests/v1/test_metrics.py": self._test_metrics_py(),
168
- "src/tests/v1/test_metrics_endpoint.py": self._test_metrics_endpoint_py(),
169
- "src/tests/v1/test_observability_middleware.py": self._test_observability_middleware_py(),
170
- "src/tests/v1/test_sqlalchemy_repository.py": self._test_sqlalchemy_repository_py(),
171
- "src/tests/v1/test_tracing.py": self._test_tracing_py(),
172
178
  "src/tests/integration/__init__.py": "",
173
179
  "src/tests/integration/test_full_stack.py": self._test_full_stack_py(),
174
180
  "src/tests/e2e/__init__.py": "",
175
181
  "src/tests/e2e/test_workflows.py": self._test_workflows_py(),
176
182
  }
177
183
 
184
+ if self._has_feature("observability"):
185
+ templates.update({
186
+ "src/observability/__init__.py": self._observability_init_py(),
187
+ "src/observability/metrics.py": self._metrics_module_py(),
188
+ "src/observability/tracing.py": self._tracing_py(),
189
+ "src/api/middlewares/observability.py": self._observability_middleware_py(),
190
+ "src/api/endpoints/v1/metrics.py": self._metrics_py(),
191
+ "src/tests/v1/test_metrics.py": self._test_metrics_py(),
192
+ "src/tests/v1/test_metrics_endpoint.py": self._test_metrics_endpoint_py(),
193
+ "src/tests/v1/test_observability_middleware.py": self._test_observability_middleware_py(),
194
+ "src/tests/v1/test_tracing.py": self._test_tracing_py(),
195
+ })
196
+
197
+ if self._has_feature("database"):
198
+ templates.update({
199
+ "src/database/__init__.py": "",
200
+ "src/database/connection.py": self._db_connection_py(),
201
+ "src/database/migrations/.gitkeep": "",
202
+ "src/database/repositories/__init__.py": "",
203
+ "src/database/repositories/base.py": self._repository_base_py(),
204
+ "src/database/repositories/sqlalchemy_repository.py": self._sqlalchemy_repo_py(),
205
+ "src/tests/v1/test_sqlalchemy_repository.py": self._test_sqlalchemy_repository_py(),
206
+ })
207
+
178
208
  for file_path, content in templates.items():
179
209
  full_path = self.project_path / file_path
180
210
  full_path.parent.mkdir(parents=True, exist_ok=True)
@@ -196,29 +226,11 @@ description = "ALMS AI-first backend starter"
196
226
  readme = "README.md"
197
227
  requires-python = ">=3.13"
198
228
  dependencies = [
199
- "alembic>=1.17.2",
200
- "asyncpg>=0.31.0",
201
229
  "fastapi[standard]>=0.122.0",
202
- "langchain>=1.1.0",
203
- "langchain-community>=0.4.1",
204
- "langchain-mcp-adapters>=0.1.14",
205
- "langchain-openai>=1.1.0",
206
- "langgraph>=1.0.4",
207
- "openai>=2.8.1",
208
- "opentelemetry-api>=1.25.0",
209
- "opentelemetry-sdk>=1.25.0",
210
- "opentelemetry-instrumentation-fastapi>=0.46b0",
211
- "opentelemetry-instrumentation-sqlalchemy>=0.46b0",
212
- "opentelemetry-instrumentation-redis>=0.46b0",
213
- "opentelemetry-exporter-otlp>=1.25.0",
214
- "prometheus-client>=0.20.0",
215
230
  "pydantic>=2.12.5",
216
231
  "pydantic-settings>=2.12.0",
217
- "redis>=7.1.0",
218
- "ruff>=0.14.11",
219
- "scalar-fastapi>=1.6.0",
220
- "sqlalchemy>=2.0.44",
221
232
  "uvicorn>=0.38.0",
233
+ "ruff>=0.14.11",
222
234
  ]
223
235
 
224
236
  [dependency-groups]
@@ -239,17 +251,12 @@ dev = [
239
251
  ### Prerequisites
240
252
 
241
253
  - Python 3.13+
242
- - PostgreSQL (optional)
243
- - Redis (optional)
244
254
  - [uv](https://docs.astral.sh/uv/getting-started/installation/) installed
245
255
 
246
256
  ### 1. Setup
247
257
 
248
258
  ```bash
249
- # Copy environment file
250
259
  cp .env.example .env
251
-
252
- # Edit .env with your credentials
253
260
  ```
254
261
 
255
262
  ### 2. Install Dependencies
@@ -258,14 +265,7 @@ cp .env.example .env
258
265
  uv sync
259
266
  ```
260
267
 
261
- ### 3. Run Database Migrations
262
-
263
- ```bash
264
- alembic revision --autogenerate -m "Initial migration"
265
- alembic upgrade head
266
- ```
267
-
268
- ### 4. Start the Application
268
+ ### 3. Start the Application
269
269
 
270
270
  ```bash
271
271
  uv run -m src.api.main
@@ -282,22 +282,6 @@ OPENAI_API_KEY=sk-...
282
282
  OPENAI_MODEL_BASIC=gpt-4o-mini
283
283
  OPENAI_MODEL_REASONING=gpt-4o
284
284
 
285
- # Database Configuration
286
- DATABASE_HOST=localhost
287
- DATABASE_PORT=5432
288
- DATABASE_NAME=mydb
289
- DATABASE_USER=postgres
290
- DATABASE_PASSWORD=secret
291
-
292
- # Redis Configuration
293
- REDIS_HOST=localhost
294
- REDIS_PORT=6379
295
-
296
- # Observability (Optional)
297
- OTLP_ENDPOINT=http://localhost:4317
298
- METRICS_ENABLED=true
299
- TRACING_ENABLED=true
300
-
301
285
  # Application
302
286
  DEBUG=true
303
287
  LOG_LEVEL=info
@@ -329,10 +313,6 @@ build/
329
313
  .env
330
314
  .env.local
331
315
 
332
- # Database
333
- *.db
334
- *.sqlite3
335
-
336
316
  # Logs
337
317
  *.log
338
318
  logs/
@@ -345,9 +325,6 @@ Thumbs.db
345
325
  .pytest_cache/
346
326
  .coverage
347
327
  htmlcov/
348
-
349
- # Alembic
350
- alembic/versions/*.pyc
351
328
  '''
352
329
 
353
330
  def _pytest_ini(self) -> str:
@@ -357,6 +334,18 @@ python_files = test_*.py
357
334
  python_classes = Test*
358
335
  python_functions = test_*
359
336
  asyncio_mode = auto
337
+ '''
338
+
339
+ def _project_rules(self) -> str:
340
+ return '''# Project Rules
341
+
342
+ ## ALMS Architecture Guidelines
343
+
344
+ 1. Never skip layers: API -> Execution -> Agent -> Infrastructure
345
+ 2. Use repository pattern for database access
346
+ 3. Use custom exceptions, not raw HTTPException
347
+ 4. Add type hints to all functions
348
+ 5. Write tests for all use cases and actions
360
349
  '''
361
350
 
362
351
  def _alembic_ini(self) -> str:
@@ -440,18 +429,6 @@ COPY . .
440
429
  EXPOSE 3000
441
430
 
442
431
  CMD ["uv", "run", "-m", "src.api.main", "--host", "0.0.0.0"]
443
- '''
444
-
445
- def _project_rules(self) -> str:
446
- return '''# Project Rules
447
-
448
- ## ALMS Architecture Guidelines
449
-
450
- 1. Never skip layers: API -> Execution -> Agent -> Infrastructure
451
- 2. Use repository pattern for database access
452
- 3. Use custom exceptions, not raw HTTPException
453
- 4. Add type hints to all functions
454
- 5. Write tests for all use cases and actions
455
432
  '''
456
433
 
457
434
  def _ci_workflow(self) -> str:
@@ -587,7 +564,6 @@ from src.api.router.routers import include_routers
587
564
  from src.api.middlewares.security import setup_security_middleware
588
565
  from src.api.middlewares.logging import setup_logging_middleware
589
566
  from src.api.middlewares.error_handler import setup_error_handler
590
- from src.api.middlewares.observability import setup_observability_middleware
591
567
 
592
568
 
593
569
  def create_app() -> FastAPI:
@@ -602,7 +578,6 @@ def create_app() -> FastAPI:
602
578
  setup_security_middleware(app)
603
579
  setup_logging_middleware(app)
604
580
  setup_error_handler(app)
605
- setup_observability_middleware(app)
606
581
 
607
582
  app.add_middleware(
608
583
  CORSMiddleware,
@@ -687,12 +662,11 @@ async def metrics():
687
662
  return '''"""Main v1 router aggregation."""
688
663
 
689
664
  from fastapi import APIRouter
690
- from src.api.endpoints.v1 import health, metrics, sample_agent, sample_di
665
+ from src.api.endpoints.v1 import health, sample_agent, sample_di
691
666
 
692
667
  v1_router = APIRouter(prefix="/api/v1")
693
668
 
694
669
  v1_router.include_router(health.router, tags=["Health"])
695
- v1_router.include_router(metrics.router, tags=["Metrics"])
696
670
  v1_router.include_router(sample_agent.router, tags=["Agent"])
697
671
  v1_router.include_router(sample_di.router, tags=["DI Example"])
698
672
  '''
@@ -1142,29 +1116,8 @@ class Settings(BaseSettings):
1142
1116
  OPENAI_MODEL_BASIC: str = "gpt-4o-mini"
1143
1117
  OPENAI_MODEL_REASONING: str = "gpt-4o"
1144
1118
 
1145
- DATABASE_HOST: str = "localhost"
1146
- DATABASE_PORT: int = 5432
1147
- DATABASE_NAME: str = "mydb"
1148
- DATABASE_USER: str = "postgres"
1149
- DATABASE_PASSWORD: str = "secret"
1150
-
1151
- REDIS_HOST: str = "localhost"
1152
- REDIS_PORT: int = 6379
1153
-
1154
- OTLP_ENDPOINT: str = "http://localhost:4317"
1155
- METRICS_ENABLED: bool = True
1156
- TRACING_ENABLED: bool = True
1157
-
1158
1119
  LOG_LEVEL: str = "info"
1159
1120
 
1160
- @property
1161
- def DATABASE_URL(self) -> str:
1162
- return f"postgresql+asyncpg://{self.DATABASE_USER}:{self.DATABASE_PASSWORD}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
1163
-
1164
- @property
1165
- def REDIS_URL(self) -> str:
1166
- return f"redis://{self.REDIS_HOST}:{self.REDIS_PORT}/0"
1167
-
1168
1121
  class Config:
1169
1122
  env_file = ".env"
1170
1123
  case_sensitive = True
File without changes
File without changes
File without changes
File without changes