viperx 0.9.14__py3-none-any.whl → 0.9.26__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.
viperx/config_engine.py CHANGED
@@ -129,7 +129,7 @@ class ConfigEngine:
129
129
  author=project_conf.get("author", "Your Name"), # Inherit author
130
130
  use_env=pkg.get("use_env", settings_conf.get("use_env", False)), # Inherit settings or default False
131
131
  use_config=pkg.get("use_config", settings_conf.get("use_config", True)), # Inherit or default True
132
- use_readme=pkg.get("use_readme", True),
132
+ use_readme=pkg.get("use_readme", False),
133
133
  use_tests=pkg.get("use_tests", settings_conf.get("use_tests", True)),
134
134
  framework=pkg.get("framework", FRAMEWORK_PYTORCH),
135
135
  verbose=self.verbose
viperx/constants.py CHANGED
@@ -33,3 +33,8 @@ PROJECT_TYPES = [TYPE_CLASSIC, TYPE_ML, TYPE_DL]
33
33
  FRAMEWORK_PYTORCH = "pytorch"
34
34
  FRAMEWORK_TENSORFLOW = "tensorflow"
35
35
  DL_FRAMEWORKS = [FRAMEWORK_PYTORCH, FRAMEWORK_TENSORFLOW]
36
+
37
+ # Builders
38
+ BUILDER_UV = "uv"
39
+ BUILDER_HATCH = "hatch"
40
+ SUPPORTED_BUILDERS = [BUILDER_UV, BUILDER_HATCH]
viperx/core.py CHANGED
@@ -62,6 +62,13 @@ class ProjectGenerator:
62
62
  loader=PackageLoader("viperx", "templates"),
63
63
  autoescape=select_autoescape()
64
64
  )
65
+
66
+ # Validate Builder
67
+ from viperx.utils import check_builder_installed
68
+ if not check_builder_installed(self.builder):
69
+ console.print(f"[bold red]Error:[/bold red] The requested builder '[bold]{self.builder}[/bold]' is not installed or not in PATH.")
70
+ console.print(f"Please install it (e.g., `pip install {self.builder}` or `curl -LsSf https://astral.sh/uv/install.sh | sh` for uv).")
71
+ sys.exit(1)
65
72
 
66
73
  def log(self, message: str, style: str = "dim"):
67
74
  if self.verbose:
@@ -136,6 +143,13 @@ class ProjectGenerator:
136
143
  # 4. Overwrite/Add Files
137
144
  self._generate_files(project_dir, is_subpackage)
138
145
 
146
+
147
+ # Cleanup extra files for subpackages
148
+ if is_subpackage:
149
+ for f in [".gitignore", ".python-version"]:
150
+ if (project_dir / f).exists():
151
+ (project_dir / f).unlink()
152
+
139
153
  # 5. Git & Final Steps
140
154
  console.print(f"\n[bold green]✓ Project {self.raw_name} created successfully![/bold green]")
141
155
  if not is_subpackage:
@@ -189,8 +203,14 @@ class ProjectGenerator:
189
203
  "framework": self.framework,
190
204
  }
191
205
 
192
- # pyproject.toml (Overwrite uv's basic one to add our specific deps)
193
- self._render("pyproject.toml.j2", root / "pyproject.toml", context)
206
+ if not is_subpackage:
207
+ # pyproject.toml (Overwrite uv's basic one to add our specific deps)
208
+ self._render("pyproject.toml.j2", root / "pyproject.toml", context)
209
+ else:
210
+ # Subpackages: remove default pyproject.toml from uv init
211
+ if (root / "pyproject.toml").exists():
212
+ (root / "pyproject.toml").unlink()
213
+ self.log("Removed default pyproject.toml (Subpackage: Code Only)")
194
214
 
195
215
  # Determine Package Root
196
216
  if is_subpackage:
@@ -210,19 +230,33 @@ class ProjectGenerator:
210
230
  self._render("__init__.py.j2", pkg_root / "__init__.py", context)
211
231
 
212
232
  # README.md
213
- if self.use_readme:
214
- self._render("README.md.j2", root / "README.md", context)
233
+ # README.md
234
+ if not is_subpackage:
235
+ # Root Project: Respect use_readme
236
+ if self.use_readme:
237
+ self._render("README.md.j2", root / "README.md", context)
238
+ else:
239
+ if (root / "README.md").exists():
240
+ (root / "README.md").unlink()
241
+ self.log("Removed default README.md (requested --no-readme)")
215
242
  else:
