viperx 0.9.40__py3-none-any.whl → 0.9.50__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
@@ -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
- # 1. Root Project Handling
53
- # If we are NOT already in the project dir (checking name), we might need to create it
54
- # Or if we are running in the root of where `viperx init` is called.
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
- # Heuristic: Are we already in a folder named 'project_name'?
57
- if self.root_path.name == project_name:
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,28 +76,27 @@ 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),
72
- scripts={project_name: f"{project_name}.main:main"}, # Simple default for hydration
79
+ scripts={project_name: f"{clean_name}.main:main"},
73
80
  verbose=self.verbose
74
81
  )
75
- # generate() expects parent dir, and will operate on parent/name (which is self.root_path)
76
82
  gen.generate(self.root_path.parent)
77
83
  else:
78
84
  console.print(Panel(f"♻️ [bold blue]Syncing Project:[/bold blue] {project_name}", border_style="blue"))
79
- current_root = self.root_path
85
+
80
86
  else:
81
- # We are outside, check if it exists
87
+ # We are outside
88
+ # target_dir (clean) is already set as current_root default
89
+
82
90
  if target_dir.exists() and (target_dir / "pyproject.toml").exists():
83
- console.print(Panel(f"♻️ [bold blue]Updating Existing Project:[/bold blue] {project_name}", border_style="blue"))
84
- current_root = target_dir
91
+ console.print(Panel(f"♻️ [bold blue]Updating Existing Project:[/bold blue] {project_name} ({target_dir.name})", border_style="blue"))
85
92
  else:
86
93
  if target_dir.exists():
87
94
  console.print(Panel(f"⚠️ [bold yellow]Directory exists but not initialized. Hydrating:[/bold yellow] {project_name}", border_style="yellow"))
95
+
88
96
  # Prepare Scripts & Dependency Context
89
97
  packages = workspace_conf.get("packages", [])
90
98
 
91
99
  # --- Aggregate Global Dependencies ---
92
- # Start with Root Settings
93
- # Root is always present (ProjectGenerator uses these)
94
100
  root_use_config = settings_conf.get("use_config", True)
95
101
  root_use_env = settings_conf.get("use_env", False)
96
102
  root_type = settings_conf.get("type", TYPE_CLASSIC)
@@ -102,22 +108,19 @@ class ConfigEngine:
102
108
  glob_is_dl = root_type == TYPE_DL
103
109
  glob_frameworks = {root_framework} if glob_is_dl else set()
104
110
 
105
- project_scripts = {project_name: f"{project_name}.main:main"}
111
+ project_scripts = {project_name: f"{clean_name}.main:main"} # Use clean mapping
106
112
 
107
113
  for pkg in packages:
108
114
  # Scripts
109
115
  pkg_name = pkg.get("name")
110
- from viperx.utils import sanitize_project_name
111
116
  pkg_name_clean = sanitize_project_name(pkg_name)
112
- # CLI Command = Raw Name (e.g. test-classic) -> sanitized module path (test_classic.main:main)
113
117
  project_scripts[pkg_name] = f"{pkg_name_clean}.main:main"
114
118
 
115
119
  # Dependency Aggregation
116
- # Inherit defaults if not defined in pkg
117
120
  p_config = pkg.get("use_config", settings_conf.get("use_config", True))
118
121
  p_env = pkg.get("use_env", settings_conf.get("use_env", False))
119
122
  p_type = pkg.get("type", TYPE_CLASSIC)
120
- p_framework = pkg.get("framework", FRAMEWORK_PYTORCH) # Defaults to pytorch if implicit
123
+ p_framework = pkg.get("framework", FRAMEWORK_PYTORCH)
121
124
 
122
125
  if p_config: glob_has_config = True
123
126
  if p_env: glob_has_env = True
@@ -134,9 +137,8 @@ class ConfigEngine:
134
137
  "frameworks": list(glob_frameworks)
135
138
  }
136
139
 
137
- # Create Root (or Hydrate)
138
140
  gen = ProjectGenerator(
139
- name=project_name,
141
+ name=project_name, # Raw name
140
142
  description=project_conf.get("description", ""),
141
143
  type=settings_conf.get("type", TYPE_CLASSIC),
142
144
  author=project_conf.get("author", None),
@@ -151,7 +153,14 @@ class ConfigEngine:
151
153
  verbose=self.verbose
152
154
  )
153
155
  gen.generate(self.root_path)
154
- current_root = target_dir
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}")
155
164
 
156
165
  # 2. Copy Config to Root (Source of Truth)
157
166
  # Only if we aren't reading the one already there
@@ -164,7 +173,7 @@ class ConfigEngine:
164
173
  # 3. Handle Workspace Packages
