python-infrakit-dev 0.1.2__py3-none-any.whl → 0.1.3__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.
infrakit/scaffolder/ai.py CHANGED
@@ -58,9 +58,12 @@ utils.llm
58
58
  ~~~~~~~~~
59
59
  Thin wrapper that boots the infrakit LLM client once and exports it.
60
60
 
61
+ Reads all configuration from the project config file (.env / config.yaml /
62
+ config.json) via infrakit.config — no raw os.getenv calls.
63
+
61
64
  The client reads key state from ``~/.infrakit/llm/`` by default, and
62
65
  loads quota limits from ``~/.infrakit/llm/quotas.json`` if that file
63
- exists. Both paths can be overridden with environment variables.
66
+ exists. Both paths can be overridden via LLM_STATE_DIR / LLM_QUOTA_FILE.
64
67
 
65
68
  Usage
66
69
  -----
@@ -89,24 +92,38 @@ Usage
89
92
  """
90
93
 
91
94
  import json
92
- import os
93
95
  from pathlib import Path
94
96
 
97
+ from infrakit.core.config.loader import load, load_env
95
98
  from infrakit.llm import LLMClient, Prompt # re-export Prompt for convenience
96
99
 
100
+ # ── config loading ────────────────────────────────────────────────────────────
101
+
102
+ def _load_cfg() -> dict:
103
+ if Path(".env").exists():
104
+ return load_env(".env", cast_values=True)
105
+ if Path("config.yaml").exists():
106
+ return load("config.yaml")
107
+ if Path("config.json").exists():
108
+ return load("config.json")
109
+ return {{}}
110
+
111
+
112
+ _cfg = _load_cfg()
113
+
97
114
  # ── key loading ───────────────────────────────────────────────────────────────
98
- # Keys are read from the environment or from a local keys.json file.
115
+ # Keys are read from the project config or from a local keys.json file.
99
116
  # Never commit real API keys — use .env or your secret manager.
100
117
 
101
118
  def _load_keys() -> dict:
102
- keys_file = Path(os.getenv("LLM_KEYS_FILE", "keys.json"))
119
+ keys_file = Path(_cfg.get("LLM_KEYS_FILE", "keys.json"))
103
120
  if keys_file.exists():
104
121
  with open(keys_file) as f:
105
122
  return json.load(f)
106
123
 
107
- # fall back to individual env vars
108
- openai_key = os.getenv("OPENAI_API_KEY", "")
109
- gemini_key = os.getenv("GEMINI_API_KEY", "")
124
+ # fall back to keys declared in the config file
125
+ openai_key = _cfg.get("OPENAI_API_KEY", "")
126
+ gemini_key = _cfg.get("GEMINI_API_KEY", "")
110
127
  return {{
111
128
  "openai_keys": [openai_key] if openai_key else [],
112
129
  "gemini_keys": [gemini_key] if gemini_key else [],
@@ -118,13 +135,13 @@ def _load_keys() -> dict:
118
135
  llm: LLMClient = LLMClient(
119
136
  keys=_load_keys(),
120
137
  # storage_dir and quota_file default to ~/.infrakit/llm/
121
- # override with env vars if needed:
122
- storage_dir=os.getenv("LLM_STATE_DIR") or None,
123
- quota_file=os.getenv("LLM_QUOTA_FILE") or None,
124
- mode=os.getenv("LLM_MODE", "async"), # "async" | "threaded"
125
- max_concurrent=int(os.getenv("LLM_CONCURRENCY", "3")),
126
- openai_model=os.getenv("OPENAI_MODEL") or None,
127
- gemini_model=os.getenv("GEMINI_MODEL") or None,
138
+ # override via LLM_STATE_DIR / LLM_QUOTA_FILE in your config file:
139
+ storage_dir=_cfg.get("LLM_STATE_DIR") or None,
140
+ quota_file=_cfg.get("LLM_QUOTA_FILE") or None,
141
+ mode=_cfg.get("LLM_MODE", "async"), # "async" | "threaded"
142
+ max_concurrent=int(_cfg.get("LLM_CONCURRENCY", 3)),
143
+ openai_model=_cfg.get("OPENAI_MODEL") or None,
144
+ gemini_model=_cfg.get("GEMINI_MODEL") or None,
128
145
  )
129
146
 
130
147
  __all__ = ["llm", "Prompt"]
@@ -474,10 +491,14 @@ def scaffold_ai(
474
491
  _write(result, project_dir / "pipelines" / "preprocess.py", _pipeline_preprocess())
475
492
  _write(result, project_dir / "pipelines" / "predict.py", _pipeline_predict())
476
493
 
494
+ # ── prompts ───────────────────────────────────────────────────────────────
495
+ _write(result, project_dir / "prompts" / "default.txt", _default_prompt())
496
+
477
497
  # ── utils ─────────────────────────────────────────────────────────────────
478
498
  _write(result, project_dir / "utils" / "__init__.py", '"""Shared utilities."""\n')
479
499
  _write(result, project_dir / "utils" / "logger.py", _logger_util())
480
- _write(result, project_dir / "utils" / "llm.py", _llm_util(project_name))
500
+ if include_llm:
501
+ _write(result, project_dir / "utils" / "llm.py", _llm_util(project_name))
481
502
 
482
503
  # ── notebooks ─────────────────────────────────────────────────────────────
483
504
  if include_notebooks:
@@ -488,7 +509,7 @@ def scaffold_ai(
488
509
  _write(result, project_dir / "tests" / "__init__.py", _tests_init())
489
510
 
490
511
  # ── config ────────────────────────────────────────────────────────────────
491
- cfg_name, cfg_content = _config_content(config_fmt)
512
+ cfg_name, cfg_content = _config_content(config_fmt, include_llm=include_llm)
492
513
  _write(result, project_dir / cfg_name, cfg_content)
493
514
 
494
515
  # ── keys template (safe placeholder — never contains real keys) ───────────
@@ -47,7 +47,6 @@ from infrakit.scaffolder.generator import (
47
47
  ScaffoldResult,
48
48
  _mkdir,
49
49
  _write,
50
- _config_content,
51
50
  _gitignore,
52
51
  _logger_util,
53
52
  _src_init,
@@ -58,6 +57,107 @@ from infrakit.scaffolder.generator import (
58
57
  # ── template content ──────────────────────────────────────────────────────────
59
58
 
60
59
 
60
+ def _backend_env_config(project_name: str, include_llm: bool) -> str:
61
+ llm_block = """\
62
+
63
+ # LLM
64
+ LLM_KEYS_FILE=keys.json
65
+ OPENAI_API_KEY=
66
+ GEMINI_API_KEY=
67
+ LLM_MODE=async
68
+ LLM_CONCURRENCY=3
69
+ # OPENAI_MODEL=gpt-4o
70
+ # GEMINI_MODEL=gemini-2.0-flash
71
+ """ if include_llm else ""
72
+ return f"""\
73
+ # Application
74
+ APP_NAME={project_name}
75
+ APP_ENV=development
76
+ APP_DEBUG=false
77
+ APP_HOST=0.0.0.0
78
+ APP_PORT=8000
79
+
80
+ # Database
81
+ DATABASE_URL=sqlite:///./app.db
82
+
83
+ # Logger
84
+ LOG_DIR=logs
85
+ LOG_STRATEGY=date
86
+ LOG_STREAM=stdout
87
+ LOG_FORMAT=human
88
+ LOG_LEVEL=DEBUG
89
+ {llm_block}"""
90
+
91
+
92
+ def _backend_yaml_config(project_name: str, include_llm: bool) -> str:
93
+ llm_block = """
94
+
95
+ # LLM
96
+ LLM_KEYS_FILE: keys.json
97
+ OPENAI_API_KEY: ""
98
+ GEMINI_API_KEY: ""
99
+ LLM_MODE: async
100
+ LLM_CONCURRENCY: 3
101
+ # OPENAI_MODEL: gpt-4o
102
+ # GEMINI_MODEL: gemini-2.0-flash
103
+ """ if include_llm else ""
104
+ return f"""\
105
+ # Application configuration
106
+ app:
107
+ name: {project_name}
108
+ env: development
109
+ debug: false
110
+ host: 0.0.0.0
111
+ port: 8000
112
+
113
+ # Database
114
+ DATABASE_URL: sqlite:///./app.db
115
+
116
+ # Logger (flat keys — read by utils/logger.py via infrakit.config)
117
+ LOG_DIR: logs
118
+ LOG_STRATEGY: date
119
+ LOG_STREAM: stdout
120
+ LOG_FORMAT: human
121
+ LOG_LEVEL: DEBUG
122
+ {llm_block}"""
123
+
124
+
125
+ def _backend_json_config(project_name: str, include_llm: bool) -> str:
126
+ llm_keys = """,
127
+ "LLM_KEYS_FILE": "keys.json",
128
+ "OPENAI_API_KEY": "",
129
+ "GEMINI_API_KEY": "",
130
+ "LLM_MODE": "async",
131
+ "LLM_CONCURRENCY": 3""" if include_llm else ""
132
+ return f"""\
133
+ {{
134
+ "app": {{
135
+ "name": "{project_name}",
136
+ "env": "development",
137
+ "debug": false,
138
+ "host": "0.0.0.0",
139
+ "port": 8000
140
+ }},
141
+ "DATABASE_URL": "sqlite:///./app.db",
142
+ "LOG_DIR": "logs",
143
+ "LOG_STRATEGY": "date",
144
+ "LOG_STREAM": "stdout",
145
+ "LOG_FORMAT": "human",
146
+ "LOG_LEVEL": "DEBUG"{llm_keys}
147
+ }}
148
+ """
149
+
150
+
151
+ def _backend_config_content(
152
+ fmt: str, project_name: str, include_llm: bool
153
+ ) -> tuple[str, str]:
154
+ if fmt == "yaml":
155
+ return "config.yaml", _backend_yaml_config(project_name, include_llm)
156
+ if fmt == "json":
157
+ return "config.json", _backend_json_config(project_name, include_llm)
158
+ return ".env", _backend_env_config(project_name, include_llm)
159
+
160
+
61
161
  def _app_init(version: str) -> str:
62
162
  return f'__version__ = "{version}"\n'
63
163
 
@@ -526,13 +626,17 @@ def scaffold_backend(
526
626
  # ── utils ─────────────────────────────────────────────────────────────────
527
627
  _write(result, project_dir / "utils" / "__init__.py", '"""Shared utilities."""\n')
528
628
  _write(result, project_dir / "utils" / "logger.py", _logger_util())
629
+ if include_llm:
630
+ from infrakit.scaffolder.ai import _llm_util, _keys_json_template
631
+ _write(result, project_dir / "utils" / "llm.py", _llm_util(project_name))
632
+ _write(result, project_dir / "keys.json", _keys_json_template())
529
633
 
530
634
  # ── tests ─────────────────────────────────────────────────────────────────
531
635
  _write(result, project_dir / "tests" / "__init__.py", _tests_init())
532
636
  _write(result, project_dir / "tests" / "test_health.py", _test_health())
533
637
 
534
638
  # ── config ────────────────────────────────────────────────────────────────
535
- cfg_name, cfg_content = _config_content(config_fmt)
639
+ cfg_name, cfg_content = _backend_config_content(config_fmt, project_name, include_llm)
536
640
  _write(result, project_dir / cfg_name, cfg_content)
537
641
 
538
642
  # ── docker ────────────────────────────────────────────────────────────────
@@ -366,7 +366,7 @@ def scaffold_cli_tool(
366
366
  _write(result, project_dir / "tests" / "test_cli.py", _test_cli(project_name))
367
367
 
368
368
  # ── config ────────────────────────────────────────────────────────────────
369
- cfg_name, cfg_content = _config_content(config_fmt)
369
+ cfg_name, cfg_content = _config_content(config_fmt, include_llm=include_llm)
370
370
  _write(result, project_dir / cfg_name, cfg_content)
371
371
 
372
372
  # ── dependency file ───────────────────────────────────────────────────────
@@ -103,45 +103,95 @@ infrakit
103
103
  """
