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 +33 -24
- viperx/core.py +3 -20
- viperx/main.py +7 -0
- viperx/templates/README.md.j2 +34 -14
- viperx/templates/__init__.py.j2 +4 -1
- viperx/templates/main.py.j2 +5 -5
- {viperx-0.9.40.dist-info → viperx-0.9.50.dist-info}/METADATA +1 -1
- {viperx-0.9.40.dist-info → viperx-0.9.50.dist-info}/RECORD +10 -10
- {viperx-0.9.40.dist-info → viperx-0.9.50.dist-info}/WHEEL +0 -0
- {viperx-0.9.40.dist-info → viperx-0.9.50.dist-info}/entry_points.txt +0 -0
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
|
-
#
|
|
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,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"{
|
|
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
|
-
|
|
85
|
+
|
|
80
86
|
else:
|
|
81
|
-
# We are outside
|
|
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"{
|
|
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)
|
|
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
|
-
|
|
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"
|
|
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]
|
|
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
|
-
|
|
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,
|
viperx/templates/README.md.j2
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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 {{
|
|
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
|
|
90
|
+
**Step 1: Install directly from Git**
|
|
87
91
|
```python
|
|
88
|
-
!pip install git+https://
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
|
viperx/templates/__init__.py.j2
CHANGED
viperx/templates/main.py.j2
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
{
|
|
1
|
+
{%- if has_config %}
|
|
2
2
|
from {{ package_name }} import SETTINGS
|
|
3
|
-
{
|
|
3
|
+
{%- endif %}
|
|
4
4
|
|
|
5
5
|
def main():
|
|
6
|
-
{
|
|
6
|
+
{%- if has_config %}
|
|
7
7
|
print(f"Hi from {SETTINGS['project_name']}!")
|
|
8
|
-
{
|
|
8
|
+
{%- else %}
|
|
9
9
|
print("Hi from viperx!")
|
|
10
|
-
{
|
|
10
|
+
{%- endif %}
|
|
11
11
|
|
|
12
12
|
if __name__ == "__main__":
|
|
13
13
|
main()
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
viperx/__init__.py,sha256=mRrD-SK9PtwBhqgLzJrvefdViNsXCyBGknzFNsIou1I,51
|
|
2
|
-
viperx/config_engine.py,sha256=
|
|
2
|
+
viperx/config_engine.py,sha256=zeCV5toxk4mfafbw61UafBK_WGBNgU1Qf93-dTzwyP8,10034
|
|
3
3
|
viperx/constants.py,sha256=Mv5tFjFxe3tD4VDCEsXiwfiV7biTlzl45ALkHmiK33M,887
|
|
4
|
-
viperx/core.py,sha256=
|
|
4
|
+
viperx/core.py,sha256=QDgSsYGzphUmzUa3PV-nY6Dl6unfhzeywIMZsAnDzjM,19722
|
|
5
5
|
viperx/licenses.py,sha256=TPsG1aqNAjtOsvuD6i3SiC5rUK9f3DslPHQkKiV7vxs,12482
|
|
6
|
-
viperx/main.py,sha256
|
|
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=
|
|
11
|
-
viperx/templates/__init__.py.j2,sha256=
|
|
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=
|
|
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.
|
|
20
|
-
viperx-0.9.
|
|
21
|
-
viperx-0.9.
|
|
22
|
-
viperx-0.9.
|
|
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,,
|
|
File without changes
|
|
File without changes
|