165
174
  packages = workspace_conf.get("packages", [])
166
175
  if packages:
167
- console.print(f"\n📦 [bold]Processing {len(packages)} workspace packages...[/bold]")
176
+ console.print(f"\\n📦 [bold]Processing {len(packages)} workspace packages...[/bold]")
168
177
 
169
178
  for pkg in packages:
170
179
  pkg_name = pkg.get("name")
@@ -187,4 +196,4 @@ class ConfigEngine:
187
196
  # Check if package seems to exist (ProjectGenerator handles upgrade logic too)
188
197
  pkg_gen.add_to_workspace(current_root)
189
198
 
190
- console.print(Panel(f"✨ [bold green]Configuration Applied Successfully![/bold green]\nProject is up to date with {self.config_path.name}", border_style="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"))
viperx/core.py CHANGED
@@ -117,27 +117,10 @@ class ProjectGenerator:
117
117
  if self.verbose:
118
118
  console.print(f" [{style}]{message}[/{style}]")
119
119
 
120
- def generate(self, target_dir: Optional[Path] = None):
121
- """Main generation flow using uv init."""
122
- if target_dir is None:
123
- target_dir = Path.cwd()
124
-
125
- project_dir = target_dir / self.raw_name
126
-
127
- if project_dir.exists():
128
- console.print(f"[bold red]Error:[/bold red] Directory {project_dir} already exists.")
129
- return
130
-
131
- console.print(f"[bold green]Creating project {self.raw_name} ({self.type})...[/bold green]")
132
-
133
- self.log(f"Target directory: {project_dir}")
134
- self.log(f"System Python: {self.system_python_version}")
135
- self.log(f"Project Python: {self.python_version}")
136
-
137
-
138
120
  def generate(self, target_dir: Path, is_subpackage: bool = False):
139
121
  """Main generation flow using uv init."""
140
- project_dir = target_dir / self.raw_name
122
+ # STRICT DIRECTORY NAMING: Always use sanitized name
123
+ project_dir = target_dir / self.project_name
141
124
 
142
125
  # 1. Scaffolding with uv init
143
126
  try:
@@ -200,7 +183,7 @@ class ProjectGenerator:
200
183
  (project_dir / f).unlink()
201
184
 
202
185
  # 5. Git & Final Steps
203
- 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]")
204
187
  if not is_subpackage:
205
188
  console.print(f" [dim]cd {self.project_name} && uv sync[/dim]")
206
189
 