104
104
 
105
105
 
106
- def _env_config() -> str:
107
- return """\
108
- # Application configuration
109
- # Copy this file to .env and fill in the values.
106
+ def _env_config(include_llm: bool = False) -> str:
107
+ llm_block = """\
108
+
109
+ # LLM
110
+ LLM_KEYS_FILE=keys.json
111
+ OPENAI_API_KEY=
112
+ GEMINI_API_KEY=
113
+ LLM_MODE=async
114
+ LLM_CONCURRENCY=3
115
+ # OPENAI_MODEL=gpt-4o
116
+ # GEMINI_MODEL=gemini-2.0-flash
117
+ # LLM_STATE_DIR=
118
+ # LLM_QUOTA_FILE=
119
+ """ if include_llm else ""
120
+ return f"""\
121
+ # Application
110
122
  APP_ENV=development
111
123
  APP_DEBUG=false
112
124
  APP_SECRET=YOUR_VALUE_HERE
113
- """
114
125
 
115
-
116
- def _yaml_config() -> str:
117
- return """\
126
+ # Logger
127
+ LOG_DIR=logs
128
+ LOG_STRATEGY=date
129
+ LOG_STREAM=stdout
130
+ LOG_FORMAT=human
131
+ LOG_LEVEL=DEBUG
132
+ {llm_block}"""
133
+
134
+
135
+ def _yaml_config(include_llm: bool = False) -> str:
136
+ llm_block = """
137
+
138
+ # LLM
139
+ LLM_KEYS_FILE: keys.json
140
+ OPENAI_API_KEY: ""
141
+ GEMINI_API_KEY: ""
142
+ LLM_MODE: async
143
+ LLM_CONCURRENCY: 3
144
+ # OPENAI_MODEL: gpt-4o
145
+ # GEMINI_MODEL: gemini-2.0-flash
146
+ # LLM_STATE_DIR: ""
147
+ # LLM_QUOTA_FILE: ""
148
+ """ if include_llm else ""
149
+ return f"""\
118
150
  # Application configuration
