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,342 @@
1
+ """
2
+ infrakit.cli.commands.module
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
+ ``infrakit module`` subcommand group.
5
+
6
+ Commands
7
+ --------
8
+ infrakit module create <path> [--no-init] [--no-init-parents]
9
+ infrakit module delete <path> [--no-confirm]
10
+ infrakit module tree [root] [--show-ignored]
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import fnmatch
16
+ import shutil
17
+ from pathlib import Path
18
+ from typing import Optional
19
+
20
+ import typer
21
+
22
+ module_app = typer.Typer(
23
+ name="module",
24
+ help="Create, delete, or inspect module directories.",
25
+ no_args_is_help=True,
26
+ )
27
+
28
+ # ── helpers ───────────────────────────────────────────────────────────────────
29
+
30
+ def _abort(msg: str) -> None:
31
+ typer.echo(typer.style(f"✗ {msg}", fg=typer.colors.RED), err=True)
32
+ raise typer.Exit(1)
33
+
34
+
35
+ def _created(rel: Path) -> None:
36
+ typer.echo(f" {typer.style('+', fg=typer.colors.GREEN, bold=True)} {rel}")
37
+
38
+
39
+ def _skipped(rel: Path, reason: str = "already exists") -> None:
40
+ typer.echo(
41
+ f" {typer.style('~', fg=typer.colors.BRIGHT_BLACK)} "
42
+ f"{typer.style(str(rel), fg=typer.colors.BRIGHT_BLACK)}"
43
+ f" {typer.style(f'({reason})', fg=typer.colors.BRIGHT_BLACK)}"
44
+ )
45
+
46
+
47
+ def _write_init(path: Path, cwd: Path) -> None:
48
+ rel = path.relative_to(cwd)
49
+ if path.exists():
50
+ _skipped(rel)
51
+ return
52
+ path.write_text('"""{}"""\n'.format(path.parent.name), encoding="utf-8")
53
+ _created(rel)
54
+
55
+
56
+ def _make_dir(path: Path, cwd: Path) -> None:
57
+ rel = path.relative_to(cwd)
58
+ if path.exists():
59
+ _skipped(rel)
60
+ return
61
+ path.mkdir(parents=True, exist_ok=True)
62
+ _created(rel)
63
+
64
+
65
+ # ── gitignore parser ──────────────────────────────────────────────────────────
66
+
67
+ def _load_gitignore_patterns(root: Path) -> list[str]:
68
+ """
69
+ Read .gitignore from root and return a list of raw patterns.
70
+ Always includes .git itself regardless of .gitignore contents.
71
+ """
72
+ patterns: list[str] = [".git"]
73
+ gitignore = root / ".gitignore"
74
+ if not gitignore.exists():
75
+ return patterns
76
+ for line in gitignore.read_text(encoding="utf-8").splitlines():
77
+ line = line.strip()
78
+ if line and not line.startswith("#"):
79
+ patterns.append(line)
80
+ return patterns
81
+
82
+
83
+ def _is_ignored(path: Path, root: Path, patterns: list[str]) -> bool:
84
+ """
85
+ Return True if ``path`` matches any gitignore pattern.
86
+
87
+ Handles:
88
+ - globstar prefix **/<name>/ — match bare name at any depth
89
+ - bare patterns __pycache__ — match any path component
90
+ - anchored patterns src/*.py — match against full relative path
91
+ - trailing slashes stripped (directory-only markers)
92
+ """
93
+ try:
94
+ rel = path.relative_to(root).as_posix()
95
+ except ValueError:
96
+ rel = path.name
97
+
98
+ rel_parts = Path(rel).parts
99
+
100
+ for pattern in patterns:
101
+ # ── globstar: **/<name> or **/<name>/ → bare name at any depth ───────
102
+ if pattern.startswith("**/"):
103
+ bare = pattern[3:].rstrip("/")
104
+ if any(fnmatch.fnmatch(part, bare) for part in rel_parts):
105
+ return True
106
+ continue
107
+
108
+ p = pattern.rstrip("/")
109
+
110
+ # ── bare name (no slash) → match against every path component ────────
111
+ if "/" not in p:
112
+ if any(fnmatch.fnmatch(part, p) for part in rel_parts):
113
+ return True
114
+ else:
115
+ # ── anchored pattern (contains slash) → match full relative path ─
116
+ if fnmatch.fnmatch(rel, p):
117
+ return True
118
+
119
+ return False
120
+
121
+
122
+ # ── tree renderer ─────────────────────────────────────────────────────────────
123
+
124
+ _PIPE = "│ "
125
+ _TEE = "├── "
126
+ _LAST = "└── "
127
+ _BLANK = " "
128
+
129
+
130
+ def _render_tree(
131
+ path: Path,
132
+ root: Path,
133
+ patterns: list[str],
134
+ show_ignored: bool,
135
+ prefix: str = "",
136
+ ) -> tuple[int, int]:
137
+ """
138
+ Recursively render the tree under ``path``.
139
+ Returns (dir_count, file_count).
140
+ """
141
+ dirs = 0
142
+ files = 0
143
+
144
+ try:
145
+ entries = sorted(path.iterdir(), key=lambda p: (p.is_file(), p.name.lower()))
146
+ except PermissionError:
147
+ return dirs, files
148
+
149
+ # filter ignored unless show_ignored
150
+ if not show_ignored:
151
+ entries = [e for e in entries if not _is_ignored(e, root, patterns)]
152
+
153
+ for i, entry in enumerate(entries):
154
+ is_last = i == len(entries) - 1
155
+ connector = _LAST if is_last else _TEE
156
+ extension = _BLANK if is_last else _PIPE
157
+ ignored = _is_ignored(entry, root, patterns)
158
+
159
+ if entry.is_dir():
160
+ dirs += 1
161
+ label = typer.style(entry.name + "/", fg=typer.colors.CYAN, bold=True)
162
+ dim = typer.style(" (ignored)", fg=typer.colors.BRIGHT_BLACK) if ignored else ""
163
+ typer.echo(f"{prefix}{connector}{label}{dim}")
164
+ d, f = _render_tree(entry, root, patterns, show_ignored, prefix + extension)
165
+ dirs += d
166
+ files += f
167
+ else:
168
+ files += 1
169
+ if ignored:
170
+ label = typer.style(entry.name, fg=typer.colors.BRIGHT_BLACK)
171
+ dim = typer.style(" (ignored)", fg=typer.colors.BRIGHT_BLACK)
172
+ else:
173
+ label = entry.name
174
+ dim = ""
175
+ typer.echo(f"{prefix}{connector}{label}{dim}")
176
+
177
+ return dirs, files
178
+
179
+
180
+ # ── ik module create ──────────────────────────────────────────────────────────
181
+
182
+ @module_app.command("create")
183
+ def cmd_create(
184
+ module_path: str = typer.Argument(
185
+ ...,
186
+ help="Module path, supports nesting e.g. core/models or just utils.",
187
+ ),
188
+ init: bool = typer.Option(
189
+ True, "--init/--no-init",
190
+ help="Add __init__.py to the final module directory (default: on).",
191
+ ),
192
+ init_parents: bool = typer.Option(
193
+ True, "--init-parents/--no-init-parents",
194
+ help="Add __init__.py to intermediate parent directories (default: on).",
195
+ ),
196
+ ) -> None:
197
+ """
198
+ Create a module directory with optional __init__.py files.
199
+
200
+ Supports nested paths — intermediate directories are created automatically.
201
+ Any directory or file that already exists is left untouched.
202
+
203
+ \b
204
+ Examples
205
+ --------
206
+ ik module create utils
207
+ ik module create core/models
208
+ ik module create core/models --no-init-parents # skip __init__.py in core/
209
+ ik module create core/models --no-init # folder only, no __init__.py anywhere
210
+ """
211
+ cwd = Path.cwd()
212
+ parts = Path(module_path).parts
213
+
214
+ if not parts:
215
+ _abort("Module path cannot be empty.")
216
+
217
+ typer.echo()
218
+
219
+ for i, _ in enumerate(parts):
220
+ current = cwd / Path(*parts[: i + 1])
221
+ is_leaf = i == len(parts) - 1
222
+
223
+ _make_dir(current, cwd)
224
+
225
+ if is_leaf:
226
+ if init:
227
+ _write_init(current / "__init__.py", cwd)
228
+ else:
229
+ if init_parents:
230
+ _write_init(current / "__init__.py", cwd)
231
+
232
+ typer.echo()
233
+ typer.echo(typer.style(f" ✓ module '{module_path}' ready.", fg=typer.colors.GREEN))
234
+ typer.echo()
235
+
236
+
237
+ # ── ik module delete ──────────────────────────────────────────────────────────
238
+
239
+ @module_app.command("delete")
240
+ def cmd_delete(
241
+ target: Path = typer.Argument(..., help="Module folder to delete."),
242
+ confirm: bool = typer.Option(
243
+ True, "--confirm/--no-confirm",
244
+ help="Prompt before deleting (default: on).",
245
+ ),
246
+ ) -> None:
247
+ """
248
+ Delete a module directory and everything inside it.
249
+
250
+ \b
251
+ Examples
252
+ --------
253
+ ik module delete core/models
254
+ ik module delete core/models --no-confirm
255
+ """
256
+ cwd = Path.cwd()
257
+ path = (cwd / target).resolve()
258
+
259
+ if not path.exists():
260
+ _abort(f"Path not found: {target}")
261
+
262
+ if not path.is_dir():
263
+ _abort(f"Not a directory: {target}")
264
+
265
+ try:
266
+ path.relative_to(cwd)
267
+ except ValueError:
268
+ _abort("Cannot delete a path outside the current working directory.")
269
+
270
+ if path == cwd:
271
+ _abort("Cannot delete the current working directory.")
272
+
273
+ rel = path.relative_to(cwd)
274
+ file_count = sum(1 for f in path.rglob("*") if f.is_file())
275
+
276
+ typer.echo()
277
+ typer.echo(
278
+ typer.style(" About to delete: ", fg=typer.colors.BRIGHT_BLACK)
279
+ + typer.style(str(rel), fg=typer.colors.YELLOW, bold=True)
280
+ )
281
+ typer.echo(typer.style(f" Contains {file_count} file(s).", fg=typer.colors.BRIGHT_BLACK))
282
+ typer.echo()
283
+
284
+ if confirm:
285
+ typer.confirm(" Confirm delete?", abort=True)
286
+
287
+ try:
288
+ shutil.rmtree(path)
289
+ except Exception as exc: # noqa: BLE001
290
+ _abort(f"Delete failed: {exc}")
291
+
292
+ typer.echo()
293
+ typer.echo(typer.style(f" ✓ Deleted {rel}", fg=typer.colors.GREEN))
294
+ typer.echo()
295
+
296
+
297
+ # ── ik module tree ────────────────────────────────────────────────────────────
298
+
299
+ @module_app.command("tree")
300
+ def cmd_tree(
301
+ root: Optional[Path] = typer.Argument(
302
+ None,
303
+ help="Root directory to print. Defaults to cwd.",
304
+ ),
305
+ show_ignored: bool = typer.Option(
306
+ False, "--show-ignored",
307
+ help="Include files and folders matched by .gitignore.",
308
+ ),
309
+ ) -> None:
310
+ """
311
+ Print the directory tree of a project.
312
+
313
+ Reads .gitignore from the root directory and hides matched
314
+ entries by default. Pass --show-ignored to reveal them (dimmed).
315
+
316
+ \b
317
+ Examples
318
+ --------
319
+ ik module tree
320
+ ik module tree ./my-project
321
+ ik module tree --show-ignored
322
+ """
323
+ target = (root or Path.cwd()).resolve()
324
+
325
+ if not target.exists():
326
+ _abort(f"Path not found: {target}")
327
+ if not target.is_dir():
328
+ _abort(f"Not a directory: {target}")
329
+
330
+ patterns = _load_gitignore_patterns(target)
331
+
332
+ typer.echo()
333
+ typer.echo(typer.style(target.name + "/", fg=typer.colors.CYAN, bold=True))
334
+
335
+ dirs, files = _render_tree(target, target, patterns, show_ignored)
336
+
337
+ typer.echo()
338
+ summary = typer.style(f" {dirs} director{'ies' if dirs != 1 else 'y'}, {files} file{'s' if files != 1 else ''}", fg=typer.colors.BRIGHT_BLACK)
339
+ if not show_ignored:
340
+ summary += typer.style(" (.gitignore applied — use --show-ignored to reveal)", fg=typer.colors.BRIGHT_BLACK)
341
+ typer.echo(summary)
342
+ typer.echo()
@@ -0,0 +1,81 @@
1
+ """
2
+ CLI commands for infrakit.time
3
+ """
4
+
5
+ import typer
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ from infrakit.time import profile_script
10
+
11
+ time_app = typer.Typer(name = "time", help="Profile and time Python scripts", no_args_is_help=True)
12
+
13
+
14
+ @time_app.command(name="run")
15
+ def run_profiler(
16
+ script: Path = typer.Argument(
17
+ ...,
18
+ help="Path to Python script to profile",
19
+ exists=True,
20
+ file_okay=True,
21
+ dir_okay=False,
22
+ ),
23
+ max_functions: int = typer.Option(
24
+ 30,
25
+ "--max-functions", "-n",
26
+ help="Maximum number of functions to display",
27
+ min=1,
28
+ ),
29
+ min_time: float = typer.Option(
30
+ 1.0,
31
+ "--min-time", "-t",
32
+ help="Minimum execution time in milliseconds to include",
33
+ min=0.0,
34
+ ),
35
+ include_stdlib: bool = typer.Option(
36
+ False,
37
+ "--include-stdlib",
38
+ help="Include Python standard library calls in results",
39
+ ),
40
+ ):
41
+ """
42
+ Profile a Python script using cProfile.
43
+
44
+ Executes the script under profiling and displays function-level timing statistics
45
+ in a clean, filtered table format.
46
+
47
+ Examples:
48
+
49
+ # Basic profiling
50
+ ik time run myscript.py
51
+
52
+ # Show more functions, include faster calls
53
+ ik time run myscript.py --max-functions 50 --min-time 0.1
54
+
55
+ # Include stdlib calls
56
+ ik time run myscript.py --include-stdlib
57
+ """
58
+ try:
59
+ # Run profiler (results printed automatically)
60
+ profile_script(
61
+ script_path=str(script),
62
+ max_functions=max_functions,
63
+ min_time_ms=min_time,
64
+ exclude_stdlib=not include_stdlib,
65
+ )
66
+
67
+ except FileNotFoundError as e:
68
+ typer.secho(f"✗ {e}", fg=typer.colors.RED, err=True)
69
+ raise typer.Exit(1)
70
+
71
+ except RuntimeError as e:
72
+ typer.secho(f"✗ {e}", fg=typer.colors.RED, err=True)
73
+ raise typer.Exit(1)
74
+
75
+ except Exception as e:
76
+ typer.secho(f"✗ Unexpected error: {e}", fg=typer.colors.RED, err=True)
77
+ raise typer.Exit(1)
78
+
79
+
80
+ if __name__ == "__main__":
81
+ time_app()
infrakit/cli/main.py ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ infrakit.cli.main
3
+ ~~~~~~~~~~~~~~~~~
4
+ Root Typer app. Registered as both ``infrakit`` and ``ik`` in pyproject.toml.
5
+
6
+ [project.scripts]
7
+ infrakit = "infrakit.cli.main:main"
8
+ ik = "infrakit.cli.main:main"
9
+ """
10
+
11
+ import typer
12
+
13
+ from infrakit.cli.commands.config import config_app
14
+ from infrakit.cli.commands.logger import logger_app
15
+ from infrakit.cli.commands.module import module_app
16
+ from infrakit.cli.commands.init import cmd_init
17
+ from infrakit.cli.commands.time import time_app
18
+ from infrakit.cli.commands.deps import deps_app
19
+ from infrakit.cli.commands.llm import app as llm_app
20
+
21
+ app = typer.Typer(
22
+ name="infrakit",
23
+ help="infrakit — developer infrastructure toolkit.",
24
+ no_args_is_help=True,
25
+ pretty_exceptions_enable=False,
26
+ )
27
+
28
+ app.add_typer(config_app, name="config")
29
+ app.add_typer(logger_app, name="logger")
30
+ app.add_typer(module_app, name="module")
31
+ app.add_typer(time_app, name="time")
32
+ app.add_typer(deps_app, name="deps")
33
+ app.add_typer(llm_app, name="llm")
34
+ app.command("init")(cmd_init)
35
+
36
+
37
+ def _version_callback(value: bool) -> None:
38
+ if value:
39
+ from importlib.metadata import version, PackageNotFoundError
40
+ try:
41
+ v = version("infrakit")
42
+ except PackageNotFoundError:
43
+ v = "dev"
44
+ typer.echo(f"infrakit {v}")
45
+ raise typer.Exit()
46
+
47
+
48
+ @app.callback()
49
+ def root(
50
+ version: bool = typer.Option(
51
+ False, "--version", "-V",
52
+ callback=_version_callback,
53
+ is_eager=True,
54
+ help="Print version and exit.",
55
+ ),
56
+ ) -> None:
57
+ """infrakit — developer infrastructure toolkit."""
58
+
59
+
60
+ def main() -> None: # pragma: no cover
61
+ app()
62
+
63
+
64
+ if __name__ == "__main__": # pragma: no cover
65
+ main()
File without changes
File without changes