python-infrakit-dev 0.1.2__tar.gz → 0.1.3__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.
- python_infrakit_dev-0.1.3/.claude/settings.local.json +7 -0
- python_infrakit_dev-0.1.3/PKG-INFO +318 -0
- python_infrakit_dev-0.1.3/README.md +301 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/ai.py +37 -16
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/backend.py +106 -2
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/cli_tool.py +1 -1
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/generator.py +92 -27
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/pipeline.py +1 -1
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/pyproject.toml +1 -1
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/test_llm.py +262 -36
- python_infrakit_dev-0.1.3/tests/test_scaffolders.py +495 -0
- python_infrakit_dev-0.1.2/PKG-INFO +0 -124
- python_infrakit_dev-0.1.2/README.md +0 -107
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/.gitignore +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/.python-version +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/config.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/deps.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/init.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/llm.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/logger.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/module.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/commands/time.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/cli/main.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/config/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/config/converter.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/config/exporter.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/config/loader.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/config/validator.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/logger/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/logger/formatters.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/logger/handlers.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/logger/retention.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/core/logger/setup.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/deps/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/deps/clean.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/deps/depfile.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/deps/health.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/deps/optimizer.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/deps/scanner.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/batch.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/client.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/key_manager.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/llm_readme.md +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/models.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/providers/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/providers/base.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/providers/gemini.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/providers/openai.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/llm/rate_limiter.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/scaffolder/registry.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/time/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/infrakit/time/profiler.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/cli/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/cli/conftest.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/cli/test_config.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/cli/test_init.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/cli/test_logger.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/cli/test_module.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/config/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/config/test_converter.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/config/test_exporter.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/config/test_loader.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/config/test_validator.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/logger/__init__.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/logger/test_formatters.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/logger/test_handler.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/logger/test_retention.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/core/logger/test_setup.py +0 -0
- {python_infrakit_dev-0.1.2 → python_infrakit_dev-0.1.3}/tests/test_time.py +0 -0
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Infrakit
|
|
2
|
+
|
|
3
|
+
A modular developer toolkit for Python — project scaffolding, logging, config loading, a multi-provider LLM client, and dependency utilities.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install infrakit
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The CLI is available as `ik`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Scaffolding
|
|
14
|
+
|
|
15
|
+
Bootstrap a new project in one command:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
ik init my-project # basic layout
|
|
19
|
+
ik init my-api -t backend # FastAPI service
|
|
20
|
+
ik init my-model -t ai # AI/ML project with pipelines and notebooks
|
|
21
|
+
ik init my-etl -t pipeline # ETL/data pipeline
|
|
22
|
+
ik init my-cli -t cli_tool # distributable CLI app
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**All flags:**
|
|
26
|
+
|
|
27
|
+
| Flag | Values | Default | Description |
|
|
28
|
+
|---|---|---|---|
|
|
29
|
+
| `-t` / `--template` | `basic`, `backend`, `ai`, `pipeline`, `cli_tool` | `basic` | Project template |
|
|
30
|
+
| `-v` / `--version` | e.g. `0.1.0` | `0.1.0` | Starting version string |
|
|
31
|
+
| `--description` | string | `""` | Short description added to pyproject.toml and README |
|
|
32
|
+
| `--author` | string | `""` | Author line in pyproject.toml |
|
|
33
|
+
| `--config` | `env`, `yaml`, `json` | `env` | Config file format to generate |
|
|
34
|
+
| `--deps` | `toml`, `requirements` | `toml` | Dependency file style |
|
|
35
|
+
| `--include-llm` | flag | off | Add `utils/llm.py` wired to `infrakit.llm` |
|
|
36
|
+
|
|
37
|
+
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.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Core — Config & Logger
|
|
42
|
+
|
|
43
|
+
### Config loader
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from infrakit.core.config.loader import load, load_env
|
|
47
|
+
|
|
48
|
+
cfg = load_env(".env", cast_values=True) # "true" → bool, "42" → int
|
|
49
|
+
cfg = load("config.yaml")
|
|
50
|
+
cfg = load("config.json")
|
|
51
|
+
|
|
52
|
+
port = cfg.get("APP_PORT", 8000)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**`load(path, *, ...)`** — load a config file (JSON, YAML, INI, or `.env`), format inferred from extension.
|
|
56
|
+
|
|
57
|
+
| Parameter | Default | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `path` | — | Path to the config file |
|
|
60
|
+
| `env_override` | `False` | Let existing environment variables override file values |
|
|
61
|
+
| `env_file` | `".env"` | `.env` file to merge in alongside the config |
|
|
62
|
+
| `inject_new` | `False` | Add any new keys from the env file into the result |
|
|
63
|
+
| `interpolate` | `True` | Expand `${VAR}` references within values |
|
|
64
|
+
| `cast_values` | `True` | Convert strings to int, float, bool where possible |
|
|
65
|
+
|
|
66
|
+
**`load_env(path, *, cast_values)`** — convenience wrapper to load a `.env` file directly. `cast_values` defaults to `False`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### Logger
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from infrakit.core.logger import setup, get_logger
|
|
74
|
+
|
|
75
|
+
setup(log_dir="logs", strategy="date", stream="stdout", fmt="human", level="INFO")
|
|
76
|
+
|
|
77
|
+
log = get_logger(__name__)
|
|
78
|
+
log.info("started on port %d", 8080)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**`setup(*, ...)`** — configure logging once at startup. All parameters are keyword-only.
|
|
82
|
+
|
|
83
|
+
| Parameter | Default | Description |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `level` | `"DEBUG"` | Minimum log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
|
|
86
|
+
| `fmt` | `"human"` | Console format — `"human"` for readable text, `"json"` for structured |
|
|
87
|
+
| `file_fmt` | `"human"` | Format used in log files (same options as `fmt`) |
|
|
88
|
+
| `strategy` | `"date"` | File rotation strategy — `"date"`, `"date_level"`, or `"single"` |
|
|
89
|
+
| `stream` | `"stdout"` | Where to echo logs — `"stdout"`, `"stderr"`, or `None` to disable |
|
|
90
|
+
| `log_dir` | `"logs"` | Directory where log files are written |
|
|
91
|
+
| `session` | `None` | Label this run — adds a session prefix to log filenames |
|
|
92
|
+
| `retention` | `30` | Days to keep old log files before deletion |
|
|
93
|
+
| `max_bytes` | `10MB` | Max size of a single log file before rotation |
|
|
94
|
+
| `force` | `False` | Re-apply setup even if already configured |
|
|
95
|
+
|
|
96
|
+
**`get_logger(name)`** — returns a stdlib `Logger`. Always call as `get_logger(__name__)`. Safe to call before `setup()`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## LLM Client
|
|
101
|
+
|
|
102
|
+
A unified client for **OpenAI** and **Gemini** with key rotation, rate limiting, quota tracking, and async/batch generation.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from infrakit.llm import LLMClient, Prompt
|
|
106
|
+
|
|
107
|
+
client = LLMClient(
|
|
108
|
+
keys={"openai_keys": ["sk-..."], "gemini_keys": ["AIza..."]},
|
|
109
|
+
storage_dir="./llm_state", # persists key usage across restarts
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
response = client.generate(Prompt(user="What is 2+2?"), provider="openai")
|
|
113
|
+
print(response.content)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**`LLMClient(keys, ...)`**
|
|
117
|
+
|
|
118
|
+
| Parameter | Default | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `keys` | — | Dict with `openai_keys` and/or `gemini_keys` lists of API key strings |
|
|
121
|
+
| `storage_dir` | `~/.infrakit/llm` | Directory where key state is persisted across restarts |
|
|
122
|
+
| `quota_file` | `None` | Path to a `quotas.json` file with per-provider/model limits |
|
|
123
|
+
| `mode` | `"async"` | Batch execution mode — `"async"` or `"threaded"` |
|
|
124
|
+
| `max_concurrent` | `3` | Max parallel requests in batch calls |
|
|
125
|
+
| `key_retries` | `3` | How many keys to try before giving up on a request |
|
|
126
|
+
| `schema_retries` | `2` | How many times to retry structured output parsing on schema mismatch |
|
|
127
|
+
| `meta_window` | `200` | Number of recent requests to keep in memory per key |
|
|
128
|
+
| `openai_model` | `"gpt-4o-mini"` | Default OpenAI model |
|
|
129
|
+
| `gemini_model` | `"gemini-2.0-flash"` | Default Gemini model |
|
|
130
|
+
| `show_progress` | `True` | Show a progress bar during batch generation |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
**`generate(prompt, provider, response_model, **kwargs)`** — blocking single call, safe in any context.
|
|
135
|
+
|
|
136
|
+
**`async_generate(prompt, provider, response_model, **kwargs)`** — async version, use inside `async` functions.
|
|
137
|
+
|
|
138
|
+
| Parameter | Default | Description |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `prompt` | — | `Prompt(system=..., user=...)` — `system` is optional |
|
|
141
|
+
| `provider` | — | `"openai"` or `"gemini"` |
|
|
142
|
+
| `response_model` | `None` | Pydantic `BaseModel` subclass for structured output parsing |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
**`batch_generate(prompts, provider, ...)`** — run many prompts; results match input order.
|
|
147
|
+
|
|
148
|
+
**`async_batch_generate(prompts, provider, ...)`** — async version for use inside `async` functions.
|
|
149
|
+
|
|
150
|
+
| Parameter | Default | Description |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `prompts` | — | List of `Prompt` objects |
|
|
153
|
+
| `provider` | — | `"openai"` or `"gemini"` |
|
|
154
|
+
| `response_model` | `None` | Pydantic model for structured output on every item |
|
|
155
|
+
| `max_concurrent` | client default | Override the client-level concurrency limit for this batch |
|
|
156
|
+
| `show_progress` | client default | Override the client-level progress bar setting |
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
batch = client.batch_generate(prompts, provider="openai")
|
|
160
|
+
print(batch.success_count, batch.failure_count, batch.total_tokens)
|
|
161
|
+
for r in batch.results:
|
|
162
|
+
print(r.content if not r.error else r.error)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
**Structured output:**
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from pydantic import BaseModel
|
|
171
|
+
|
|
172
|
+
class Summary(BaseModel):
|
|
173
|
+
title: str
|
|
174
|
+
bullets: list[str]
|
|
175
|
+
|
|
176
|
+
response = client.generate(Prompt(user="Summarise: ..."), provider="openai", response_model=Summary)
|
|
177
|
+
if response.schema_matched:
|
|
178
|
+
print(response.parsed.bullets) # typed Summary instance
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
**`set_quota(provider, key_id, quota)`** — set limits for a specific key.
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from infrakit.llm import QuotaConfig
|
|
187
|
+
|
|
188
|
+
# key-level RPM (applies to all models on this key)
|
|
189
|
+
client.set_quota(provider="openai", key_id="sk-key1", quota=QuotaConfig(rpm_limit=60))
|
|
190
|
+
|
|
191
|
+
# per-model daily token limit
|
|
192
|
+
client.set_quota(provider="gemini", key_id="AIza-key1", quota=QuotaConfig(model="gemini-2.5-pro", daily_token_limit=250_000))
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`QuotaConfig` fields: `model` (None = all models on the key), `rpm_limit`, `daily_token_limit`, `tpm_limit`.
|
|
196
|
+
|
|
197
|
+
Quota defaults can also be set in a `quotas.json` file — pass the path via `quota_file=` in the constructor.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
**`status(provider, key_id)`** — return key status as a list of dicts. **`print_status(provider, key_id)`** — same but pretty-printed to stdout.
|
|
202
|
+
|
|
203
|
+
| Parameter | Default | Description |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `provider` | `None` | Filter to `"openai"` or `"gemini"`; `None` returns all |
|
|
206
|
+
| `key_id` | `None` | Filter to a specific key (first 8 chars); `None` returns all |
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
rows = client.status(provider="openai")
|
|
210
|
+
# each row: provider, key_id, status, rpm_limit, current_rpm, models
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**CLI:**
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
ik llm status --storage-dir ./llm_state
|
|
217
|
+
ik llm quota set --provider openai --key sk-abc --rpm 60 --storage-dir ./llm_state
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Other Features
|
|
223
|
+
|
|
224
|
+
### Dependency management (`infrakit.deps`)
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
ik deps scan . # list packages your code actually imports
|
|
228
|
+
ik deps export . --format toml # sync pyproject.toml / requirements.txt
|
|
229
|
+
ik deps check --packages numpy # outdated, security, and license checks
|
|
230
|
+
ik deps clean . --dry-run # find unused installed packages
|
|
231
|
+
ik deps optimise . # sort and clean imports (isort)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**`scan(root, include_notebooks, use_gitignore)`**
|
|
235
|
+
|
|
236
|
+
| Parameter | Default | Description |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| `root` | — | Project root to scan |
|
|
239
|
+
| `include_notebooks` | `False` | Also scan `.ipynb` notebook files |
|
|
240
|
+
| `use_gitignore` | `True` | Skip paths matched by `.gitignore` |
|
|
241
|
+
|
|
242
|
+
**`export(root, output, inplace, keep_versions, include_notebooks, use_gitignore)`**
|
|
243
|
+
|
|
244
|
+
| Parameter | Default | Description |
|
|
245
|
+
|---|---|---|
|
|
246
|
+
| `root` | — | Project root to scan |
|
|
247
|
+
| `output` | `None` | Write to this path instead of the detected dependency file |
|
|
248
|
+
| `inplace` | `False` | Update the existing file in place |
|
|
249
|
+
| `keep_versions` | `True` | Preserve pinned version specifiers already in the file |
|
|
250
|
+
| `include_notebooks` | `False` | Also scan `.ipynb` files |
|
|
251
|
+
| `use_gitignore` | `True` | Skip gitignored paths |
|
|
252
|
+
|
|
253
|
+
**`check(root, packages, outdated, security, licenses)`**
|
|
254
|
+
|
|
255
|
+
| Parameter | Default | Description |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `root` | `None` | Auto-scan this root for packages (used if `packages` is `None`) |
|
|
258
|
+
| `packages` | `None` | Explicit list of package names to check |
|
|
259
|
+
| `outdated` | `True` | Check for newer versions on PyPI |
|
|
260
|
+
| `security` | `True` | Run vulnerability checks |
|
|
261
|
+
| `licenses` | `True` | Check license compatibility |
|
|
262
|
+
|
|
263
|
+
Returns a `HealthReport` with `.outdated`, `.vulnerable`, `.licenses`, and `.errors` lists.
|
|
264
|
+
|
|
265
|
+
**`clean(root, protected, dry_run)`**
|
|
266
|
+
|
|
267
|
+
| Parameter | Default | Description |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| `root` | — | Project root (used to determine what is actually imported) |
|
|
270
|
+
| `protected` | `None` | Set of package names to never remove |
|
|
271
|
+
| `dry_run` | `True` | Preview only — pass `False` to actually uninstall |
|
|
272
|
+
|
|
273
|
+
Returns a `CleanResult` with `.to_remove`, `.removed`, `.skipped`, and `.errors` lists.
|
|
274
|
+
|
|
275
|
+
**`optimise(root, files, convert_to, use_isort, dry_run)`**
|
|
276
|
+
|
|
277
|
+
| Parameter | Default | Description |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| `root` | — | Project root |
|
|
280
|
+
| `files` | `None` | Specific files to process; `None` processes all `.py` files |
|
|
281
|
+
| `convert_to` | `None` | Rewrite import style (e.g. `"absolute"`) |
|
|
282
|
+
| `use_isort` | `True` | Run `isort` on each file |
|
|
283
|
+
| `dry_run` | `False` | Preview changes without writing files |
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Profiling (`infrakit.time`)
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
ik time run script.py
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from infrakit.time import pipeline_profiler, track
|
|
295
|
+
|
|
296
|
+
@pipeline_profiler("My Pipeline")
|
|
297
|
+
def main(): ...
|
|
298
|
+
|
|
299
|
+
@track(name="Load Step")
|
|
300
|
+
def load_data(): ...
|
|
301
|
+
```
|