119
151
  app:
120
152
  env: development
121
153
  debug: false
122
154
  secret: YOUR_VALUE_HERE
123
- """
124
155
 
125
-
126
- def _json_config() -> str:
127
- return """\
128
- {
129
- "app": {
156
+ # Logger (flat keys — read by utils/logger.py via infrakit.config)
157
+ LOG_DIR: logs
158
+ LOG_STRATEGY: date
159
+ LOG_STREAM: stdout
160
+ LOG_FORMAT: human
161
+ LOG_LEVEL: DEBUG
162
+ {llm_block}"""
163
+
164
+
165
+ def _json_config(include_llm: bool = False) -> str:
166
+ llm_keys = """,
167
+ "LLM_KEYS_FILE": "keys.json",
168
+ "OPENAI_API_KEY": "",
169
+ "GEMINI_API_KEY": "",
170
+ "LLM_MODE": "async",
171
+ "LLM_CONCURRENCY": 3""" if include_llm else ""
172
+ return f"""\
173
+ {{
174
+ "app": {{
130
175
  "env": "development",
131
176
  "debug": false,
132
177
  "secret": "YOUR_VALUE_HERE"
133
- }
134
- }
178
+ }},
179
+ "LOG_DIR": "logs",
180
+ "LOG_STRATEGY": "date",
181
+ "LOG_STREAM": "stdout",
182
+ "LOG_FORMAT": "human",
183
+ "LOG_LEVEL": "DEBUG"{llm_keys}
184
+ }}
135
185
  """
136
186
 
137
187
 
138
- def _config_content(fmt: str) -> tuple[str, str]:
188
+ def _config_content(fmt: str, *, include_llm: bool = False) -> tuple[str, str]:
139
189
  """Return (filename, content) for the chosen config format."""
140
190
  if fmt == "yaml":
141
- return "config.yaml", _yaml_config()
191
+ return "config.yaml", _yaml_config(include_llm)
142
192
  if fmt == "json":
143
- return "config.json", _json_config()
144
- return ".env", _env_config() # default
193
+ return "config.json", _json_config(include_llm)
194
+ return ".env", _env_config(include_llm) # default
145
195
 
146
196
 
147
197
  def _logger_util() -> str:
@@ -151,6 +201,9 @@ utils.logger
151
201
  ~~~~~~~~~~~~
152
202
  Thin wrapper that boots the infrakit logger once and exports ``get_logger``.
153
203
 
204
+ Reads LOG_DIR, LOG_STRATEGY, LOG_STREAM, LOG_FORMAT, and LOG_LEVEL from the
205
+ project config file (.env / config.yaml / config.json) via infrakit.config.
206
+
154
207
  Usage
155
208
  -----
156
209
  from utils.logger import get_logger
@@ -159,22 +212,34 @@ Usage
159
212
  log.info("hello")
160
213
  \"\"\"
161
214
 
162
- import os
215
+ from pathlib import Path
216
+ from infrakit.core.config.loader import load, load_env
163
217
  from infrakit.core.logger import setup, get_logger # re-export get_logger
164
218
 
165
219
  _booted = False
166
220
 
167
221
 
222
+ def _load_cfg() -> dict:
223
+ if Path(".env").exists():
224
+ return load_env(".env", cast_values=True)
225
+ if Path("config.yaml").exists():
226
+ return load("config.yaml")
227
+ if Path("config.json").exists():
228
+ return load("config.json")
229
+ return {}
230
+
231
+
168
232
  def _boot() -> None:
169
233
  global _booted
170
234
  if _booted:
171
235
  return
236
+ cfg = _load_cfg()
172
237
  setup(
173
- log_dir=os.getenv("LOG_DIR", "logs"),
174
- strategy=os.getenv("LOG_STRATEGY", "date"),
175
- stream=os.getenv("LOG_STREAM", "stdout"),
176
- fmt=os.getenv("LOG_FORMAT", "human"),
177
- level=os.getenv("LOG_LEVEL", "DEBUG"),
238
+ log_dir=cfg.get("LOG_DIR", "logs"),
239
+ strategy=cfg.get("LOG_STRATEGY", "date"),
240
+ stream=cfg.get("LOG_STREAM", "stdout"),
241
+ fmt=cfg.get("LOG_FORMAT", "human"),
242
+ level=cfg.get("LOG_LEVEL", "DEBUG"),
178
243
  )
179
244
  _booted = True
180
245
 
@@ -318,7 +383,7 @@ def scaffold_basic(
318
383
  _write(result, project_dir / "tests" / "__init__.py", _tests_init())
319
384
 
320
385
  # ── config file ───────────────────────────────────────────────────────────
321
- cfg_name, cfg_content = _config_content(config_fmt)
386
+ cfg_name, cfg_content = _config_content(config_fmt, include_llm=include_llm)
322
387
  _write(result, project_dir / cfg_name, cfg_content)
323
388
 
324
389
  # ── dependency file ───────────────────────────────────────────────────────
@@ -543,7 +543,7 @@ def scaffold_pipeline(
543
543
  _write(result, project_dir / "tests" / "test_pipeline.py", _test_pipeline())
544
544
 
545
545
  # ── config ────────────────────────────────────────────────────────────────
546
- cfg_name, cfg_content = _config_content(config_fmt)
546
+ cfg_name, cfg_content = _config_content(config_fmt, include_llm=include_llm)
547
547
  _write(result, project_dir / cfg_name, cfg_content)
548
548
 
549
549
  # ── dependency file ───────────────────────────────────────────────────────
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-infrakit-dev
3
+ Version: 0.1.3
4
+ Summary: A comprehensive Python developer infrastructure toolkit
5
+ Project-URL: Homepage, https://github.com/chiragg21/infrakit
6
+ Project-URL: Repository, https://github.com/chiragg21/infrakit
7
+ Requires-Python: >=3.13
8
+ Requires-Dist: google-genai>=1.69.0
9
+ Requires-Dist: isort>=8.0.1
10
+ Requires-Dist: openai>=2.30.0
11
+ Requires-Dist: pydantic>=2.12.5
12
+ Requires-Dist: python-dotenv>=1.2.2
13
+ Requires-Dist: pyyaml>=6.0.3
14
+ Requires-Dist: tqdm>=4.67.3
15
+ Requires-Dist: typer>=0.24.1
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Infrakit
19
+
20
+ A modular developer toolkit for Python — project scaffolding, logging, config loading, a multi-provider LLM client, and dependency utilities.
21
+
22
+ ```bash
23
+ pip install infrakit
24
+ ```
25
+
26
+ The CLI is available as `ik`.
27
+
28
+ ---
29
+
30
+ ## Scaffolding
31
+
32
+ Bootstrap a new project in one command:
33
+
34
+ ```bash
35
+ ik init my-project # basic layout
36
+ ik init my-api -t backend # FastAPI service
37
+ ik init my-model -t ai # AI/ML project with pipelines and notebooks
38
+ ik init my-etl -t pipeline # ETL/data pipeline
39
+ ik init my-cli -t cli_tool # distributable CLI app
40
+ ```
41
+
42
+ **All flags:**
43
+
44
+ | Flag | Values | Default | Description |
45
+ |---|---|---|---|
46
+ | `-t` / `--template` | `basic`, `backend`, `ai`, `pipeline`, `cli_tool` | `basic` | Project template |
47
+ | `-v` / `--version` | e.g. `0.1.0` | `0.1.0` | Starting version string |
48
+ | `--description` | string | `""` | Short description added to pyproject.toml and README |
49
+ | `--author` | string | `""` | Author line in pyproject.toml |
50
+ | `--config` | `env`, `yaml`, `json` | `env` | Config file format to generate |
51
+ | `--deps` | `toml`, `requirements` | `toml` | Dependency file style |
52
+ | `--include-llm` | flag | off | Add `utils/llm.py` wired to `infrakit.llm` |
53
+
54
+ Every template generates a config file pre-populated with the variables its utilities need (logger settings, LLM keys, app settings). Re-running over an existing directory skips files already present.
55
+
56
+ ---
57
+
58
+ ## Core — Config & Logger
59
+
60
+ ### Config loader
61
+
62
+ ```python
63
+ from infrakit.core.config.loader import load, load_env
64
+
65
+ cfg = load_env(".env", cast_values=True) # "true" → bool, "42" → int
66
+ cfg = load("config.yaml")
67
+ cfg = load("config.json")
68
+
69
+ port = cfg.get("APP_PORT", 8000)
70
+ ```
71
+
72
+ **`load(path, *, ...)`** — load a config file (JSON, YAML, INI, or `.env`), format inferred from extension.
73
+
74
+ | Parameter | Default | Description |
75
+ |---|---|---|
76
+ | `path` | — | Path to the config file |
77
+ | `env_override` | `False` | Let existing environment variables override file values |
78
+ | `env_file` | `".env"` | `.env` file to merge in alongside the config |
79
+ | `inject_new` | `False` | Add any new keys from the env file into the result |
80
+ | `interpolate` | `True` | Expand `${VAR}` references within values |
81
+ | `cast_values` | `True` | Convert strings to int, float, bool where possible |
82
+
83
+ **`load_env(path, *, cast_values)`** — convenience wrapper to load a `.env` file directly. `cast_values` defaults to `False`.
84
+
85
+ ---
86
+
87
+ ### Logger
88
+
89
+ ```python
90
+ from infrakit.core.logger import setup, get_logger
91
+
92
+ setup(log_dir="logs", strategy="date", stream="stdout", fmt="human", level="INFO")
93
+
94
+ log = get_logger(__name__)
95
+ log.info("started on port %d", 8080)
96
+ ```
97
+
98
+ **`setup(*, ...)`** — configure logging once at startup. All parameters are keyword-only.
99
+
100
+ | Parameter | Default | Description |
101
+ |---|---|---|
102
+ | `level` | `"DEBUG"` | Minimum log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
103
+ | `fmt` | `"human"` | Console format — `"human"` for readable text, `"json"` for structured |
104
+ | `file_fmt` | `"human"` | Format used in log files (same options as `fmt`) |
105
+ | `strategy` | `"date"` | File rotation strategy — `"date"`, `"date_level"`, or `"single"` |
106
+ | `stream` | `"stdout"` | Where to echo logs — `"stdout"`, `"stderr"`, or `None` to disable |
107
+ | `log_dir` | `"logs"` | Directory where log files are written |
108
+ | `session` | `None` | Label this run — adds a session prefix to log filenames |
109
+ | `retention` | `30` | Days to keep old log files before deletion |
110
+ | `max_bytes` | `10MB` | Max size of a single log file before rotation |
111
+ | `force` | `False` | Re-apply setup even if already configured |
112
+
113
+ **`get_logger(name)`** — returns a stdlib `Logger`. Always call as `get_logger(__name__)`. Safe to call before `setup()`.
114
+
115
+ ---
116
+
117
+ ## LLM Client
118
+
119
+ A unified client for **OpenAI** and **Gemini** with key rotation, rate limiting, quota tracking, and async/batch generation.
120
+
121
+ ```python
122
+ from infrakit.llm import LLMClient, Prompt
123
+
124
+ client = LLMClient(
125
+ keys={"openai_keys": ["sk-..."], "gemini_keys": ["AIza..."]},
126
+ storage_dir="./llm_state", # persists key usage across restarts
127
+ )
128
+
129
+ response = client.generate(Prompt(user="What is 2+2?"), provider="openai")
130
+ print(response.content)
131
+ ```
132
+
133
+ **`LLMClient(keys, ...)`**
134
+
135
+ | Parameter | Default | Description |
136
+ |---|---|---|
137
+ | `keys` | — | Dict with `openai_keys` and/or `gemini_keys` lists of API key strings |
138
+ | `storage_dir` | `~/.infrakit/llm` | Directory where key state is persisted across restarts |
139
+ | `quota_file` | `None` | Path to a `quotas.json` file with per-provider/model limits |
140
+ | `mode` | `"async"` | Batch execution mode — `"async"` or `"threaded"` |
141
+ | `max_concurrent` | `3` | Max parallel requests in batch calls |
142
+ | `key_retries` | `3` | How many keys to try before giving up on a request |
143
+ | `schema_retries` | `2` | How many times to retry structured output parsing on schema mismatch |
144
+ | `meta_window` | `200` | Number of recent requests to keep in memory per key |
145
+ | `openai_model` | `"gpt-4o-mini"` | Default OpenAI model |
146
+ | `gemini_model` | `"gemini-2.0-flash"` | Default Gemini model |
147
+ | `show_progress` | `True` | Show a progress bar during batch generation |
148
+
149
+ ---
150
+
151
+ **`generate(prompt, provider, response_model, **kwargs)`** — blocking single call, safe in any context.
152
+
153
+ **`async_generate(prompt, provider, response_model, **kwargs)`** — async version, use inside `async` functions.
154
+
155
+ | Parameter | Default | Description |
156
+ |---|---|---|
157
+ | `prompt` | — | `Prompt(system=..., user=...)` — `system` is optional |
158
+ | `provider` | — | `"openai"` or `"gemini"` |
159
+ | `response_model` | `None` | Pydantic `BaseModel` subclass for structured output parsing |
160
+
161
+ ---
162
+
163
+ **`batch_generate(prompts, provider, ...)`** — run many prompts; results match input order.
164
+
165
+ **`async_batch_generate(prompts, provider, ...)`** — async version for use inside `async` functions.
166
+
167
+ | Parameter | Default | Description |
168
+ |---|---|---|
169
+ | `prompts` | — | List of `Prompt` objects |
170
+ | `provider` | — | `"openai"` or `"gemini"` |
171
+ | `response_model` | `None` | Pydantic model for structured output on every item |
172
+ | `max_concurrent` | client default | Override the client-level concurrency limit for this batch |
173
+ | `show_progress` | client default | Override the client-level progress bar setting |
174
+
175
+ ```python
176
+ batch = client.batch_generate(prompts, provider="openai")
177
+ print(batch.success_count, batch.failure_count, batch.total_tokens)
178
+ for r in batch.results:
179
+ print(r.content if not r.error else r.error)
180
+ ```
181
+
182
+ ---
183
+
184
+ **Structured output:**
185
+
186
+ ```python
187
+ from pydantic import BaseModel
188
+
189
+ class Summary(BaseModel):
190
+ title: str
191
+ bullets: list[str]
192
+
193
+ response = client.generate(Prompt(user="Summarise: ..."), provider="openai", response_model=Summary)
194
+ if response.schema_matched:
195
+ print(response.parsed.bullets) # typed Summary instance
196
+ ```
197
+
198
+ ---
199
+
200
+ **`set_quota(provider, key_id, quota)`** — set limits for a specific key.
201
+
202
+ ```python
203
+ from infrakit.llm import QuotaConfig
204
+
205
+ # key-level RPM (applies to all models on this key)
206
+ client.set_quota(provider="openai", key_id="sk-key1", quota=QuotaConfig(rpm_limit=60))
207
+
208
+ # per-model daily token limit
209
+ client.set_quota(provider="gemini", key_id="AIza-key1", quota=QuotaConfig(model="gemini-2.5-pro", daily_token_limit=250_000))
210
+ ```
211
+
212
+ `QuotaConfig` fields: `model` (None = all models on the key), `rpm_limit`, `daily_token_limit`, `tpm_limit`.
213
+
214
+ Quota defaults can also be set in a `quotas.json` file — pass the path via `quota_file=` in the constructor.
215
+
216
+ ---
217
+
218
+ **`status(provider, key_id)`** — return key status as a list of dicts. **`print_status(provider, key_id)`** — same but pretty-printed to stdout.
219
+
220
+ | Parameter | Default | Description |
221
+ |---|---|---|
222
+ | `provider` | `None` | Filter to `"openai"` or `"gemini"`; `None` returns all |
223
+ | `key_id` | `None` | Filter to a specific key (first 8 chars); `None` returns all |
224
+
225
+ ```python
226
+ rows = client.status(provider="openai")
227
+ # each row: provider, key_id, status, rpm_limit, current_rpm, models
228
+ ```
229
+
230
+ **CLI:**
231
+
232
+ ```bash
233
+ ik llm status --storage-dir ./llm_state
234
+ ik llm quota set --provider openai --key sk-abc --rpm 60 --storage-dir ./llm_state
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Other Features
240
+
241
+ ### Dependency management (`infrakit.deps`)
242
+
243
+ ```bash
244
+ ik deps scan . # list packages your code actually imports
245
+ ik deps export . --format toml # sync pyproject.toml / requirements.txt
246
+ ik deps check --packages numpy # outdated, security, and license checks
247
+ ik deps clean . --dry-run # find unused installed packages
248
+ ik deps optimise . # sort and clean imports (isort)
249
+ ```
250
+
251
+ **`scan(root, include_notebooks, use_gitignore)`**
252
+
253
+ | Parameter | Default | Description |
254
+ |---|---|---|
255
+ | `root` | — | Project root to scan |
256
+ | `include_notebooks` | `False` | Also scan `.ipynb` notebook files |
257
+ | `use_gitignore` | `True` | Skip paths matched by `.gitignore` |
258
+
259
+ **`export(root, output, inplace, keep_versions, include_notebooks, use_gitignore)`**
260
+
261
+ | Parameter | Default | Description |
262
+ |---|---|---|
263
+ | `root` | — | Project root to scan |
264
+ | `output` | `None` | Write to this path instead of the detected dependency file |
265
+ | `inplace` | `False` | Update the existing file in place |
266
+ | `keep_versions` | `True` | Preserve pinned version specifiers already in the file |
267
+ | `include_notebooks` | `False` | Also scan `.ipynb` files |
268
+ | `use_gitignore` | `True` | Skip gitignored paths |
269
+
270
+ **`check(root, packages, outdated, security, licenses)`**
271
+
272
+ | Parameter | Default | Description |
273
+ |---|---|---|
274
+ | `root` | `None` | Auto-scan this root for packages (used if `packages` is `None`) |
275
+ | `packages` | `None` | Explicit list of package names to check |
276
+ | `outdated` | `True` | Check for newer versions on PyPI |
277
+ | `security` | `True` | Run vulnerability checks |
278
+ | `licenses` | `True` | Check license compatibility |
279
+
280
+ Returns a `HealthReport` with `.outdated`, `.vulnerable`, `.licenses`, and `.errors` lists.
281
+
282
+ **`clean(root, protected, dry_run)`**
283
+
284
+ | Parameter | Default | Description |
285
+ |---|---|---|
286
+ | `root` | — | Project root (used to determine what is actually imported) |
287
+ | `protected` | `None` | Set of package names to never remove |
288
+ | `dry_run` | `True` | Preview only — pass `False` to actually uninstall |
289
+
290
+ Returns a `CleanResult` with `.to_remove`, `.removed`, `.skipped`, and `.errors` lists.
291
+
292
+ **`optimise(root, files, convert_to, use_isort, dry_run)`**
293
+
294
+ | Parameter | Default | Description |
295
+ |---|---|---|
296
+ | `root` | — | Project root |
297
+ | `files` | `None` | Specific files to process; `None` processes all `.py` files |
298
+ | `convert_to` | `None` | Rewrite import style (e.g. `"absolute"`) |
299
+ | `use_isort` | `True` | Run `isort` on each file |
300
+ | `dry_run` | `False` | Preview changes without writing files |
301
+
302
+ ---
303
+
304
+ ### Profiling (`infrakit.time`)
305
+
306
+ ```bash
307
+ ik time run script.py
308
+ ```
309
+
310
+ ```python
311
+ from infrakit.time import pipeline_profiler, track
312
+
313
+ @pipeline_profiler("My Pipeline")
314
+ def main(): ...
315
+
316
+ @track(name="Load Step")
317
+ def load_data(): ...
318
+ ```
@@ -38,15 +38,15 @@ infrakit/llm/providers/base.py,sha256=SglgqSIGm_jHsxj7gXvha4CsKb0L9Q8TXBXcUaVXK5
38
38
  infrakit/llm/providers/gemini.py,sha256=rVI5seLpmb_R1AqcP9B6Hp9WvyTmWeDdCVttyCH05mI,5552
39
39
  infrakit/llm/providers/openai.py,sha256=qAZ1iXRbEilvqOvYXkUh3rS-PhQVp_fQFXiUNk-vers,5469
40
40
  infrakit/scaffolder/__init__.py,sha256=VBsYgmzomi4abMF0G7xtto3vecKqKBVgKtguugtmzkQ,1132
41
- infrakit/scaffolder/ai.py,sha256=rHCISnrTooe1B2rquOagfYkBfOMyqGYwW6SDYq10xxY,15602
42
- infrakit/scaffolder/backend.py,sha256=cq7Vj8fsbOAsPSyt1S0ApSZkazbR_rwOxszOYeSyzBA,16224
43
- infrakit/scaffolder/cli_tool.py,sha256=jFjAZj7bbozWWPZluMtxt9VJTr95mQbFPHcwrAYSHSU,12031
44
- infrakit/scaffolder/generator.py,sha256=9FF3ju8JS_Z4F2CxRKZDIE7DM9GGnnzPcFHBJ02zELM,9829
45
- infrakit/scaffolder/pipeline.py,sha256=9Mq8biVHj9ywOQWSwK42H4SO-2iq8KlcZGk6UOXhA1Q,17081
41
+ infrakit/scaffolder/ai.py,sha256=otj0YjPApBfLwXTc1EwA-t-ePTefe6bhIKskk4TcZTk,16679
42
+ infrakit/scaffolder/backend.py,sha256=Ty_uEPzT-qMi2c5Kb8HM32dQC9kTRhgJHYycTSvrJ08,18725
43
+ infrakit/scaffolder/cli_tool.py,sha256=QOgqtTqi-OASowIh8ezQWFPMZLMcamz4gt7tK7i9qeg,12056
44
+ infrakit/scaffolder/generator.py,sha256=C8JgSAHS5qN0pO1-tx3tVdZBBeK6nz5Jo7GOQgDnCe4,11558
45
+ infrakit/scaffolder/pipeline.py,sha256=HeBkreiso0fYIHrObkaPKRB3XewU3UgQevrrcWHY_wI,17106
46
46
  infrakit/scaffolder/registry.py,sha256=TqZetmDChmXD_oW6kl10A0SbN6ADm4xi7NdK2m9GNGI,3802
47
47
  infrakit/time/__init__.py,sha256=R-o_F3XkqaC-QWtfMJ8TCUKnCqLycFl2WQHs20e8ElI,1206
48
48
  infrakit/time/profiler.py,sha256=bfM-UCFULr1cA4Y6fgBWdQ7d4tNgdY_YJowohhVH-So,16075
49
- python_infrakit_dev-0.1.2.dist-info/METADATA,sha256=cddNHaIIySzMjPeMcLrr0p1ErZFnwvfLTsyelufjyaw,4481
50
- python_infrakit_dev-0.1.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
51
- python_infrakit_dev-0.1.2.dist-info/entry_points.txt,sha256=9uBSvx5accKf026gAQijwUkkN_eL37RBWlCO3e_9Iz4,80
52
- python_infrakit_dev-0.1.2.dist-info/RECORD,,
49
+ python_infrakit_dev-0.1.3.dist-info/METADATA,sha256=lhrd5mPqOI-5LQzDyWX9k_AG3Yzei066ynS47O_M5Bc,11484
50
+ python_infrakit_dev-0.1.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
51
+ python_infrakit_dev-0.1.3.dist-info/entry_points.txt,sha256=9uBSvx5accKf026gAQijwUkkN_eL37RBWlCO3e_9Iz4,80
52
+ python_infrakit_dev-0.1.3.dist-info/RECORD,,
@@ -1,124 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: python-infrakit-dev
3
- Version: 0.1.2
4
- Summary: A comprehensive Python developer infrastructure toolkit
5
- Project-URL: Homepage, https://github.com/chiragg21/infrakit
6
- Project-URL: Repository, https://github.com/chiragg21/infrakit
7
- Requires-Python: >=3.13
8
- Requires-Dist: google-genai>=1.69.0
9
- Requires-Dist: isort>=8.0.1
10
- Requires-Dist: openai>=2.30.0
11
- Requires-Dist: pydantic>=2.12.5
12
- Requires-Dist: python-dotenv>=1.2.2
13
- Requires-Dist: pyyaml>=6.0.3
14
- Requires-Dist: tqdm>=4.67.3
15
- Requires-Dist: typer>=0.24.1
16
- Description-Content-Type: text/markdown
17
-
18
- # Infrakit
19
-
20
- Infrakit is a comprehensive, modular developer infrastructure toolkit for Python projects. It provides CLI utilities, rich project scaffolding, a robust multi-provider LLM client, dependency management, profiling, and core utilities for logging and configuration.
21
-
22
- ## Installation
23
-
24
- Install via pip:
25
-
26
- ```bash
27
- pip install infrakit
28
- # or if using uv
29
- uv pip install infrakit
30
- ```
31
-
32
- The CLI is exposed as `infrakit` and conveniently aliased as `ik`.
33
-
34
- ## Key Features
35
-
36
- ### 1. Project Scaffolding (`ik init`)
37
-
38
- Quickly bootstrap new Python projects with standardized layouts, ready-to-use boilerplate, and optional LLM integration. Existing files are safely skipped if you re-run over a directory.
39
-
40
- ```bash
41
- ik init my-project -t basic
42
- ik init my-fastapi-app -t backend -v 0.1.0
43
- ik init my-ai-tool -t ai --include-llm
44
- ```
45
-
46
- **Available Templates**:
47
- - **`basic`**: Minimal template (src, utils, tests).
48
- - **`backend`**: FastAPI service (app, routes, middleware, Dockerfile, docker-compose).
49
- - **`cli_tool`**: Distributable CLI application using Typer.
50
- - **`pipeline`**: Data pipeline / ETL template (extract, transform, load, enrich).
51
- - **`ai`**: AI/ML project optimized for notebooks and pipelines.
52
-
53
- ### 2. Multi-provider LLM Client (`infrakit.llm`)
54
-
55
- A unified LLM client interface supporting **OpenAI** and **Gemini**. Built natively for robust production use cases, particularly when navigating free-tier API quotas.
56
-
57
- **Features:**
58
- - Seamless key rotation and persistent storage tracking.
59
- - Local rate limiting (RPM/TPM gates).
60
- - Async and multi-threaded batch generation.
61
- - Structured output parsing and schema validation using `pydantic.BaseModel`.
62
-
63
- **Quick Start:**
64
- ```python
65
- from infrakit.llm import LLMClient, Prompt
66
-
67
- client = LLMClient(keys={"openai_keys": ["sk-..."], "gemini_keys": ["AIza..."]}, storage_dir="./logs")
68
- response = client.generate(Prompt(user="What is the capital of France?"), provider="openai")
69
- print(response.content)
70
- ```
71
-
72
- **LLM CLI tools:**
73
- Monitor and control limits right from the CLI.
74
- - `ik llm status --storage-dir ./logs`
75
- - `ik llm quota set --provider openai --key sk-abc --rpm 60 --storage-dir ./logs`
76
-
77
- ### 3. Dependency Management (`infrakit.deps`)
78
-
79
- In-depth Python dependency tools available under `ik deps` (or programmatically via `infrakit.deps`) to clean, health-check, scan, and optimize dependencies.
80
-
81
- **Features:**
82
- - `scan(root)`: Scan your project for actual Python dependencies used in code.
83
- - `export()`: Export used dependencies to update `requirements.txt` or `pyproject.toml` automatically.
84
- - `check(packages)`: Run health, security, and outdated checks on packages.
85
- - `clean()`: Find and remove unused packages from the virtual environment.
86
- - `optimise()`: Optimize and format imports across the project (integrates with `isort`).
87
-
88
- ### 4. Code Profiling (`infrakit.time`)
89
-
90
- Lightweight timing and execution profiling for your Python projects.
91
-
92
- **CLI Usage:**
93
- Profile any script instantly to detect performance bottlenecks.
94
- ```bash
95
- ik time run script.py --max-functions 30 --min-time 1.0
96
- ```
97
-
98
- **Decorator Usage:**
99
- Track specific pipeline steps inside your code:
100
- ```python
101
- from infrakit.time import pipeline_profiler, track
102
-
103
- @pipeline_profiler("Data Processing Pipeline")
104
- def main():
105
- load_data()
106
- transform_data()
107
-
108
- @track(name="Load Step")
109
- def load_data(): pass
110
- ```
111
-
112
- ### 5. Core Utilities (`infrakit.core`)
113
-
114
- Convenient implementations for common application needs.
115
-
116
- - **Config Loader** (`infrakit.core.config.loader`): Multi-format configuration loader supporting JSON, YAML, INI, and `.env`. Automatically resolves variables using `python-dotenv` and converts types properly natively.
117
- - **Logger** (`infrakit.core.logger`): Unified logging setups for consistent output across applications.
118
- ```python
119
- from infrakit.core.logger import setup, get_logger
120
-
121
- setup(level="INFO", strategy="date_level", stream="stdout")
122
- log = get_logger(__name__)
123
- log.info("Ready")
124
- ```