python-infrakit-dev 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 (51) hide show
  1. infrakit/__init__.py +0 -0
  2. infrakit/cli/__init__.py +1 -0
  3. infrakit/cli/commands/__init__.py +1 -0
  4. infrakit/cli/commands/deps.py +530 -0
  5. infrakit/cli/commands/init.py +129 -0
  6. infrakit/cli/commands/llm.py +295 -0
  7. infrakit/cli/commands/logger.py +160 -0
  8. infrakit/cli/commands/module.py +342 -0
  9. infrakit/cli/commands/time.py +81 -0
  10. infrakit/cli/main.py +65 -0
  11. infrakit/core/__init__.py +0 -0
  12. infrakit/core/config/__init__.py +0 -0
  13. infrakit/core/config/converter.py +480 -0
  14. infrakit/core/config/exporter.py +304 -0
  15. infrakit/core/config/loader.py +713 -0
  16. infrakit/core/config/validator.py +389 -0
  17. infrakit/core/logger/__init__.py +21 -0
  18. infrakit/core/logger/formatters.py +143 -0
  19. infrakit/core/logger/handlers.py +322 -0
  20. infrakit/core/logger/retention.py +176 -0
  21. infrakit/core/logger/setup.py +314 -0
  22. infrakit/deps/__init__.py +239 -0
  23. infrakit/deps/clean.py +141 -0
  24. infrakit/deps/depfile.py +405 -0
  25. infrakit/deps/health.py +357 -0
  26. infrakit/deps/optimizer.py +642 -0
  27. infrakit/deps/scanner.py +550 -0
  28. infrakit/llm/__init__.py +35 -0
  29. infrakit/llm/batch.py +165 -0
  30. infrakit/llm/client.py +575 -0
  31. infrakit/llm/key_manager.py +728 -0
  32. infrakit/llm/llm_readme.md +306 -0
  33. infrakit/llm/models.py +148 -0
  34. infrakit/llm/providers/__init__.py +5 -0
  35. infrakit/llm/providers/base.py +112 -0
  36. infrakit/llm/providers/gemini.py +164 -0
  37. infrakit/llm/providers/openai.py +168 -0
  38. infrakit/llm/rate_limiter.py +54 -0
  39. infrakit/scaffolder/__init__.py +31 -0
  40. infrakit/scaffolder/ai.py +508 -0
  41. infrakit/scaffolder/backend.py +555 -0
  42. infrakit/scaffolder/cli_tool.py +386 -0
  43. infrakit/scaffolder/generator.py +338 -0
  44. infrakit/scaffolder/pipeline.py +562 -0
  45. infrakit/scaffolder/registry.py +121 -0
  46. infrakit/time/__init__.py +60 -0
  47. infrakit/time/profiler.py +511 -0
  48. python_infrakit_dev-0.1.0.dist-info/METADATA +124 -0
  49. python_infrakit_dev-0.1.0.dist-info/RECORD +51 -0
  50. python_infrakit_dev-0.1.0.dist-info/WHEEL +4 -0
  51. python_infrakit_dev-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,555 @@
