viperx 0.9.5__tar.gz → 0.9.49__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.
- {viperx-0.9.5 → viperx-0.9.49}/PKG-INFO +3 -3
- {viperx-0.9.5 → viperx-0.9.49}/README.md +2 -2
- {viperx-0.9.5 → viperx-0.9.49}/pyproject.toml +1 -1
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/config_engine.py +78 -20
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/constants.py +5 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/core.py +135 -107
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/main.py +78 -22
- viperx-0.9.49/src/viperx/templates/__init__.py.j2 +8 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/config.py.j2 +2 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/config.yaml.j2 +3 -0
- viperx-0.9.49/src/viperx/templates/main.py.j2 +13 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/pyproject.toml.j2 +23 -16
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/viperx_config.yaml.j2 +8 -11
- viperx-0.9.49/src/viperx/utils.py +78 -0
- viperx-0.9.5/src/viperx/templates/__init__.py.j2 +0 -8
- viperx-0.9.5/src/viperx/templates/main.py.j2 +0 -13
- viperx-0.9.5/src/viperx/utils.py +0 -47
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/__init__.py +0 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/licenses.py +0 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/Base.ipynb.j2 +0 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/Base_General.ipynb.j2 +0 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/Base_Kaggle.ipynb.j2 +0 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/README.md.j2 +0 -0
- {viperx-0.9.5 → viperx-0.9.49}/src/viperx/templates/data_loader.py.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: viperx
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.49
|
|
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
|
|
@@ -64,7 +64,7 @@ viperx init -n deep-vision -t dl --framework pytorch
|
|
|
64
64
|
viperx init -n deep-vision -t dl --framework pytorch
|
|
65
65
|
|
|
66
66
|
# ✨ Declarative Config (Infrastructure as Code)
|
|
67
|
-
viperx config
|
|
67
|
+
viperx config get # Generate template
|
|
68
68
|
viperx init -c viperx.yaml # Apply config
|
|
69
69
|
```
|
|
70
70
|
|
|
@@ -174,7 +174,7 @@ viperx init -c viperx.yaml
|
|
|
174
174
|
Manage your project infrastructure using a YAML file.
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
|
-
viperx config
|
|
177
|
+
viperx config get
|
|
178
178
|
```
|
|
179
179
|
Generates a `viperx.yaml` template in the current directory.
|
|
180
180
|
|
|
@@ -45,7 +45,7 @@ viperx init -n deep-vision -t dl --framework pytorch
|
|
|
45
45
|
viperx init -n deep-vision -t dl --framework pytorch
|
|
46
46
|
|
|
47
47
|
# ✨ Declarative Config (Infrastructure as Code)
|
|
48
|
-
viperx config
|
|
48
|
+
viperx config get # Generate template
|
|
49
49
|
viperx init -c viperx.yaml # Apply config
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -155,7 +155,7 @@ viperx init -c viperx.yaml
|
|
|
155
155
|
Manage your project infrastructure using a YAML file.
|
|
156
156
|
|
|
157
157
|
```bash
|
|
158
|
-
viperx config
|
|
158
|
+
viperx config get
|
|
159
159
|
```
|
|
160
160
|
Generates a `viperx.yaml` template in the current directory.
|
|
161
161
|
|
|
@@ -4,7 +4,7 @@ from rich.console import Console
|
|
|
4
4
|
from rich.panel import Panel
|
|
5
5
|
|
|
6
6
|
from viperx.core import ProjectGenerator
|
|
7
|
-
from viperx.constants import DEFAULT_LICENSE, DEFAULT_BUILDER, TYPE_CLASSIC, FRAMEWORK_PYTORCH
|
|
7
|
+
from viperx.constants import DEFAULT_LICENSE, DEFAULT_BUILDER, TYPE_CLASSIC, TYPE_ML, TYPE_DL, FRAMEWORK_PYTORCH
|
|
8
8
|
|
|
9
9
|
console = Console()
|
|
10
10
|
|
|
@@ -47,15 +47,22 @@ class ConfigEngine:
|
|
|
47
47
|
workspace_conf = self.config.get("workspace", {})
|
|
48
48
|
|
|
49
49
|
project_name = project_conf.get("name")
|
|
50
|
-
target_dir = self.root_path / project_name
|
|
51
50
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
# STRICT NAMING: Always calculate the expected root path using sanitized name
|
|
52
|
+
from viperx.utils import sanitize_project_name
|
|
53
|
+
clean_name = sanitize_project_name(project_name)
|
|
55
54
|
|
|
56
|
-
#
|
|
57
|
-
|
|
55
|
+
# Default assumption: current_root is the target directory (folder with underscores)
|
|
56
|
+
current_root = self.root_path / clean_name
|
|
57
|
+
target_dir = current_root
|
|
58
|
+
|
|
59
|
+
# 1. Root Project Handling
|
|
60
|
+
# Heuristic: Are we already in a folder matching the raw name OR sanitized name?
|
|
61
|
+
# e.g. inside test_classic/
|
|
62
|
+
if self.root_path.name == project_name or self.root_path.name == clean_name:
|
|
58
63
|
# We are inside the project folder
|
|
64
|
+
current_root = self.root_path
|
|
65
|
+
|
|
59
66
|
if not (self.root_path / "pyproject.toml").exists():
|
|
60
67
|
console.print(Panel(f"⚠️ [bold yellow]Current directory matches name but is not initialized. Hydrating:[/bold yellow] {project_name}", border_style="yellow"))
|
|
61
68
|
gen = ProjectGenerator(
|
|
@@ -69,27 +76,69 @@ class ConfigEngine:
|
|
|
69
76
|
use_config=settings_conf.get("use_config", True),
|
|
70
77
|
use_tests=settings_conf.get("use_tests", True),
|
|
71
78
|
framework=settings_conf.get("framework", FRAMEWORK_PYTORCH),
|
|
79
|
+
scripts={project_name: f"{clean_name}.main:main"},
|
|
72
80
|
verbose=self.verbose
|
|
73
81
|
)
|
|
74
|
-
# generate() expects parent dir, and will operate on parent/name (which is self.root_path)
|
|
75
82
|
gen.generate(self.root_path.parent)
|
|
76
83
|
else:
|
|
77
84
|
console.print(Panel(f"♻️ [bold blue]Syncing Project:[/bold blue] {project_name}", border_style="blue"))
|
|
78
|
-
|
|
85
|
+
|
|
79
86
|
else:
|
|
80
|
-
# We are outside
|
|
87
|
+
# We are outside
|
|
88
|
+
# target_dir (clean) is already set as current_root default
|
|
89
|
+
|
|
81
90
|
if target_dir.exists() and (target_dir / "pyproject.toml").exists():
|
|
82
|
-
console.print(Panel(f"♻️ [bold blue]Updating Existing Project:[/bold blue] {project_name}", border_style="blue"))
|
|
83
|
-
current_root = target_dir
|
|
91
|
+
console.print(Panel(f"♻️ [bold blue]Updating Existing Project:[/bold blue] {project_name} ({target_dir.name})", border_style="blue"))
|
|
84
92
|
else:
|
|
85
93
|
if target_dir.exists():
|
|
86
94
|
console.print(Panel(f"⚠️ [bold yellow]Directory exists but not initialized. Hydrating:[/bold yellow] {project_name}", border_style="yellow"))
|
|
87
|
-
else:
|
|
88
|
-
console.print(Panel(f"🚀 [bold green]Creating New Project:[/bold green] {project_name}", border_style="green"))
|
|
89
95
|
|
|
90
|
-
#
|
|
96
|
+
# Prepare Scripts & Dependency Context
|
|
97
|
+
packages = workspace_conf.get("packages", [])
|
|
98
|
+
|
|
99
|
+
# --- Aggregate Global Dependencies ---
|
|
100
|
+
root_use_config = settings_conf.get("use_config", True)
|
|
101
|
+
root_use_env = settings_conf.get("use_env", False)
|
|
102
|
+
root_type = settings_conf.get("type", TYPE_CLASSIC)
|
|
103
|
+
root_framework = settings_conf.get("framework", FRAMEWORK_PYTORCH)
|
|
104
|
+
|
|
105
|
+
glob_has_config = root_use_config
|
|
106
|
+
glob_has_env = root_use_env
|
|
107
|
+
glob_is_ml_dl = root_type in [TYPE_ML, TYPE_DL]
|
|
108
|
+
glob_is_dl = root_type == TYPE_DL
|
|
109
|
+
glob_frameworks = {root_framework} if glob_is_dl else set()
|
|
110
|
+
|
|
111
|
+
project_scripts = {project_name: f"{clean_name}.main:main"} # Use clean mapping
|
|
112
|
+
|
|
113
|
+
for pkg in packages:
|
|
114
|
+
# Scripts
|
|
115
|
+
pkg_name = pkg.get("name")
|
|
116
|
+
pkg_name_clean = sanitize_project_name(pkg_name)
|
|
117
|
+
project_scripts[pkg_name] = f"{pkg_name_clean}.main:main"
|
|
118
|
+
|
|
119
|
+
# Dependency Aggregation
|
|
120
|
+
p_config = pkg.get("use_config", settings_conf.get("use_config", True))
|
|
121
|
+
p_env = pkg.get("use_env", settings_conf.get("use_env", False))
|
|
122
|
+
p_type = pkg.get("type", TYPE_CLASSIC)
|
|
123
|
+
p_framework = pkg.get("framework", FRAMEWORK_PYTORCH)
|
|
124
|
+
|
|
125
|
+
if p_config: glob_has_config = True
|
|
126
|
+
if p_env: glob_has_env = True
|
|
127
|
+
if p_type in [TYPE_ML, TYPE_DL]: glob_is_ml_dl = True
|
|
128
|
+
if p_type == TYPE_DL:
|
|
129
|
+
glob_is_dl = True
|
|
130
|
+
glob_frameworks.add(p_framework)
|
|
131
|
+
|
|
132
|
+
dep_context = {
|
|
133
|
+
"has_config": glob_has_config,
|
|
134
|
+
"has_env": glob_has_env,
|
|
135
|
+
"is_ml_dl": glob_is_ml_dl,
|
|
136
|
+
"is_dl": glob_is_dl,
|
|
137
|
+
"frameworks": list(glob_frameworks)
|
|
138
|
+
}
|
|
139
|
+
|
|
91
140
|
gen = ProjectGenerator(
|
|
92
|
-
name=project_name,
|
|
141
|
+
name=project_name, # Raw name
|
|
93
142
|
description=project_conf.get("description", ""),
|
|
94
143
|
type=settings_conf.get("type", TYPE_CLASSIC),
|
|
95
144
|
author=project_conf.get("author", None),
|
|
@@ -99,10 +148,19 @@ class ConfigEngine:
|
|
|
99
148
|
use_config=settings_conf.get("use_config", True),
|
|
100
149
|
use_tests=settings_conf.get("use_tests", True),
|
|
101
150
|
framework=settings_conf.get("framework", FRAMEWORK_PYTORCH),
|
|
151
|
+
scripts=project_scripts,
|
|
152
|
+
dependency_context=dep_context,
|
|
102
153
|
verbose=self.verbose
|
|
103
154
|
)
|
|
104
155
|
gen.generate(self.root_path)
|
|
105
|
-
|
|
156
|
+
|
|
157
|
+
# Verify creation
|
|
158
|
+
if not current_root.exists():
|
|
159
|
+
if (self.root_path / project_name).exists():
|
|
160
|
+
current_root = self.root_path / project_name
|
|
161
|
+
|
|
162
|
+
if self.verbose:
|
|
163
|
+
console.print(f"[debug] Project Root resolves to: {current_root}")
|
|
106
164
|
|
|
107
165
|
# 2. Copy Config to Root (Source of Truth)
|
|
108
166
|
# Only if we aren't reading the one already there
|
|
@@ -115,7 +173,7 @@ class ConfigEngine:
|
|
|
115
173
|
# 3. Handle Workspace Packages
|
|
116
174
|
packages = workspace_conf.get("packages", [])
|
|
117
175
|
if packages:
|
|
118
|
-
console.print(f"
|
|
176
|
+
console.print(f"\\n📦 [bold]Processing {len(packages)} workspace packages...[/bold]")
|
|
119
177
|
|
|
120
178
|
for pkg in packages:
|
|
121
179
|
pkg_name = pkg.get("name")
|
|
@@ -129,7 +187,7 @@ class ConfigEngine:
|
|
|
129
187
|
author=project_conf.get("author", "Your Name"), # Inherit author
|
|
130
188
|
use_env=pkg.get("use_env", settings_conf.get("use_env", False)), # Inherit settings or default False
|
|
131
189
|
use_config=pkg.get("use_config", settings_conf.get("use_config", True)), # Inherit or default True
|
|
132
|
-
use_readme=pkg.get("use_readme",
|
|
190
|
+
use_readme=pkg.get("use_readme", False),
|
|
133
191
|
use_tests=pkg.get("use_tests", settings_conf.get("use_tests", True)),
|
|
134
192
|
framework=pkg.get("framework", FRAMEWORK_PYTORCH),
|
|
135
193
|
verbose=self.verbose
|
|
@@ -138,4 +196,4 @@ class ConfigEngine:
|
|
|
138
196
|
# Check if package seems to exist (ProjectGenerator handles upgrade logic too)
|
|
139
197
|
pkg_gen.add_to_workspace(current_root)
|
|
140
198
|
|
|
141
|
-
console.print(Panel(f"✨ [bold green]Configuration Applied Successfully![/bold green]
|
|
199
|
+
console.print(Panel(f"✨ [bold green]Configuration Applied Successfully![/bold green]\\nProject is up to date with {self.config_path.name}", border_style="green"))
|
|
@@ -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]
|
|
@@ -34,12 +34,32 @@ class ProjectGenerator:
|
|
|
34
34
|
license: str = DEFAULT_LICENSE,
|
|
35
35
|
builder: str = DEFAULT_BUILDER,
|
|
36
36
|
framework: str = "pytorch",
|
|
37
|
+
scripts: Optional[dict] = None,
|
|
38
|
+
dependency_context: Optional[dict] = None,
|
|
37
39
|
verbose: bool = False):
|
|
38
40
|
self.raw_name = name
|
|
39
41
|
self.project_name = sanitize_project_name(name)
|
|
40
42
|
self.description = description or name
|
|
41
43
|
self.type = type
|
|
42
44
|
self.framework = framework
|
|
45
|
+
self.scripts = scripts or {}
|
|
46
|
+
# Dependency Context (Global workspace features)
|
|
47
|
+
self.dependency_context = dependency_context or {
|
|
48
|
+
"has_config": use_config,
|
|
49
|
+
"has_env": use_env,
|
|
50
|
+
"is_ml_dl": type in ["ml", "dl"],
|
|
51
|
+
"is_dl": type == "dl",
|
|
52
|
+
"frameworks": [framework] if type == "dl" else []
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Default script for the main package if none provided (and it's a root project mostly)
|
|
56
|
+
self.scripts = scripts or {}
|
|
57
|
+
# Default script for the main package if none provided (and it's a root project mostly)
|
|
58
|
+
if not self.scripts:
|
|
59
|
+
# Key = Raw Name (CLI command, e.g. test-classic)
|
|
60
|
+
# Value = Sanitized Path (Module, e.g. test_classic.main:main)
|
|
61
|
+
self.scripts = {self.raw_name: f"{self.project_name}.main:main"}
|
|
62
|
+
|
|
43
63
|
self.author = author
|
|
44
64
|
if not self.author or self.author == "Your Name":
|
|
45
65
|
self.author, self.author_email = get_author_from_git()
|
|
@@ -54,9 +74,39 @@ class ProjectGenerator:
|
|
|
54
74
|
self.use_tests = use_tests
|
|
55
75
|
self.verbose = verbose
|
|
56
76
|
|
|
57
|
-
# Detect System Python
|
|
58
|
-
self.
|
|
77
|
+
# Detect System Python (For logging/diagnostics)
|
|
78
|
+
self.system_python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
79
|
+
|
|
80
|
+
# Project Python Version (For requires-python in pyproject.toml)
|
|
81
|
+
# Driven by package constants to ensure compatibility/evolution
|
|
82
|
+
from viperx.constants import DEFAULT_PYTHON_VERSION
|
|
83
|
+
self.python_version = DEFAULT_PYTHON_VERSION
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Validate Choices
|
|
87
|
+
from viperx.utils import validate_choice, check_builder_installed
|
|
88
|
+
from viperx.constants import PROJECT_TYPES, DL_FRAMEWORKS, TYPE_DL
|
|
59
89
|
|
|
90
|
+
try:
|
|
91
|
+
validate_choice(self.type, PROJECT_TYPES, "project type")
|
|
92
|
+
if self.type == TYPE_DL:
|
|
93
|
+
validate_choice(self.framework, DL_FRAMEWORKS, "framework")
|
|
94
|
+
|
|
95
|
+
# Validate Builder Existence & Support
|
|
96
|
+
if not check_builder_installed(self.builder):
|
|
97
|
+
from viperx.constants import SUPPORTED_BUILDERS
|
|
98
|
+
if self.builder not in SUPPORTED_BUILDERS:
|
|
99
|
+
console.print(f"[bold red]Error:[/bold red] Invalid builder '[bold]{self.builder}[/bold]'.")
|
|
100
|
+
console.print(f"Supported builders: [green]{', '.join(SUPPORTED_BUILDERS)}[/green]")
|
|
101
|
+
else:
|
|
102
|
+
console.print(f"[bold red]Error:[/bold red] The builder '[bold]{self.builder}[/bold]' is not installed or not in PATH.")
|
|
103
|
+
console.print(f"Please install it (e.g., `pip install {self.builder}` or `curl -LsSf https://astral.sh/uv/install.sh | sh` for uv).")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
|
|
106
|
+
except ValueError as e:
|
|
107
|
+
console.print(f"[bold red]Configuration Error:[/bold red] {e}")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
60
110
|
# Jinja Setup
|
|
61
111
|
self.env = Environment(
|
|
62
112
|
loader=PackageLoader("viperx", "templates"),
|
|
@@ -67,26 +117,10 @@ class ProjectGenerator:
|
|
|
67
117
|
if self.verbose:
|
|
68
118
|
console.print(f" [{style}]{message}[/{style}]")
|
|
69
119
|
|
|
70
|
-
def generate(self, target_dir: Optional[Path] = None):
|
|
71
|
-
"""Main generation flow using uv init."""
|
|
72
|
-
if target_dir is None:
|
|
73
|
-
target_dir = Path.cwd()
|
|
74
|
-
|
|
75
|
-
project_dir = target_dir / self.raw_name
|
|
76
|
-
|
|
77
|
-
if project_dir.exists():
|
|
78
|
-
console.print(f"[bold red]Error:[/bold red] Directory {project_dir} already exists.")
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
console.print(f"[bold green]Creating project {self.raw_name} ({self.type})...[/bold green]")
|
|
82
|
-
|
|
83
|
-
self.log(f"Target directory: {project_dir}")
|
|
84
|
-
self.log(f"Python version: {self.python_version}")
|
|
85
|
-
|
|
86
|
-
|
|
87
120
|
def generate(self, target_dir: Path, is_subpackage: bool = False):
|
|
88
121
|
"""Main generation flow using uv init."""
|
|
89
|
-
|
|
122
|
+
# STRICT DIRECTORY NAMING: Always use sanitized name
|
|
123
|
+
project_dir = target_dir / self.project_name
|
|
90
124
|
|
|
91
125
|
# 1. Scaffolding with uv init
|
|
92
126
|
try:
|
|
@@ -102,8 +136,13 @@ class ProjectGenerator:
|
|
|
102
136
|
)
|
|
103
137
|
else:
|
|
104
138
|
# Create new
|
|
139
|
+
# STRICT DIR NAMING: Use self.project_name (underscores) for directory
|
|
140
|
+
# But use self.raw_name (hyphens) for the package Name metadata if possible?
|
|
141
|
+
# uv init [NAME] creates directory NAME.
|
|
142
|
+
# If we want dir=test_classic but name=test-classic:
|
|
143
|
+
# uv init test_classic --name test-classic
|
|
105
144
|
subprocess.run(
|
|
106
|
-
["uv", "init", "--package", "--no-workspace", self.raw_name],
|
|
145
|
+
["uv", "init", "--package", "--no-workspace", self.project_name, "--name", self.raw_name],
|
|
107
146
|
check=True, cwd=target_dir, capture_output=True
|
|
108
147
|
)
|
|
109
148
|
console.print(" [blue]✓ Scaffolding created with uv init[/blue]")
|
|
@@ -112,20 +151,23 @@ class ProjectGenerator:
|
|
|
112
151
|
return
|
|
113
152
|
|
|
114
153
|
# 2. Restructure / Clean up
|
|
115
|
-
# If is_subpackage, convert to Flat Layout
|
|
154
|
+
# If is_subpackage, convert to Ultra-Flat Layout (Code at Root)
|
|
155
|
+
# Target: root/__init__.py (and siblings)
|
|
156
|
+
# Source: root/src/pkg/__init__.py (from uv init --lib)
|
|
116
157
|
if is_subpackage:
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
158
|
+
src_pkg_path = project_dir / "src" / self.project_name
|
|
159
|
+
import shutil
|
|
160
|
+
|
|
161
|
+
if src_pkg_path.exists():
|
|
162
|
+
# Move children of src/pkg to root
|
|
163
|
+
for item in src_pkg_path.iterdir():
|
|
164
|
+
shutil.move(str(item), str(project_dir))
|
|
165
|
+
|
|
166
|
+
# Cleanup src/pkg and src
|
|
167
|
+
shutil.rmtree(src_pkg_path)
|
|
168
|
+
if (project_dir / "src").exists() and not any((project_dir / "src").iterdir()):
|
|
169
|
+
shutil.rmtree(project_dir / "src")
|
|
170
|
+
self.log("Converted to Ultra-Flat Layout (Code at Root)")
|
|
129
171
|
|
|
130
172
|
# 3. Create extra directories (First, so templates have target dirs)
|
|
131
173
|
self._create_extra_dirs(project_dir, is_subpackage)
|
|
@@ -133,12 +175,26 @@ class ProjectGenerator:
|
|
|
133
175
|
# 4. Overwrite/Add Files
|
|
134
176
|
self._generate_files(project_dir, is_subpackage)
|
|
135
177
|
|
|
178
|
+
|
|
179
|
+
# Cleanup extra files for subpackages
|
|
180
|
+
if is_subpackage:
|
|
181
|
+
for f in [".gitignore", ".python-version"]:
|
|
182
|
+
if (project_dir / f).exists():
|
|
183
|
+
(project_dir / f).unlink()
|
|
184
|
+
|
|
136
185
|
# 5. Git & Final Steps
|
|
137
|
-
console.print(f"\n[bold green]✓ Project {self.raw_name} created successfully![/bold green]")
|
|
186
|
+
console.print(f"\n[bold green]✓ Project {self.raw_name} created in {self.project_name}/ successfully![/bold green]")
|
|
138
187
|
if not is_subpackage:
|
|
139
|
-
console.print(f" [dim]cd {self.
|
|
188
|
+
console.print(f" [dim]cd {self.project_name} && uv sync[/dim]")
|
|
140
189
|
|
|
141
190
|
def _create_extra_dirs(self, root: Path, is_subpackage: bool = False):
|
|
191
|
+
if is_subpackage:
|
|
192
|
+
# Ultra-Flat Layout: root IS the package root
|
|
193
|
+
pkg_root = root
|
|
194
|
+
else:
|
|
195
|
+
# Standard Layout: root / src / package_name
|
|
196
|
+
pkg_root = root / SRC_DIR / self.project_name
|
|
197
|
+
|
|
142
198
|
# Notebooks for ML/DL (Only for Root Project usually)
|
|
143
199
|
if not is_subpackage and self.type in [TYPE_ML, TYPE_DL]:
|
|
144
200
|
(root / NOTEBOOKS_DIR).mkdir(exist_ok=True)
|
|
@@ -149,11 +205,10 @@ class ProjectGenerator:
|
|
|
149
205
|
# For Flat Layout (Subpackage), tests usually go to `tests/` at root
|
|
150
206
|
# For Src Layout (Root), inside `src/pkg/tests`?
|
|
151
207
|
# User request: "create dossier tests ... que ce soit au init general ou pour sous package"
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
tests_dir =
|
|
156
|
-
tests_dir.mkdir(exist_ok=True)
|
|
208
|
+
# User request: "tests/ pour le package principal est dans src/name_package/ tout est isolé"
|
|
209
|
+
# So tests are INSIDE the package.
|
|
210
|
+
tests_dir = pkg_root / TESTS_DIR
|
|
211
|
+
tests_dir.mkdir(parents=True, exist_ok=True)
|
|
157
212
|
|
|
158
213
|
with open(tests_dir / "__init__.py", "w") as f:
|
|
159
214
|
pass
|
|
@@ -177,16 +232,21 @@ class ProjectGenerator:
|
|
|
177
232
|
"has_config": self.use_config,
|
|
178
233
|
"use_readme": self.use_readme,
|
|
179
234
|
"use_env": self.use_env,
|
|
235
|
+
"use_env": self.use_env,
|
|
180
236
|
"framework": self.framework,
|
|
237
|
+
"scripts": self.scripts,
|
|
181
238
|
}
|
|
239
|
+
# Merge dependency context overrides
|
|
240
|
+
context.update(self.dependency_context)
|
|
182
241
|
|
|
183
242
|
# pyproject.toml (Overwrite uv's basic one to add our specific deps)
|
|
243
|
+
# Even subpackages need this if they are Workspace Members (which they are in our model)
|
|
184
244
|
self._render("pyproject.toml.j2", root / "pyproject.toml", context)
|
|
185
245
|
|
|
186
246
|
# Determine Package Root
|
|
187
247
|
if is_subpackage:
|
|
188
|
-
# Flat Layout: root
|
|
189
|
-
pkg_root = root
|
|
248
|
+
# Ultra-Flat Layout: root IS the package root
|
|
249
|
+
pkg_root = root
|
|
190
250
|
else:
|
|
191
251
|
# Standard Layout: root / src / package_name
|
|
192
252
|
pkg_root = root / SRC_DIR / self.project_name
|
|
@@ -201,19 +261,33 @@ class ProjectGenerator:
|
|
|
201
261
|
self._render("__init__.py.j2", pkg_root / "__init__.py", context)
|
|
202
262
|
|
|
203
263
|
# README.md
|
|
204
|
-
|
|
205
|
-
|
|
264
|
+
# README.md
|
|
265
|
+
if not is_subpackage:
|
|
266
|
+
# Root Project: Respect use_readme
|
|
267
|
+
if self.use_readme:
|
|
268
|
+
self._render("README.md.j2", root / "README.md", context)
|
|
269
|
+
else:
|
|
270
|
+
if (root / "README.md").exists():
|
|
271
|
+
(root / "README.md").unlink()
|
|
272
|
+
self.log("Removed default README.md (requested --no-readme)")
|
|
206
273
|
else:
|
|
207
|
-
if
|
|
274
|
+
# Subpackage: Default False, but if True, generate it
|
|
275
|
+
if self.use_readme:
|
|
276
|
+
self._render("README.md.j2", root / "README.md", context)
|
|
277
|
+
elif (root / "README.md").exists():
|
|
278
|
+
# Cleanup default README from uv init if we didn't request one
|
|
208
279
|
(root / "README.md").unlink()
|
|
209
|
-
|
|
280
|
+
|
|
210
281
|
|
|
211
282
|
# LICENSE
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
283
|
+
if not is_subpackage:
|
|
284
|
+
license_text = LICENSES.get(self.license, LICENSES["MIT"])
|
|
285
|
+
license_text = license_text.format(year=datetime.now().year, author=self.author)
|
|
286
|
+
with open(root / "LICENSE", "w") as f:
|
|
287
|
+
f.write(license_text)
|
|
288
|
+
self.log(f"Generated LICENSE ({self.license})")
|
|
289
|
+
elif (root / "LICENSE").exists():
|
|
290
|
+
(root / "LICENSE").unlink()
|
|
217
291
|
|
|
218
292
|
# Config files
|
|
219
293
|
if self.use_config:
|
|
@@ -240,10 +314,12 @@ class ProjectGenerator:
|
|
|
240
314
|
self.log(f"Created .env and .env.example in {pkg_root.relative_to(root)}")
|
|
241
315
|
|
|
242
316
|
# .gitignore
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
317
|
+
# Only for Root
|
|
318
|
+
if not is_subpackage:
|
|
319
|
+
with open(root / ".gitignore", "a") as f:
|
|
320
|
+
# Add data/ to gitignore but allow .gitkeep
|
|
321
|
+
f.write("\n# ViperX specific\n.ipynb_checkpoints/\n# Isolated Env\nsrc/**/.env\n# Data (Local)\ndata/*\n!data/.gitkeep\n")
|
|
322
|
+
self.log("Updated .gitignore")
|
|
247
323
|
|
|
248
324
|
def _render(self, template_name: str, target_path: Path, context: dict):
|
|
249
325
|
template = self.env.get_template(template_name)
|
|
@@ -256,57 +332,9 @@ class ProjectGenerator:
|
|
|
256
332
|
"""Add a new package to an existing workspace."""
|
|
257
333
|
console.print(f"[bold green]Adding package {self.raw_name} to workspace...[/bold green]")
|
|
258
334
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return
|
|
263
|
-
|
|
264
|
-
# Read pyproject.toml
|
|
265
|
-
with open(pyproject_path, "r") as f:
|
|
266
|
-
content = f.read()
|
|
267
|
-
|
|
268
|
-
# Check if it's already a workspace
|
|
269
|
-
is_workspace = "[tool.uv.workspace]" in content
|
|
270
|
-
|
|
271
|
-
if not is_workspace:
|
|
272
|
-
console.print("[yellow]Upgrading project to Workspace...[/yellow]")
|
|
273
|
-
# Append workspace definition
|
|
274
|
-
with open(pyproject_path, "a") as f:
|
|
275
|
-
f.write(f"\n[tool.uv.workspace]\nmembers = [\"src/{self.raw_name}\"]\n")
|
|
276
|
-
self.log("Added [tool.uv.workspace] section")
|
|
277
|
-
else:
|
|
278
|
-
# Add member to specific list if it exists
|
|
279
|
-
# We use a simple regex approach to find 'members = [...]'
|
|
280
|
-
import re
|
|
281
|
-
members_pattern = r'members\s*=\s*\[(.*?)\]'
|
|
282
|
-
match = re.search(members_pattern, content, re.DOTALL)
|
|
283
|
-
|
|
284
|
-
if match:
|
|
285
|
-
current_members = match.group(1)
|
|
286
|
-
# Check if already present
|
|
287
|
-
if f'"{self.raw_name}"' in current_members or f"'{self.raw_name}'" in current_members:
|
|
288
|
-
self.log(f"Package {self.raw_name} is already in workspace members.")
|
|
289
|
-
else:
|
|
290
|
-
# Append new member
|
|
291
|
-
# We inject it into the list
|
|
292
|
-
self.log("Adding member to existing workspace list")
|
|
293
|
-
# Naively replace the closing bracket
|
|
294
|
-
# Better: parse, but for now robust string insertion
|
|
295
|
-
# Cleanest way without breaking formatting involves finding the last element
|
|
296
|
-
# Cleanest way without breaking formatting involves finding the last element
|
|
297
|
-
new_member = f', "src/{self.raw_name}"'
|
|
298
|
-
# Warning: This regex replace is basic. `uv` handles toml well, maybe we should just edit safely.
|
|
299
|
-
# Let's try to append to the end of the content of the list
|
|
300
|
-
new_content = re.sub(members_pattern, lambda m: f'members = [{m.group(1)}{new_member}]', content, flags=re.DOTALL)
|
|
301
|
-
with open(pyproject_path, "w") as f:
|
|
302
|
-
f.write(new_content)
|
|
303
|
-
else:
|
|
304
|
-
# Section exists but members key might be missing? Or weird formatting.
|
|
305
|
-
# Append to section?
|
|
306
|
-
# Safe fallback
|
|
307
|
-
console.print("[yellow]Warning: Could not parse members list. Adding manually at end.[/yellow]")
|
|
308
|
-
with open(pyproject_path, "a") as f:
|
|
309
|
-
f.write(f"\n# Added by viperx\n[tool.uv.workspace]\nmembers = [\"{self.raw_name}\"]\n")
|
|
335
|
+
# User Request: Do not modify root pyproject.toml to add workspace members.
|
|
336
|
+
# "uv est assez intelligent"
|
|
337
|
+
pass
|
|
310
338
|
|
|
311
339
|
# Generate the package in the root IF it doesn't exist
|
|
312
340
|
pkg_dir = workspace_root / SRC_DIR / self.raw_name
|
|
@@ -14,9 +14,14 @@ from viperx.constants import (
|
|
|
14
14
|
DL_FRAMEWORKS,
|
|
15
15
|
FRAMEWORK_PYTORCH,
|
|
16
16
|
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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,18 +33,30 @@ 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 ❤️
|
|
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
|
-
|
|
43
|
+
def version_callback(value: bool):
|
|
44
|
+
if value:
|
|
45
|
+
console.print(f"ViperX CLI Version: [bold green]{version}[/bold green]")
|
|
46
|
+
raise typer.Exit()
|
|
47
|
+
|
|
48
|
+
@app.callback(invoke_without_command=True)
|
|
39
49
|
def cli_callback(
|
|
50
|
+
ctx: typer.Context,
|
|
40
51
|
verbose: bool = typer.Option(
|
|
41
52
|
False, "-v", "--verbose",
|
|
42
53
|
help="Enable verbose logging."
|
|
54
|
+
),
|
|
55
|
+
version: bool = typer.Option(
|
|
56
|
+
None, "--version", "-V",
|
|
57
|
+
callback=version_callback,
|
|
58
|
+
is_eager=True,
|
|
59
|
+
help="Show version and exit."
|
|
43
60
|
)
|
|
44
61
|
):
|
|
45
62
|
"""
|
|
@@ -51,10 +68,21 @@ def cli_callback(
|
|
|
51
68
|
if verbose:
|
|
52
69
|
state["verbose"] = True
|
|
53
70
|
console.print("[dim]Verbose mode enabled[/dim]")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
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")
|
|
54
81
|
|
|
55
82
|
|
|
56
|
-
@
|
|
57
|
-
def
|
|
83
|
+
@config_app.callback(invoke_without_command=True)
|
|
84
|
+
def config_main(
|
|
85
|
+
ctx: typer.Context,
|
|
58
86
|
# --- Config Driven Mode ---
|
|
59
87
|
config: Path = typer.Option(
|
|
60
88
|
None, "--config", "-c",
|
|
@@ -84,14 +112,21 @@ def init(
|
|
|
84
112
|
help=f"DL Framework ({'|'.join(DL_FRAMEWORKS)}). Defaults to pytorch."
|
|
85
113
|
),
|
|
86
114
|
use_env: bool = typer.Option(True, "--env/--no-env", help="Generate .env file"),
|
|
87
|
-
use_config: bool = typer.Option(True, "--config/--no-config", help="Generate embedded config"),
|
|
115
|
+
use_config: bool = typer.Option(True, "--embed-config/--no-embed-config", help="Generate embedded config"),
|
|
88
116
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
89
117
|
):
|
|
90
118
|
"""
|
|
91
|
-
Initialize
|
|
119
|
+
**Configure & Initialize**: Apply configuration to create or update a project.
|
|
92
120
|
|
|
93
|
-
|
|
121
|
+
usage: [bold]viperx config [OPTIONS][/bold]
|
|
122
|
+
[bold]viperx config get[/bold]
|
|
94
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
|
+
|
|
95
130
|
# 1. Declarative Mode
|
|
96
131
|
if config:
|
|
97
132
|
if not config.exists():
|
|
@@ -104,6 +139,10 @@ def init(
|
|
|
104
139
|
|
|
105
140
|
# 2. Imperative Mode (Validation)
|
|
106
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".
|
|
107
146
|
console.print("[bold red]Error:[/bold red] Missing option '--name' / '-n'. Required in manual mode.")
|
|
108
147
|
raise typer.Exit(code=1)
|
|
109
148
|
|
|
@@ -113,6 +152,13 @@ def init(
|
|
|
113
152
|
|
|
114
153
|
console.print(Panel(f"Initializing [bold blue]{name}[/bold blue]", border_style="blue"))
|
|
115
154
|
|
|
155
|
+
# Check if target directory (sanitized) exists
|
|
156
|
+
from viperx.utils import sanitize_project_name
|
|
157
|
+
name_clean = sanitize_project_name(name)
|
|
158
|
+
target_dir = Path.cwd() / name_clean
|
|
159
|
+
if target_dir.exists():
|
|
160
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Directory {name_clean} already exists. Updating.")
|
|
161
|
+
|
|
116
162
|
generator = ProjectGenerator(
|
|
117
163
|
name=name,
|
|
118
164
|
description=description,
|
|
@@ -129,19 +175,15 @@ def init(
|
|
|
129
175
|
# Generate in current directory
|
|
130
176
|
generator.generate(Path.cwd())
|
|
131
177
|
|
|
132
|
-
# Config Management Group
|
|
133
|
-
config_app = typer.Typer(
|
|
134
|
-
help="Manage Declarative Configuration (viperx.yaml).",
|
|
135
|
-
no_args_is_help=True
|
|
136
|
-
)
|
|
137
|
-
app.add_typer(config_app, name="config")
|
|
138
178
|
|
|
139
|
-
|
|
140
|
-
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@config_app.command("get")
|
|
182
|
+
def config_get(
|
|
141
183
|
filename: Path = typer.Option("viperx.yaml", "--output", "-o", help="Output filename")
|
|
142
184
|
):
|
|
143
185
|
"""
|
|
144
|
-
|
|
186
|
+
Get the default configuration template (viperx.yaml).
|
|
145
187
|
Use this to start a 'Project as Code' workflow.
|
|
146
188
|
"""
|
|
147
189
|
from viperx.constants import TEMPLATES_DIR
|
|
@@ -184,8 +226,8 @@ def package_add(
|
|
|
184
226
|
help=f"DL Framework ({'|'.join(DL_FRAMEWORKS)}). Defaults to pytorch."
|
|
185
227
|
),
|
|
186
228
|
use_env: bool = typer.Option(True, "--env/--no-env", help="Generate .env file"),
|
|
187
|
-
use_config: bool = typer.Option(True, "--config/--no-config", help="Generate embedded config"),
|
|
188
|
-
use_readme: bool = typer.Option(
|
|
229
|
+
use_config: bool = typer.Option(True, "--embed-config/--no-embed-config", help="Generate embedded config"),
|
|
230
|
+
use_readme: bool = typer.Option(False, "--readme/--no-readme", help="Generate README.md"),
|
|
189
231
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging"),
|
|
190
232
|
):
|
|
191
233
|
"""
|
|
@@ -248,4 +290,18 @@ def package_update(
|
|
|
248
290
|
generator.update_package(Path.cwd())
|
|
249
291
|
|
|
250
292
|
if __name__ == "__main__":
|
|
251
|
-
|
|
293
|
+
try:
|
|
294
|
+
app()
|
|
295
|
+
except SystemExit as e:
|
|
296
|
+
if e.code != 0:
|
|
297
|
+
# On error (non-zero exit), display help as requested
|
|
298
|
+
from typer.main import get_command
|
|
299
|
+
import click
|
|
300
|
+
cli = get_command(app)
|
|
301
|
+
# Create a dummy context to render help
|
|
302
|
+
# We print it to stderr or stdout? Console prints to stdout usually.
|
|
303
|
+
# User wants it displayed immediately.
|
|
304
|
+
with click.Context(cli) as ctx:
|
|
305
|
+
console.print("\n")
|
|
306
|
+
console.print(cli.get_help(ctx))
|
|
307
|
+
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 %}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{%- if has_config %}
|
|
2
|
+
from {{ package_name }} import SETTINGS
|
|
3
|
+
{%- endif %}
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
{%- if has_config %}
|
|
7
|
+
print(f"Hi from {SETTINGS['project_name']}!")
|
|
8
|
+
{%- else %}
|
|
9
|
+
print("Hi from viperx!")
|
|
10
|
+
{%- endif %}
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
main()
|
|
@@ -11,10 +11,14 @@ authors = [
|
|
|
11
11
|
]
|
|
12
12
|
license = { text = "{{ license }}" }
|
|
13
13
|
dependencies = [
|
|
14
|
+
{%- if has_config %}
|
|
14
15
|
"pyyaml>=6.0",
|
|
16
|
+
{%- endif %}
|
|
17
|
+
{%- if has_env %}
|
|
15
18
|
"python-dotenv>=1.0.0",
|
|
19
|
+
{%- endif %}
|
|
20
|
+
{%- if is_ml_dl %}
|
|
16
21
|
"kagglehub>=0.2.0",
|
|
17
|
-
{% if project_type == 'ml' %}
|
|
18
22
|
"numpy>=1.24.0",
|
|
19
23
|
"pandas>=2.0.0",
|
|
20
24
|
"scikit-learn>=1.3.0",
|
|
@@ -22,29 +26,32 @@ dependencies = [
|
|
|
22
26
|
"seaborn>=0.12.0",
|
|
23
27
|
"requests>=2.30.0",
|
|
24
28
|
"tqdm>=4.65.0",
|
|
25
|
-
{
|
|
26
|
-
{
|
|
29
|
+
{%- endif %}
|
|
30
|
+
{%- if is_dl %}
|
|
31
|
+
{%- if 'pytorch' in frameworks %}
|
|
27
32
|
"torch>=2.0.0",
|
|
28
33
|
"torchvision>=0.15.0",
|
|
29
|
-
{
|
|
34
|
+
{%- endif %}
|
|
35
|
+
{%- if 'tensorflow' in frameworks %}
|
|
30
36
|
"tensorflow>=2.13.0",
|
|
31
37
|
# "keras>=3.0.0", # Optional, included in tf usually
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
"pandas>=2.0.0",
|
|
35
|
-
"matplotlib>=3.7.0",
|
|
36
|
-
"seaborn>=0.12.0",
|
|
37
|
-
"requests>=2.30.0",
|
|
38
|
-
"tqdm>=4.65.0",
|
|
39
|
-
{% endif %}
|
|
38
|
+
{%- endif %}
|
|
39
|
+
{%- endif %}
|
|
40
40
|
]
|
|
41
41
|
|
|
42
|
+
[project.scripts]
|
|
43
|
+
{%- for name, entry in scripts.items() %}
|
|
44
|
+
{{ name }} = "{{ entry }}"
|
|
45
|
+
{%- endfor %}
|
|
46
|
+
|
|
42
47
|
[build-system]
|
|
48
|
+
{%- if builder == 'hatch' %}
|
|
43
49
|
requires = ["hatchling"]
|
|
44
50
|
build-backend = "hatchling.build"
|
|
51
|
+
{%- else %}
|
|
52
|
+
# Default: uv native build backend
|
|
53
|
+
requires = ["uv_build>=0.9.21,<0.10.0"]
|
|
54
|
+
build-backend = "uv_build"
|
|
55
|
+
{%- endif %}
|
|
45
56
|
|
|
46
|
-
{% if use_uv %}
|
|
47
|
-
[tool.uv]
|
|
48
|
-
managed = true
|
|
49
|
-
{% endif %}
|
|
50
57
|
|
|
@@ -10,7 +10,7 @@ project:
|
|
|
10
10
|
|
|
11
11
|
# [Optional] Defaults (Inferred from git/system if omitted)
|
|
12
12
|
# description: "My robust project"
|
|
13
|
-
# author: "
|
|
13
|
+
# author: "Nameless"
|
|
14
14
|
# license: "MIT"
|
|
15
15
|
# builder: "uv"
|
|
16
16
|
|
|
@@ -36,14 +36,11 @@ workspace:
|
|
|
36
36
|
# Define workspace members (Monorepo / Utility Packages).
|
|
37
37
|
# These are minimal packages created at the workspace root (Flat Layout).
|
|
38
38
|
packages:
|
|
39
|
-
# --- Example 1:
|
|
40
|
-
# - name: "
|
|
41
|
-
# description: "
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# type: "dl"
|
|
47
|
-
# framework: "pytorch"
|
|
48
|
-
# use_env: true # Override default
|
|
39
|
+
# --- Example 1: Preprocess Package ---
|
|
40
|
+
# - name: "preprocess"
|
|
41
|
+
# description: "reprocessing utilities"
|
|
42
|
+
# use_env: false
|
|
43
|
+
# use_config: true
|
|
44
|
+
# use_tests: true
|
|
45
|
+
# use_readme: false
|
|
49
46
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import shutil
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
def check_uv_installed() -> bool:
|
|
8
|
+
"""Check if 'uv' is installed and accessible."""
|
|
9
|
+
return shutil.which("uv") is not None
|
|
10
|
+
|
|
11
|
+
def sanitize_project_name(name: str) -> str:
|
|
12
|
+
"""
|
|
13
|
+
Sanitize the project name to be a valid Python package name.
|
|
14
|
+
Replaces hyphens with underscores and removes invalid characters.
|
|
15
|
+
"""
|
|
16
|
+
# Replace - with _
|
|
17
|
+
name = name.replace("-", "_")
|
|
18
|
+
# Remove any characters that aren't alphanumerics or underscores
|
|
19
|
+
name = re.sub(r"[^a-zA-Z0-9_]", "", name)
|
|
20
|
+
# Ensure it starts with a letter or underscore
|
|
21
|
+
if not re.match(r"^[a-zA-Z_]", name):
|
|
22
|
+
name = f"_{name}"
|
|
23
|
+
return name.lower()
|
|
24
|
+
|
|
25
|
+
def validate_project_name(ctx, param, value):
|
|
26
|
+
"""
|
|
27
|
+
Typer callback to validate project name.
|
|
28
|
+
"""
|
|
29
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", value):
|
|
30
|
+
from typer import BadParameter
|
|
31
|
+
raise BadParameter("Project name must contain only letters, numbers, underscores, and hyphens.")
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
from viperx.constants import SUPPORTED_BUILDERS
|
|
35
|
+
|
|
36
|
+
def validate_choice(value: str, choices: list[str], name: str):
|
|
37
|
+
"""
|
|
38
|
+
Validate that a value is within the allowed choices.
|
|
39
|
+
Raises ValueError with a friendly message if invalid.
|
|
40
|
+
"""
|
|
41
|
+
if value not in choices:
|
|
42
|
+
raise ValueError(f"Invalid {name} '{value}'. Allowed: {', '.join(choices)}")
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
def check_builder_installed(builder: str) -> bool:
|
|
46
|
+
"""
|
|
47
|
+
Check if the specified builder is valid AND installed.
|
|
48
|
+
Uses shutil.which() for robust path detection.
|
|
49
|
+
"""
|
|
50
|
+
# 1. Validate against supported list
|
|
51
|
+
if builder not in SUPPORTED_BUILDERS:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
# 2. Check existence
|
|
55
|
+
return shutil.which(builder) is not None
|
|
56
|
+
|
|
57
|
+
def get_author_from_git() -> tuple[str, str]:
|
|
58
|
+
"""
|
|
59
|
+
Attempt to get author name and email from git config.
|
|
60
|
+
Returns (name, email) or defaults.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
# Check if git is installed first
|
|
64
|
+
if not shutil.which("git"):
|
|
65
|
+
return "Nameless", "nameless@example.com"
|
|
66
|
+
|
|
67
|
+
import git
|
|
68
|
+
# Robust way using git command wrapper
|
|
69
|
+
reader = git.Git().config
|
|
70
|
+
name = reader("--global", "--get", "user.name")
|
|
71
|
+
email = reader("--global", "--get", "user.email")
|
|
72
|
+
|
|
73
|
+
# strip newlines if any
|
|
74
|
+
return (name.strip() if name else "Nameless",
|
|
75
|
+
email.strip() if email else "nameless@example.com")
|
|
76
|
+
except Exception:
|
|
77
|
+
# Fallback if git call fails
|
|
78
|
+
return "Nameless", "nameless@example.com"
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from {{ package_name }} import hello
|
|
2
|
-
{% if has_config %}
|
|
3
|
-
from {{ package_name }} import SETTINGS
|
|
4
|
-
{% endif %}
|
|
5
|
-
|
|
6
|
-
def main():
|
|
7
|
-
hello()
|
|
8
|
-
{% if has_config %}
|
|
9
|
-
print(f"Project config loaded: {SETTINGS['project_name']}")
|
|
10
|
-
{% endif %}
|
|
11
|
-
|
|
12
|
-
if __name__ == "__main__":
|
|
13
|
-
main()
|
viperx-0.9.5/src/viperx/utils.py
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
import shutil
|
|
3
|
-
import subprocess
|
|
4
|
-
from rich.console import Console
|
|
5
|
-
|
|
6
|
-
console = Console()
|
|
7
|
-
|
|
8
|
-
def check_uv_installed() -> bool:
|
|
9
|
-
"""Check if 'uv' is installed and accessible."""
|
|
10
|
-
return shutil.which("uv") is not None
|
|
11
|
-
|
|
12
|
-
def sanitize_project_name(name: str) -> str:
|
|
13
|
-
"""
|
|
14
|
-
Sanitize the project name to be a valid Python package name.
|
|
15
|
-
Replaces hyphens with underscores and removes invalid characters.
|
|
16
|
-
"""
|
|
17
|
-
# Replace - with _
|
|
18
|
-
name = name.replace("-", "_")
|
|
19
|
-
# Remove any characters that aren't alphanumerics or underscores
|
|
20
|
-
name = re.sub(r"[^a-zA-Z0-9_]", "", name)
|
|
21
|
-
# Ensure it starts with a letter or underscore
|
|
22
|
-
if not re.match(r"^[a-zA-Z_]", name):
|
|
23
|
-
name = f"_{name}"
|
|
24
|
-
return name.lower()
|
|
25
|
-
|
|
26
|
-
def validate_project_name(ctx, param, value):
|
|
27
|
-
"""
|
|
28
|
-
Typer callback to validate project name.
|
|
29
|
-
"""
|
|
30
|
-
if not re.match(r"^[a-zA-Z0-9_-]+$", value):
|
|
31
|
-
from typer import BadParameter
|
|
32
|
-
raise BadParameter("Project name must contain only letters, numbers, underscores, and hyphens.")
|
|
33
|
-
return value
|
|
34
|
-
|
|
35
|
-
def get_author_from_git() -> tuple[str, str]:
|
|
36
|
-
"""
|
|
37
|
-
Attempt to get author name and email from git config.
|
|
38
|
-
Returns (name, email) or defaults.
|
|
39
|
-
"""
|
|
40
|
-
try:
|
|
41
|
-
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
|
|
46
|
-
except Exception:
|
|
47
|
-
return "Your Name", "your.email@example.com"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|