viperx/main.py CHANGED
@@ -152,6 +152,13 @@ def config_main(
152
152
 
153
153
  console.print(Panel(f"Initializing [bold blue]{name}[/bold blue]", border_style="blue"))
154
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
+
155
162
  generator = ProjectGenerator(
156
163
  name=name,
157
164
  description=description,
@@ -7,7 +7,7 @@
7
7
  ## 🧐 Philosophy & Architecture
8
8
 
9
9
  Values transparency and standard tooling over "black box" magic.
10
- This project was generated with [ViperX](https://github.com/kpihx/viperx).sh/uv/)**, the extremely fast Python package and project manager written in Rust.
10
+ This project was generated with [ViperX](https://github.com/kpihx/viperx), using **[uv](https://docs.astral.sh/uv/)**, the extremely fast Python package and project manager written in Rust.
11
11
 
12
12
  ### Why `uv`?
13
13
  Unlike traditional workflows (pip, poetry, venv mixing), `uv` manages the **entire lifecycle**:
@@ -18,17 +18,25 @@ Unlike traditional workflows (pip, poetry, venv mixing), `uv` manages the **enti
18
18
  {% endif %}
19
19
  - **Environment**: Virtual environments are managed internally, you just run `uv run`.
20
20
 
21
+ {% if has_config or use_env %}
21
22
  ### ⚙️ Configuration
22
23
 
23
- - **Config**: `src/{{ package_name }}/config.yaml` (Loaded automatically)
24
24
  {% if has_config %}
25
+ - **Config**: `src/{{ package_name }}/config.yaml` (Loaded automatically)
26
+ {% endif %}
27
+ {% if use_env %}
25
28
  - **Environment**: `src/{{ package_name }}/.env` (Isolated variables)
26
29
  {% endif %}
27
30
 
28
- Edit `config.yaml` to change parameters. The project uses a **Config-in-Package** architecture:
31
+ The project uses a **Config-in-Package** architecture:
32
+ {% if has_config %}
29
33
  1. `config.yaml` is inside the package.
30
34
  2. `config.py` loads it safely (even in production wheels).
35
+ {% endif %}
36
+ {% if use_env %}
31
37
  3. `.env` is isolated within the package source.
38
+ {% endif %}
39
+ {% endif %}
32
40
 
33
41
  ---
34
42
 
@@ -42,8 +50,7 @@ No need to install Python or create venvs manually.
42
50
  ### Installation
43
51
 
44
52
  ```bash
45
- # Clone the repo
46
- git clone https://github.com/{{ author_name | lower | replace(" ", "") }}/{{ project_name }}.git
53
+ # Ensure you are in the project directory
47
54
  cd {{ project_name }}
48
55
 
49
56
  # Sync dependencies (creates .venv and installs python if needed)
@@ -52,15 +59,13 @@ uv sync
52
59
 
53
60
  ## 🧑‍💻 Usage
54
61
 
55
- The entry point depends on your project type.
56
-
57
62
  ### For Developers (Code)
58
63
 
59
64
  To run the package entry point or scripts:
60
65
 
61
66
  ```bash
62
67
  # Run the main package
63
- uv run {{ package_name }}
68
+ uv run {{ project_name }}
64
69
 
65
70
  # Or run a specific script
66
71
  uv run python src/{{ package_name }}/main.py
@@ -77,29 +82,36 @@ uv run jupyter notebook
77
82
 
78
83
  - Open `notebooks/Base.ipynb`.
79
84
  - Note how it imports `config` from the package.
80
- {% endif %}
81
85
 
82
86
  ### ☁️ Cloud (Colab / Kaggle)
83
87
 
84
88
  You can use the code and config from this repository directly in cloud environments without cloning.
85
89
 
86
- **Step 1: Install directly from GitHub**
90
+ **Step 1: Install directly from Git**
87
91
  ```python
88
- !pip install git+https://github.com/{{ author_name | lower | replace(" ", "") }}/{{ project_name }}.git
92
+ !pip install git+https://your-git-provider.com/username/repo.git
89
93
  ```
90
94
 
91
95
  **Step 2: Use the unified config**
92
96
  ```python
97
+ {% if project_type == 'classic' %}
98
+ from {{ package_name }} import SETTINGS
99
+ {% else %}
93
100
  from {{ package_name }} import get_dataset_path, SETTINGS
94
101
  import kagglehub as kh
102
+ {% endif %}
95
103
 
96
104
  # Transparency: You can inspect what was loaded
97
- print(f"Loaded config for: {SETTINGS['project_name']}")
105
+ print(f"Loaded config for: {SETTINGS.get('project_name', 'Unknown')}")
98
106
 
107
+ {% if project_type != 'classic' %}
99
108
  # Download datasets defined in config.yaml
100
109
  # The key 'titanic' maps to 'heptapod/titanic' in the yaml
101
- path = kh.dataset_download(SETTINGS['datasets']['titanic'])
110
+ if 'datasets' in SETTINGS and 'titanic' in SETTINGS['datasets']:
111
+ path = kh.dataset_download(SETTINGS['datasets']['titanic'])
112
+ {% endif %}
102
113
  ```
114
+ {% endif %}
103
115
 
104
116
  ## 🔧 Internal Structure
105
117
 
@@ -109,14 +121,22 @@ path = kh.dataset_download(SETTINGS['datasets']['titanic'])
109
121
  ├── uv.lock # Exact versions lockfile
110
122
  ├── .python-version # Pinned Python version
111
123
  ├── src/
112
- │ └── {{ package_name }}/
124
+ {% for name, entry in scripts.items() %}
125
+ {% set pkg_dir = entry.split('.')[0] %}
126
+ │ └── {{ pkg_dir }}/
113
127
  │ ├── __init__.py
128
+ {% if has_config %}
114
129
  │ ├── config.yaml # EDIT THIS for project settings
115
130
  │ ├── config.py # Code that loads the yaml above
131
+ {% endif %}
132
+ {% if use_tests %}
116
133
  │ └── tests/ # Unit tests
134
+ {% endif %}
135
+ {% endfor %}
117
136
  {% if project_type in ['ml', 'dl'] %}
118
137
  └── notebooks/ # Experimentation (Jupyter)
119
138
  {% endif %}
120
139
  ```
121
140
 
122
141
 
142
+
@@ -1,5 +1,8 @@
1
1
  {% if has_config %}
2
- from .config import SETTINGS, get_config, get_dataset_path
2
+ from .config import SETTINGS, get_config
3
+ {% if project_type != 'classic' %}
4
+ from .config import get_dataset_path
5
+ {% endif %}
3
6
  {% endif %}
4
7
 
5
8
 
@@ -1,13 +1,13 @@
1
- {% if has_config %}
1
+ {%- if has_config %}
2
2
  from {{ package_name }} import SETTINGS
3
- {% endif %}
3
+ {%- endif %}
4
4
 
5
5
  def main():
6
- {% if has_config %}
6
+ {%- if has_config %}
7
7
  print(f"Hi from {SETTINGS['project_name']}!")
8
- {% else %}
8
+ {%- else %}
9
9
  print("Hi from viperx!")
10
- {% endif %}
10
+ {%- endif %}
11
11
 
12
12
  if __name__ == "__main__":
13
13
  main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: viperx
3
- Version: 0.9.40
3
+ Version: 0.9.50
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=A5u1qUaWlgvXQReRHtF05--spPsd90XgT81zZYR2rBM,9908
2
+ viperx/config_engine.py,sha256=zeCV5toxk4mfafbw61UafBK_WGBNgU1Qf93-dTzwyP8,10034
3
3
  viperx/constants.py,sha256=Mv5tFjFxe3tD4VDCEsXiwfiV7biTlzl45ALkHmiK33M,887
4
- viperx/core.py,sha256=yEACR7U-z5h7_fuCKyy4uljGPMe4S-F1HO5H_XeqHNg,20308
4
+ viperx/core.py,sha256=QDgSsYGzphUmzUa3PV-nY6Dl6unfhzeywIMZsAnDzjM,19722
5
5
  viperx/licenses.py,sha256=TPsG1aqNAjtOsvuD6i3SiC5rUK9f3DslPHQkKiV7vxs,12482
6
- viperx/main.py,sha256=N0pqLnZoWr00XjH1kS5dxbDKEqXvazsVspqLQ_-fF8k,10663
6
+ viperx/main.py,sha256=-k1_vGk-3DCayF1_gGdFthFR_FPWWb2VZz-VBB8y6qc,10996
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
- viperx/templates/README.md.j2,sha256=TJNUwTcKnjALrP0MOSs8UBx8zNhI8erRHrc2-8nuMgQ,3445
11
- viperx/templates/__init__.py.j2,sha256=j-i1NRnKdvk-EgjijSyx-rfOsxWOVSXCyHY2-JEwMDM,93
10
+ viperx/templates/README.md.j2,sha256=s5HBkx6PEZTv9gsBlmYRJnH4jAuiWJ6qSyaoFVCR_PU,3792
11
+ viperx/templates/__init__.py.j2,sha256=NNzSYZTYzbohNf6zL-u3_WgmiUspuejoS7nLCafB7w8,159
12
12
  viperx/templates/config.py.j2,sha256=ExMbopPQPntmkNgbv9uKooGDjv82gh_XhyQmjJ8MiQg,1456
13
13
  viperx/templates/config.yaml.j2,sha256=EyeUfIIGvNIyMSHayh4i_0XVxIeu4Wdf2I6uAgfmSf4,552
14
14
  viperx/templates/data_loader.py.j2,sha256=hehewwvBAYJM-acKn6_T4cU5EvUPX7wHAurAQ3z3ET4,3696
15
- viperx/templates/main.py.j2,sha256=cpXjtc9MS6CnGAZ0RK1jaB3xBqh4r5Q4iwscBVB2pcY,258
15
+ viperx/templates/main.py.j2,sha256=v4rukrrLXYgyMt5We02_QU3eNSrKFh-uafVReh-2RKw,263
16
16
  viperx/templates/pyproject.toml.j2,sha256=NGrgIJM_pzjbQ3OAgsT_y3mX2n8UzdAg_mo9AERhGmU,1297
17
17
  viperx/templates/viperx_config.yaml.j2,sha256=z-teOb1hTYmoVdXBQqgr6kK5fQGkMpI6KsTzbeF0m4Q,1408
18
18
  viperx/utils.py,sha256=c9FKL9eRsszNvinQd3zjvA2bRMVhEQI5F26aLN_PPZE,2578
19
- viperx-0.9.40.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
20
- viperx-0.9.40.dist-info/entry_points.txt,sha256=Is38BrTuf6unQCLgDE990Rjd9HyGldBSw4Uzy_nMePY,44
21
- viperx-0.9.40.dist-info/METADATA,sha256=h7IGeSpqB1LJ__SFKtWl_PGaJ1tVoZCARLzynArtGEk,6782
22
- viperx-0.9.40.dist-info/RECORD,,
19
+ viperx-0.9.50.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
20
+ viperx-0.9.50.dist-info/entry_points.txt,sha256=Is38BrTuf6unQCLgDE990Rjd9HyGldBSw4Uzy_nMePY,44
21
+ viperx-0.9.50.dist-info/METADATA,sha256=t-3Q_mNDXVdcZCLJVSF3E4JPc5oETU9INZwgTfP6pBc,6782
22
+ viperx-0.9.50.dist-info/RECORD,,