216
- if (root / "README.md").exists():
243
+ # Subpackage: Default False, but if True, generate it
244
+ if self.use_readme:
245
+ self._render("README.md.j2", root / "README.md", context)
246
+ elif (root / "README.md").exists():
247
+ # Cleanup default README from uv init if we didn't request one
217
248
  (root / "README.md").unlink()
218
- self.log("Removed default README.md (requested --no-readme)")
249
+
219
250
 
220
251
  # LICENSE
221
- license_text = LICENSES.get(self.license, LICENSES["MIT"])
222
- license_text = license_text.format(year=datetime.now().year, author=self.author)
223
- with open(root / "LICENSE", "w") as f:
224
- f.write(license_text)
225
- self.log(f"Generated LICENSE ({self.license})")
252
+ if not is_subpackage:
253
+ license_text = LICENSES.get(self.license, LICENSES["MIT"])
254
+ license_text = license_text.format(year=datetime.now().year, author=self.author)
255
+ with open(root / "LICENSE", "w") as f:
256
+ f.write(license_text)
257
+ self.log(f"Generated LICENSE ({self.license})")
258
+ elif (root / "LICENSE").exists():
259
+ (root / "LICENSE").unlink()
226
260
 
227
261
  # Config files
228
262
  if self.use_config:
@@ -249,10 +283,12 @@ class ProjectGenerator:
249
283
  self.log(f"Created .env and .env.example in {pkg_root.relative_to(root)}")
250
284
 
251
285
  # .gitignore
252
- with open(root / ".gitignore", "a") as f:
253
- # Add data/ to gitignore but allow .gitkeep
254
- f.write("\n# ViperX specific\n.ipynb_checkpoints/\n# Isolated Env\nsrc/**/.env\n# Data (Local)\ndata/*\n!data/.gitkeep\n")
255
- self.log("Updated .gitignore")
286
+ # Only for Root
287
+ if not is_subpackage:
288
+ with open(root / ".gitignore", "a") as f:
289
+ # Add data/ to gitignore but allow .gitkeep
290
+ f.write("\n# ViperX specific\n.ipynb_checkpoints/\n# Isolated Env\nsrc/**/.env\n# Data (Local)\ndata/*\n!data/.gitkeep\n")
291
+ self.log("Updated .gitignore")
256
292
 
257
293
  def _render(self, template_name: str, target_path: Path, context: dict):
258
294
  template = self.env.get_template(template_name)
viperx/main.py CHANGED
@@ -14,9 +14,14 @@ from viperx.constants import (
14
14
  DL_FRAMEWORKS,
15
15
  FRAMEWORK_PYTORCH,
16
16
  )
17
-
18
- HELP_TEXT = """
19
- [bold green]ViperX[/bold green]: Professional Python Project Initializer
17
+ import importlib.metadata
18
+ try:
19
+ version = importlib.metadata.version("viperx")
20
+ except importlib.metadata.PackageNotFoundError:
21
+ version = "unknown"
22
+
23
+ HELP_TEXT = f"""
24
+ [bold green]ViperX[/bold green] (v{version}): Professional Python Project Initializer
20
25
  .
21
26
 
22
27
  Automates the creation of professional-grade Python projects using `uv`.
@@ -28,31 +33,15 @@ app = typer.Typer(
28
33
  add_completion=False,
29
34
  no_args_is_help=True,
30
35
  rich_markup_mode="markdown",
31
- epilog="Made with ❤️ by KpihX"
36
+ epilog="Made with ❤️ by KpihX"
32
37
  )
33
38
 
34
39
  # Global state for verbose flag
35
40
  state = {"verbose": False}
36
41
  console = Console(force_terminal=True)
37
42
 
38
-
39
- """
40
- **ViperX**: Professional Python Project Initializer.
41
-
42
- Automates the creation of professional-grade Python projects using `uv`.
43
- Supports Standard Libraries, Machine Learning, and Deep Learning templates.
44
- """
45
- if verbose:
46
- state["verbose"] = True
47
- console.print("[dim]Verbose mode enabled[/dim]")
48
-
49
43
  def version_callback(value: bool):
50
44
  if value:
51
- import importlib.metadata
52
- try:
53
- version = importlib.metadata.version("viperx")
54
- except importlib.metadata.PackageNotFoundError:
55
- version = "unknown"
56
45
  console.print(f"ViperX CLI Version: [bold green]{version}[/bold green]")
57
46
  raise typer.Exit()
58
47
 
@@ -79,9 +68,21 @@ def cli_callback(
79
68
  if verbose:
80
69
  state["verbose"] = True
81
70
  console.print("[dim]Verbose mode enabled[/dim]")
71
+
72
+
82
73
 
83
- @app.command()
84
- def init(
74
+
75
+ # Config Management Group (The Main Entry Point)
76
+ config_app = typer.Typer(
77
+ help="Manage Declarative Configuration (viperx.yaml).",
78
+ no_args_is_help=False, # Allow running without subcommands (acts as apply)
79
+ )
80
+ app.add_typer(config_app, name="config")
81
+
82
+
83
+ @config_app.callback(invoke_without_command=True)
84
+ def config_main(
85
+ ctx: typer.Context,
85
86
  # --- Config Driven Mode ---
86
87
  config: Path = typer.Option(
87
88
  None, "--config", "-c",
@@ -115,10 +116,17 @@ def init(
115
116
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
116
117
  ):
117
118
  """
118
- Initialize a new Python project.
119
+ **Configure & Initialize**: Apply configuration to create or update a project.
119
120
 
120
- Can stem from a config file (Declarative) or CLI arguments (Imperative).
121
+ usage: [bold]viperx config [OPTIONS][/bold]
122
+ [bold]viperx config get[/bold]
121
123
  """
124
+ # Check if a subcommand (like 'get') is invoked
125
+ if ctx.invoked_subcommand is not None:
126
+ return
127
+
128
+ # --- Apply Logic (Former Init) ---
129
+
122
130
  # 1. Declarative Mode
123
131
  if config:
124
132
  if not config.exists():
@@ -131,6 +139,10 @@ def init(
131
139
 
132
140
  # 2. Imperative Mode (Validation)
133
141
  if not name:
142
+ # Implicitly show help if no options provided?
143
+ # Or error out. User expects correct run.
144
+ # If user runs `viperx config` with NO args, and NO config, what happens?
145
+ # "Missing option name".
134
146
  console.print("[bold red]Error:[/bold red] Missing option '--name' / '-n'. Required in manual mode.")
135
147
  raise typer.Exit(code=1)
136
148
 
@@ -156,12 +168,8 @@ def init(
156
168
  # Generate in current directory
157
169
  generator.generate(Path.cwd())
158
170
 
159
- # Config Management Group
160
- config_app = typer.Typer(
161
- help="Manage Declarative Configuration (viperx.yaml).",
162
- no_args_is_help=True
163
- )
164
- app.add_typer(config_app, name="config")
171
+
172
+
165
173
 
166
174
  @config_app.command("get")
167
175
  def config_get(
@@ -212,7 +220,7 @@ def package_add(
212
220
  ),
213
221
  use_env: bool = typer.Option(True, "--env/--no-env", help="Generate .env file"),
214
222
  use_config: bool = typer.Option(True, "--embed-config/--no-embed-config", help="Generate embedded config"),
215
- use_readme: bool = typer.Option(True, "--readme/--no-readme", help="Generate README.md"),
223
+ use_readme: bool = typer.Option(False, "--readme/--no-readme", help="Generate README.md"),
216
224
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
217
225
  ):
218
226
  """
@@ -275,4 +283,18 @@ def package_update(
275
283
  generator.update_package(Path.cwd())
276
284
 
277
285
  if __name__ == "__main__":
278
- app()
286
+ try:
287
+ app()
288
+ except SystemExit as e:
289
+ if e.code != 0:
290
+ # On error (non-zero exit), display help as requested
291
+ from typer.main import get_command
292
+ import click
293
+ cli = get_command(app)
294
+ # Create a dummy context to render help
295
+ # We print it to stderr or stdout? Console prints to stdout usually.
296
+ # User wants it displayed immediately.
297
+ with click.Context(cli) as ctx:
298
+ console.print("\n")
299
+ console.print(cli.get_help(ctx))
300
+ raise
@@ -28,6 +28,7 @@ def get_config(key: str, default: Any = None) -> Any:
28
28
  """Retrieve a value from the globally loaded settings."""
29
29
  return SETTINGS.get(key, default)
30
30
 
31
+ {% if project_type != 'classic' %}
31
32
  def get_dataset_path(notebook_name: str, key: str = "datasets", extension: str = ".csv") -> str | None:
32
33
  """
33
34
  Helper for notebook data loading.
@@ -38,3 +39,4 @@ def get_dataset_path(notebook_name: str, key: str = "datasets", extension: str =
38
39
  if not dataset_name:
39
40
  return None
40
41
  return f"{dataset_name}{extension}"
42
+ {% endif %}
@@ -43,13 +43,10 @@ dependencies = [
43
43
  {% if builder == 'hatch' %}
44
44
  requires = ["hatchling"]
45
45
  build-backend = "hatchling.build"
46
- {% elif builder == 'flit' %}
47
- requires = ["flit_core>=3.2,<4"]
48
- build-backend = "flit_core.buildapi"
49
46
  {% else %}
50
- # Default to Setuptools (Standard/Robust) to avoid Hatchling unless requested
51
- requires = ["setuptools>=61.0"]
52
- build-backend = "setuptools.build_meta"
47
+ # Default: uv native build backend
48
+ requires = ["uv_build>=0.9.21,<0.10.0"]
49
+ build-backend = "uv_build"
53
50
  {% endif %}
54
51
 
55
52
  {% if use_uv %}
@@ -42,4 +42,5 @@ workspace:
42
42
  # use_env: false
43
43
  # use_config: true
44
44
  # use_tests: true
45
+ # use_readme: false
45
46
 
viperx/utils.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import re
2
2
  import shutil
3
- import subprocess
4
3
  from rich.console import Console
5
4
 
6
5
  console = Console()
@@ -32,16 +31,32 @@ def validate_project_name(ctx, param, value):
32
31
  raise BadParameter("Project name must contain only letters, numbers, underscores, and hyphens.")
33
32
  return value
34
33
 
34
+ def check_builder_installed(builder: str) -> bool:
35
+ """
36
+ Check if the specified builder is installed.
37
+ Uses shutil.which() for robust path detection.
38
+ """
39
+ return shutil.which(builder) is not None
40
+
35
41
  def get_author_from_git() -> tuple[str, str]:
36
42
  """
37
43
  Attempt to get author name and email from git config.
38
44
  Returns (name, email) or defaults.
39
45
  """
40
46
  try:
47
+ # Check if git is installed first
48
+ if not shutil.which("git"):
49
+ return "Nameless", "nameless@example.com"
50
+
41
51
  import git
42
- config = git.GitConfigParser(git.GitConfigParser.get_global_config(), read_only=True)
43
- name = config.get("user", "name", fallback="Your Name")
44
- email = config.get("user", "email", fallback="your.email@example.com")
45
- return name, email
52
+ # Robust way using git command wrapper
53
+ reader = git.Git().config
54
+ name = reader("--global", "--get", "user.name")
55
+ email = reader("--global", "--get", "user.email")
56
+
57
+ # strip newlines if any
58
+ return (name.strip() if name else "Nameless",
59
+ email.strip() if email else "nameless@example.com")
46
60
  except Exception:
47
- return "Your Name", "your.email@example.com"
61
+ # Fallback if git call fails
62
+ return "Nameless", "nameless@example.com"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: viperx
3
- Version: 0.9.14
3
+ Version: 0.9.26
4
4
  Summary: Professional Python Project Initializer with uv, ml/dl support, and embedded config.
5
5
  Keywords: python,project-template,uv,data-science,machine-learning
6
6
  Author: Ivann KAMDEM
@@ -1,22 +1,22 @@
1
1
  viperx/__init__.py,sha256=mRrD-SK9PtwBhqgLzJrvefdViNsXCyBGknzFNsIou1I,51
2
- viperx/config_engine.py,sha256=Fzb5F3rE7BqcnpAdu6QhN28tfQNTuBHI04YuR1B7cHM,7340
3
- viperx/constants.py,sha256=BMogG1-ik67YeheQdCgjsdS5FNFYiLfdro6utCWwczY,784
4
- viperx/core.py,sha256=dOLLPxBohYN6A5i1BnT5gx35x6x5n40yYYGm6yEZKHM,18733
2
+ viperx/config_engine.py,sha256=9P9xxWIYWhL_QFRyvi601D3_4HBHn4RA20dreGwdSX4,7341
3
+ viperx/constants.py,sha256=Mv5tFjFxe3tD4VDCEsXiwfiV7biTlzl45ALkHmiK33M,887
4
+ viperx/core.py,sha256=R0IzxNTL_Coyz1bCTc2QAnQM3FK36ADV86NVQXuXmBM,20438
5
5
  viperx/licenses.py,sha256=TPsG1aqNAjtOsvuD6i3SiC5rUK9f3DslPHQkKiV7vxs,12482
6
- viperx/main.py,sha256=B8Z6bjESFKFvL80NJcfPGgYycxQP3NwLhKtJDtaj1Zc,9856
6
+ viperx/main.py,sha256=N0pqLnZoWr00XjH1kS5dxbDKEqXvazsVspqLQ_-fF8k,10663
7
7
  viperx/templates/Base.ipynb.j2,sha256=rlGCw7jIds3RiXFrG8D8oAjzXzzj1cayKjBI91k-1kA,3140
8
8
  viperx/templates/Base_General.ipynb.j2,sha256=f5ZUu65khtvBeCofs3Lvzccn5Krl0nRGu0dv7SzLvzg,2716
9
9
  viperx/templates/Base_Kaggle.ipynb.j2,sha256=gPkmFt4cA5UzhRU_gLZ1UzSjEa7Mgel001Eqiw94-fc,2698
10
10
  viperx/templates/README.md.j2,sha256=TJNUwTcKnjALrP0MOSs8UBx8zNhI8erRHrc2-8nuMgQ,3445
11
11
  viperx/templates/__init__.py.j2,sha256=uq8IYd0SGYX2-cpGb9q5197eYs-ode-g5y2NCGWU5YM,196
12
- viperx/templates/config.py.j2,sha256=sU6bwiA0eW7g0d9Y_rhxSd-6LcMUyYiZKBhcF_OJcxM,1409
12
+ viperx/templates/config.py.j2,sha256=ExMbopPQPntmkNgbv9uKooGDjv82gh_XhyQmjJ8MiQg,1456
13
13
  viperx/templates/config.yaml.j2,sha256=IYvjQUioZ9lyzVHVDgwrdjgM6s4pUnCFXwtnydoKe2k,485
14
14
  viperx/templates/data_loader.py.j2,sha256=hehewwvBAYJM-acKn6_T4cU5EvUPX7wHAurAQ3z3ET4,3696
15
15
  viperx/templates/main.py.j2,sha256=caGN54Hck_EcwT3Aqju52CBuGujSnzU2Hehw8mowzjI,277
16
- viperx/templates/pyproject.toml.j2,sha256=RnJm7BinT-p646BiC2gxOTBuIX0nmRM7BUqt0n981io,1434
17
- viperx/templates/viperx_config.yaml.j2,sha256=dZ5GtiGRWT_FOsMeF1YUjqL5wHO0y0i38gqU-lwrVSQ,1379
18
- viperx/utils.py,sha256=2kiQLDHsPcCELEKeQYbIkSL8e3HhW4pRzw1pUth6hYA,1583
19
- viperx-0.9.14.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
20
- viperx-0.9.14.dist-info/entry_points.txt,sha256=Is38BrTuf6unQCLgDE990Rjd9HyGldBSw4Uzy_nMePY,44
21
- viperx-0.9.14.dist-info/METADATA,sha256=OC8UDqEikK2-pU1RKt02YGRbKtsLvg0p-7OFGzvzXKA,6782
22
- viperx-0.9.14.dist-info/RECORD,,
16
+ viperx/templates/pyproject.toml.j2,sha256=9s3wbXZjoA_643feZAlS5-7YMQLxVUd6XLPhDusyuJY,1287
17
+ viperx/templates/viperx_config.yaml.j2,sha256=a2H0N_W8sq0Zrjio4El7kFjOYQUATf_VdD9zK3NzG3U,1405
18
+ viperx/utils.py,sha256=yCJMclJT10_Vc8A1dx_lP0OSs7crCnVNLP46SCpxmog,2054
19
+ viperx-0.9.26.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
20
+ viperx-0.9.26.dist-info/entry_points.txt,sha256=Is38BrTuf6unQCLgDE990Rjd9HyGldBSw4Uzy_nMePY,44
21
+ viperx-0.9.26.dist-info/METADATA,sha256=GuapQRE5fpVYvo6_5i54EI8uA1AplAIAAsEaSiBTty4,6782
22
+ viperx-0.9.26.dist-info/RECORD,,