1
+ """
2
+ infrakit.scaffolder.templates.backend
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ Scaffold a FastAPI backend service.
5
+
6
+ Layout
7
+ ------
8
+ <project>/
9
+ ├── app/
10
+ │ ├── __init__.py
11
+ │ ├── main.py # FastAPI app factory + lifespan
12
+ │ ├── config.py # Pydantic Settings — reads from env
13
+ │ ├── dependencies.py # Shared FastAPI dependencies (db, auth, llm)
14
+ │ ├── routes/
15
+ │ │ ├── __init__.py
16
+ │ │ └── health.py # GET /health — always included
17
+ │ ├── models/
18
+ │ │ ├── __init__.py
19
+ │ │ └── base.py # SQLAlchemy / Pydantic base stubs
20
+ │ ├── services/
21
+ │ │ ├── __init__.py
22
+ │ │ └── llm_service.py # thin service wrapper around utils.llm
23
+ │ └── middleware/
24
+ │ ├── __init__.py
25
+ │ └── logging.py # request-level access log middleware
26
+ ├── utils/
27
+ │ ├── __init__.py
28
+ │ ├── logger.py
29
+ │ └── llm.py # infrakit.llm singleton (optional)
30
+ ├── tests/
31
+ │ ├── __init__.py
32
+ │ └── test_health.py
33
+ ├── logs/
34
+ ├── Dockerfile
35
+ ├── docker-compose.yml
36
+ ├── pyproject.toml / requirements.txt
37
+ ├── config.{env|yaml|json}
38
+ ├── README.md
39
+ └── .gitignore
40
+ """
41
+
42
+ from __future__ import annotations
43
+
44
+ from pathlib import Path
45
+
46
+ from infrakit.scaffolder.generator import (
47
+ ScaffoldResult,
48
+ _mkdir,
49
+ _write,
50
+ _config_content,
51
+ _gitignore,
52
+ _logger_util,
53
+ _src_init,
54
+ _tests_init,
55
+ )
56
+
57
+
58
+ # ── template content ──────────────────────────────────────────────────────────
59
+
60
+
61
+ def _app_init(version: str) -> str:
62
+ return f'__version__ = "{version}"\n'
63
+
64
+
65
+ def _app_config(project_name: str) -> str:
66
+ return f'''\
67
+ """
68
+ app.config
69
+ ~~~~~~~~~~
70
+ All configuration is read from environment variables (12-factor style).
71
+
72
+ Usage
73
+ -----
74
+ from app.config import settings
75
+
76
+ print(settings.app_env)
77
+ """
78
+
79
+ from pydantic_settings import BaseSettings, SettingsConfigDict
80
+
81
+
82
+ class Settings(BaseSettings):
83
+ model_config = SettingsConfigDict(env_file=".env", extra="ignore")
84
+
85
+ # application
86
+ app_name: str = "{project_name}"
87
+ app_env: str = "development"
88
+ app_debug: bool = False
89
+ app_host: str = "0.0.0.0"
90
+ app_port: int = 8000
91
+
92
+ # optional: database
93
+ database_url: str = "sqlite:///./app.db"
94
+
95
+ # optional: LLM keys (override ~/.infrakit/llm/ defaults)
96
+ openai_api_key: str = ""
97
+ gemini_api_key: str = ""
98
+ llm_mode: str = "async"
99
+ llm_concurrency: int = 3
100
+
101
+
102
+ settings = Settings()
103
+ '''
104
+
105
+
106
+ def _app_main(project_name: str) -> str:
107
+ title = project_name.replace("-", " ").replace("_", " ").title()
108
+ return f'''\
109
+ """
110
+ app.main
111
+ ~~~~~~~~
112
+ FastAPI application factory.
113
+ """
114
+
115
+ from contextlib import asynccontextmanager
116
+
117
+ from fastapi import FastAPI
118
+ from fastapi.middleware.cors import CORSMiddleware
119
+
120
+ from app.middleware.logging import AccessLogMiddleware
121
+ from app.routes import health
122
+ from utils.logger import get_logger
123
+
124
+ log = get_logger(__name__)
125
+
126
+
127
+ @asynccontextmanager
128
+ async def lifespan(app: FastAPI):
129
+ log.info("startup: {project_name}")
130
+ yield
131
+ log.info("shutdown: {project_name}")
132
+
133
+
134
+ def create_app() -> FastAPI:
135
+ app = FastAPI(
136
+ title="{title}",
137
+ lifespan=lifespan,
138
+ )
139
+
140
+ app.add_middleware(
141
+ CORSMiddleware,
142
+ allow_origins=["*"],
143
+ allow_methods=["*"],
144
+ allow_headers=["*"],
145
+ )
146
+ app.add_middleware(AccessLogMiddleware)
147
+
148
+ app.include_router(health.router)
149
+
150
+ return app
151
+
152
+
153
+ app = create_app()
154
+ '''
155
+
156
+
157
+ def _app_dependencies() -> str:
158
+ return '''\
159
+ """
160
+ app.dependencies
161
+ ~~~~~~~~~~~~~~~~
162
+ Shared FastAPI dependency-injection helpers.
163
+ """
164
+
165
+ from fastapi import Header, HTTPException
166
+
167
+
168
+ async def get_api_key(x_api_key: str = Header(...)) -> str:
169
+ """Placeholder API-key auth dependency."""
170
+ if not x_api_key:
171
+ raise HTTPException(status_code=401, detail="Missing API key")
172
+ return x_api_key
173
+ '''
174
+
175
+
176
+ def _route_health() -> str:
177
+ return '''\
178
+ """
179
+ app.routes.health
180
+ ~~~~~~~~~~~~~~~~~
181
+ GET /health — liveness probe.
182
+ """
183
+
184
+ from fastapi import APIRouter
185
+ from pydantic import BaseModel
186
+
187
+ router = APIRouter(tags=["health"])
188
+
189
+
190
+ class HealthResponse(BaseModel):
191
+ status: str
192
+ version: str
193
+
194
+
195
+ @router.get("/health", response_model=HealthResponse)
196
+ async def health() -> HealthResponse:
197
+ from app import __version__
198
+ return HealthResponse(status="ok", version=__version__)
199
+ '''
200
+
201
+
202
+ def _models_base() -> str:
203
+ return '''\
204
+ """
205
+ app.models.base
206
+ ~~~~~~~~~~~~~~~
207
+ Shared base classes for ORM models and Pydantic schemas.
208
+ """
209
+
210
+ from pydantic import BaseModel, ConfigDict
211
+
212
+
213
+ class APIModel(BaseModel):
214
+ """Base class for all API request/response schemas."""
215
+ model_config = ConfigDict(from_attributes=True)
216
+ '''
217
+
218
+
219
+ def _services_llm() -> str:
220
+ return '''\
221
+ """
222
+ app.services.llm_service
223
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
224
+ Thin async service wrapper around the infrakit LLM client.
225
+
226
+ Keeps route handlers clean — they call this service, never the LLM client directly.
227
+ """
228
+
229
+ from pydantic import BaseModel
230
+ from typing import Optional, Type
231
+
232
+ from utils.llm import llm, Prompt
233
+ from utils.logger import get_logger
234
+
235
+ log = get_logger(__name__)
236
+
237
+
238
+ async def generate(
239
+ user: str,
240
+ *,
241
+ system: Optional[str] = None,
242
+ provider: str = "openai",
243
+ response_model: Optional[Type[BaseModel]] = None,
244
+ ):
245
+ """
246
+ Single-prompt async generate.
247
+
248
+ Returns the LLMResponse — callers should check .error before using .content.
249
+ """
250
+ prompt = Prompt(user=user, system=system)
251
+ response = await llm.async_generate(prompt, provider=provider,
252
+ response_model=response_model)
253
+ if response.error:
254
+ log.warning("llm_service: error — %s", response.error)
255
+ return response
256
+ '''
257
+
258
+
259
+ def _middleware_logging() -> str:
260
+ return '''\
261
+ """
262
+ app.middleware.logging
263
+ ~~~~~~~~~~~~~~~~~~~~~~
264
+ Minimal access-log middleware — logs method, path, status, and latency.
265
+ """
266
+
267
+ import time
268
+
269
+ from starlette.middleware.base import BaseHTTPMiddleware
270
+ from starlette.requests import Request
271
+
272
+ from utils.logger import get_logger
273
+
274
+ log = get_logger("access")
275
+
276
+
277
+ class AccessLogMiddleware(BaseHTTPMiddleware):
278
+ async def dispatch(self, request: Request, call_next):
279
+ t0 = time.perf_counter()
280
+ response = await call_next(request)
281
+ ms = (time.perf_counter() - t0) * 1000
282
+ log.info(
283
+ "%s %s %d %.0fms",
284
+ request.method,
285
+ request.url.path,
286
+ response.status_code,
287
+ ms,
288
+ )
289
+ return response
290
+ '''
291
+
292
+
293
+ def _test_health() -> str:
294
+ return '''\
295
+ """tests.test_health — basic liveness check."""
296
+
297
+ from fastapi.testclient import TestClient
298
+
299
+ from app.main import app
300
+
301
+ client = TestClient(app)
302
+
303
+
304
+ def test_health_ok():
305
+ response = client.get("/health")
306
+ assert response.status_code == 200
307
+ data = response.json()
308
+ assert data["status"] == "ok"
309
+ assert "version" in data
310
+ '''
311
+
312
+
313
+ def _dockerfile(project_name: str) -> str:
314
+ return f"""\
315
+ FROM python:3.11-slim
316
+
317
+ WORKDIR /app
318
+
319
+ COPY pyproject.toml .
320
+ RUN pip install --no-cache-dir -e .
321
+
322
+ COPY . .
323
+
324
+ EXPOSE 8000
325
+
326
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
327
+ """
328
+
329
+
330
+ def _docker_compose(project_name: str) -> str:
331
+ return f"""\
332
+ version: "3.9"
333
+
334
+ services:
335
+ api:
336
+ build: .
337
+ container_name: {project_name}
338
+ ports:
339
+ - "8000:8000"
340
+ env_file:
341
+ - .env
342
+ volumes:
343
+ - ./logs:/app/logs
344
+ restart: unless-stopped
345
+ """
346
+
347
+
348
+ def _backend_pyproject(
349
+ project_name: str, version: str, description: str, author: str
350
+ ) -> str:
351
+ author_line = f' "{author}",' if author else ' # "Your Name <you@example.com>",'
352
+ return f"""\
353
+ [project]
354
+ name = "{project_name}"
355
+ version = "{version}"
356
+ description = "{description}"
357
+ readme = "README.md"
358
+ requires-python = ">=3.10"
359
+ authors = [
360
+ {author_line}
361
+ ]
362
+
363
+ dependencies = [
364
+ "infrakit",
365
+ "fastapi>=0.110",
366
+ "uvicorn[standard]",
367
+ "pydantic>=2.0",
368
+ "pydantic-settings",
369
+ "openai",
370
+ "google-generativeai",
371
+ "tqdm",
372
+ ]
373
+
374
+ [project.optional-dependencies]
375
+ dev = [
376
+ "pytest",
377
+ "pytest-cov",
378
+ "httpx", # required by TestClient
379
+ ]
380
+
381
+ [project.scripts]
382
+ serve = "{project_name}.app.main:app"
383
+ """
384
+
385
+
386
+ def _backend_readme(project_name: str, description: str) -> str:
387
+ title = project_name.replace("-", " ").replace("_", " ").title()
388
+ desc_line = f"\n{description}\n" if description else ""
389
+ return f"""\
390
+ # {title}
391
+ {desc_line}
392
+ ## Setup
393
+
394
+ ```bash
395
+ pip install -e .
396
+ cp .env .env # fill in secrets
397
+ ```
398
+
399
+ ## Run
400
+
401
+ ```bash
402
+ uvicorn app.main:app --reload
403
+ ```
404
+
405
+ Or with Docker:
406
+
407
+ ```bash
408
+ docker-compose up --build
409
+ ```
410
+
411
+ ## Endpoints
412
+
413
+ | Method | Path | Description |
414
+ |---|---|---|
415
+ | GET | `/health` | Liveness probe |
416
+
417
+ ## Structure
418
+
419
+ | Path | Purpose |
420
+ |---|---|
421
+ | `app/main.py` | FastAPI app factory |
422
+ | `app/config.py` | Pydantic Settings (env-driven) |
423
+ | `app/routes/` | Route handlers |
424
+ | `app/models/` | ORM / schema models |
425
+ | `app/services/` | Business logic |
426
+ | `app/middleware/` | Request middleware |
427
+ | `utils/llm.py` | LLM client singleton |
428
+ | `utils/logger.py` | Logger singleton |
429
+
430
+ ## Development
431
+
432
+ ```bash
433
+ pip install -e ".[dev]"
434
+ pytest
435
+ ```
436
+ """
437
+
438
+
439
+ def _backend_gitignore() -> str:
440
+ return _gitignore() + """\
441
+ # Docker
442
+ .dockerignore
443
+
444
+ # Database
445
+ *.db
446
+ *.sqlite3
447
+
448
+ # Keys
449
+ .env
450
+ keys.json
451
+ """
452
+
453
+
454
+ # ── public API ────────────────────────────────────────────────────────────────
455
+
456
+
457
+ def scaffold_backend(
458
+ project_dir: Path,
459
+ *,
460
+ version: str = "0.1.0",
461
+ description: str = "",
462
+ author: str = "",
463
+ config_fmt: str = "env",
464
+ deps: str = "toml",
465
+ include_llm: bool = True,
466
+ ) -> ScaffoldResult:
467
+ """
468
+ Scaffold a FastAPI backend service under ``project_dir``.
469
+
470
+ Parameters
471
+ ----------
472
+ project_dir:
473
+ Root directory for the project.
474
+ version:
475
+ Starting version string.
476
+ description:
477
+ Short project description.
478
+ author:
479
+ Author string.
480
+ config_fmt:
481
+ Config file format — ``"env"``, ``"yaml"``, or ``"json"``.
482
+ deps:
483
+ ``"toml"`` or ``"requirements"``.
484
+ include_llm:
485
+ Whether to include ``utils/llm.py`` and ``app/services/llm_service.py``.
486
+ """
487
+ result = ScaffoldResult(project_dir=project_dir)
488
+ project_name = project_dir.name
489
+
490
+ # ── directories ───────────────────────────────────────────────────────────
491
+ _mkdir(result, project_dir)
492
+ _mkdir(result, project_dir / "app")
493
+ _mkdir(result, project_dir / "app" / "routes")
494
+ _mkdir(result, project_dir / "app" / "models")
495
+ _mkdir(result, project_dir / "app" / "services")
496
+ _mkdir(result, project_dir / "app" / "middleware")
497
+ _mkdir(result, project_dir / "utils")
498
+ _mkdir(result, project_dir / "tests")
499
+ _mkdir(result, project_dir / "logs")
500
+
501
+ # ── app package ───────────────────────────────────────────────────────────
502
+ _write(result, project_dir / "app" / "__init__.py", _app_init(version))
503
+ _write(result, project_dir / "app" / "main.py", _app_main(project_name))
504
+ _write(result, project_dir / "app" / "config.py", _app_config(project_name))
505
+ _write(result, project_dir / "app" / "dependencies.py", _app_dependencies())
506
+
507
+ # ── routes ────────────────────────────────────────────────────────────────
508
+ _write(result, project_dir / "app" / "routes" / "__init__.py", "")
509
+ _write(result, project_dir / "app" / "routes" / "health.py", _route_health())
510
+
511
+ # ── models ────────────────────────────────────────────────────────────────
512
+ _write(result, project_dir / "app" / "models" / "__init__.py", "")
513
+ _write(result, project_dir / "app" / "models" / "base.py", _models_base())
514
+
515
+ # ── services ──────────────────────────────────────────────────────────────
516
+ _write(result, project_dir / "app" / "services" / "__init__.py", "")
517
+ if include_llm:
518
+ _write(result, project_dir / "app" / "services" / "llm_service.py",
519
+ _services_llm())
520
+
521
+ # ── middleware ────────────────────────────────────────────────────────────
522
+ _write(result, project_dir / "app" / "middleware" / "__init__.py", "")
523
+ _write(result, project_dir / "app" / "middleware" / "logging.py",
524
+ _middleware_logging())
525
+
526
+ # ── utils ─────────────────────────────────────────────────────────────────
527
+ _write(result, project_dir / "utils" / "__init__.py", '"""Shared utilities."""\n')
528
+ _write(result, project_dir / "utils" / "logger.py", _logger_util())
529
+
530
+ # ── tests ─────────────────────────────────────────────────────────────────
531
+ _write(result, project_dir / "tests" / "__init__.py", _tests_init())
532
+ _write(result, project_dir / "tests" / "test_health.py", _test_health())
533
+
534
+ # ── config ────────────────────────────────────────────────────────────────
535
+ cfg_name, cfg_content = _config_content(config_fmt)
536
+ _write(result, project_dir / cfg_name, cfg_content)
537
+
538
+ # ── docker ────────────────────────────────────────────────────────────────
539
+ _write(result, project_dir / "Dockerfile", _dockerfile(project_name))
540
+ _write(result, project_dir / "docker-compose.yml", _docker_compose(project_name))
541
+
542
+ # ── dependency file ───────────────────────────────────────────────────────
543
+ if deps == "requirements":
544
+ from infrakit.scaffolder.generator import _requirements_txt
545
+ _write(result, project_dir / "requirements.txt",
546
+ _requirements_txt(project_name))
547
+ else:
548
+ _write(result, project_dir / "pyproject.toml",
549
+ _backend_pyproject(project_name, version, description, author))
550
+
551
+ # ── repo files ────────────────────────────────────────────────────────────
552
+ _write(result, project_dir / "README.md", _backend_readme(project_name, description))
553
+ _write(result, project_dir / ".gitignore", _backend_gitignore())
554
+
555
+ return result