uvpg 1.0.1__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.
Potentially problematic release.
This version of uvpg might be problematic. Click here for more details.
- uvpg/__init__.py +0 -0
- uvpg/main.py +283 -0
- uvpg/templates.py +687 -0
- uvpg-1.0.1.dist-info/METADATA +188 -0
- uvpg-1.0.1.dist-info/RECORD +8 -0
- uvpg-1.0.1.dist-info/WHEEL +4 -0
- uvpg-1.0.1.dist-info/entry_points.txt +2 -0
- uvpg-1.0.1.dist-info/licenses/LICENSE +21 -0
uvpg/__init__.py
ADDED
|
File without changes
|
uvpg/main.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
uvpg - UV Project generator based on monorepo template with uv workspaces.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
uvpg my-project
|
|
7
|
+
uvpg my-project --package my-lib
|
|
8
|
+
uvpg my-project -p lib1 -p lib2
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import platform
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from datetime import UTC, datetime
|
|
17
|
+
|
|
18
|
+
# ============================================================================
|
|
19
|
+
# Configuration
|
|
20
|
+
# ============================================================================
|
|
21
|
+
from importlib.metadata import version
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from uvpg.templates import (
|
|
25
|
+
TEMPLATE_CLEAN_SCRIPT,
|
|
26
|
+
TEMPLATE_COMPOSE,
|
|
27
|
+
TEMPLATE_DOCKERFILE,
|
|
28
|
+
TEMPLATE_DOCKERIGNORE,
|
|
29
|
+
TEMPLATE_GITIGNORE,
|
|
30
|
+
TEMPLATE_LICENSE_MIT,
|
|
31
|
+
TEMPLATE_MAIN_APP,
|
|
32
|
+
TEMPLATE_PACKAGE_MAIN,
|
|
33
|
+
TEMPLATE_PYPROJECT_PACKAGE,
|
|
34
|
+
TEMPLATE_PYPROJECT_ROOT,
|
|
35
|
+
TEMPLATE_README,
|
|
36
|
+
TEMPLATE_VSCODE_EXTENSIONS,
|
|
37
|
+
TEMPLATE_VSCODE_SETTINGS,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
VERSION: str = version(distribution_name="uvpg")
|
|
41
|
+
AUTHORS_NAME = "John Doe"
|
|
42
|
+
AUTHORS_EMAIL = "john@example.com"
|
|
43
|
+
PYTHON_VERSION_DEFAULT = f"{sys.version_info.major}.{sys.version_info.minor}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ============================================================================
|
|
47
|
+
# Functions
|
|
48
|
+
# ============================================================================
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_command(cmd: list[str], cwd: Path | None = None) -> bool:
|
|
52
|
+
"""Execute command and return success status."""
|
|
53
|
+
try:
|
|
54
|
+
subprocess.run(cmd, cwd=cwd, check=True, capture_output=True) # noqa: S603
|
|
55
|
+
except subprocess.CalledProcessError as e:
|
|
56
|
+
print(f"Error running {' '.join(cmd)}: {e.stderr.decode()}")
|
|
57
|
+
return False
|
|
58
|
+
else:
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def create_package(root: Path, name: str, python_version: str = PYTHON_VERSION_DEFAULT) -> bool:
|
|
63
|
+
"""Create a package inside packages/."""
|
|
64
|
+
package_name = name.replace("-", "_")
|
|
65
|
+
package_dir = root / "packages" / package_name
|
|
66
|
+
|
|
67
|
+
if package_dir.exists():
|
|
68
|
+
print(f"Package '{name}' already exists!")
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
print(f"Creating package: {name}")
|
|
72
|
+
|
|
73
|
+
# Package structure
|
|
74
|
+
src_dir = package_dir / "src" / package_name
|
|
75
|
+
src_dir.mkdir(parents=True)
|
|
76
|
+
(package_dir / "tests").mkdir()
|
|
77
|
+
|
|
78
|
+
# Files
|
|
79
|
+
(package_dir / "pyproject.toml").write_text(
|
|
80
|
+
TEMPLATE_PYPROJECT_PACKAGE.format(
|
|
81
|
+
name=name,
|
|
82
|
+
package_name=package_name,
|
|
83
|
+
python_version=python_version,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
(src_dir / "__init__.py").touch()
|
|
87
|
+
(src_dir / "main.py").write_text(TEMPLATE_PACKAGE_MAIN.format(name=name))
|
|
88
|
+
(package_dir / "tests" / "__init__.py").touch()
|
|
89
|
+
|
|
90
|
+
# Register package in root pyproject.toml
|
|
91
|
+
register_package_in_root(root, package_name)
|
|
92
|
+
|
|
93
|
+
return True
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def register_package_in_root(root: Path, package_name: str) -> None:
|
|
97
|
+
"""Register a package in root pyproject.toml dependencies and sources."""
|
|
98
|
+
pyproject_path = root / "pyproject.toml"
|
|
99
|
+
if not pyproject_path.exists():
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
content = pyproject_path.read_text()
|
|
103
|
+
|
|
104
|
+
# Add to dependencies = []
|
|
105
|
+
if "dependencies = []" in content:
|
|
106
|
+
content = content.replace("dependencies = []", f'dependencies = ["{package_name}"]')
|
|
107
|
+
elif "dependencies = [" in content:
|
|
108
|
+
# Find the dependencies line and add the package
|
|
109
|
+
pattern = r"dependencies = \[([^\]]*)\]"
|
|
110
|
+
match = re.search(pattern, content)
|
|
111
|
+
if match:
|
|
112
|
+
current_deps = match.group(1).strip()
|
|
113
|
+
if current_deps and f'"{package_name}"' not in current_deps:
|
|
114
|
+
new_deps = f'{current_deps}, "{package_name}"'
|
|
115
|
+
content = re.sub(pattern, f"dependencies = [{new_deps}]", content)
|
|
116
|
+
elif not current_deps:
|
|
117
|
+
content = re.sub(pattern, f'dependencies = ["{package_name}"]', content)
|
|
118
|
+
|
|
119
|
+
# Add to [tool.uv.sources]
|
|
120
|
+
source_entry = f"{package_name} = {{ workspace = true }}"
|
|
121
|
+
if "[tool.uv.sources]" in content and f"{package_name} = " not in content:
|
|
122
|
+
content = content.replace("[tool.uv.sources]", f"[tool.uv.sources]\n{source_entry}")
|
|
123
|
+
|
|
124
|
+
pyproject_path.write_text(content)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def create_project(
|
|
128
|
+
name: str,
|
|
129
|
+
packages: list[str] | None = None,
|
|
130
|
+
python_version: str = PYTHON_VERSION_DEFAULT,
|
|
131
|
+
authors_name: str = AUTHORS_NAME,
|
|
132
|
+
authors_email: str = AUTHORS_EMAIL,
|
|
133
|
+
) -> bool:
|
|
134
|
+
"""Create complete project with monorepo structure."""
|
|
135
|
+
root = Path(name).resolve()
|
|
136
|
+
|
|
137
|
+
# If directory exists and has pyproject.toml, only add packages
|
|
138
|
+
if root.exists() and (root / "pyproject.toml").exists():
|
|
139
|
+
if packages:
|
|
140
|
+
print(f"Adding packages to existing project: {root}")
|
|
141
|
+
for pkg in packages:
|
|
142
|
+
create_package(root, pkg, python_version)
|
|
143
|
+
print("\nRun 'uv sync' to update dependencies.")
|
|
144
|
+
return True
|
|
145
|
+
print(f"Project already exists at {root}")
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
print(f"Creating project: {name} (Python {python_version})")
|
|
149
|
+
|
|
150
|
+
# Create directory structure
|
|
151
|
+
root.mkdir(exist_ok=True)
|
|
152
|
+
(root / "src" / "app").mkdir(parents=True)
|
|
153
|
+
(root / "packages").mkdir()
|
|
154
|
+
(root / "scripts").mkdir()
|
|
155
|
+
(root / "tests").mkdir()
|
|
156
|
+
(root / ".vscode").mkdir()
|
|
157
|
+
|
|
158
|
+
# Create root files
|
|
159
|
+
py_target = f"py{python_version.replace('.', '')}"
|
|
160
|
+
(root / "pyproject.toml").write_text(
|
|
161
|
+
TEMPLATE_PYPROJECT_ROOT.format(
|
|
162
|
+
name=name,
|
|
163
|
+
python_version=python_version,
|
|
164
|
+
py_target=py_target,
|
|
165
|
+
authors_name=authors_name,
|
|
166
|
+
authors_email=authors_email,
|
|
167
|
+
)
|
|
168
|
+
)
|
|
169
|
+
(root / "README.md").write_text(TEMPLATE_README.format(name=name))
|
|
170
|
+
(root / "LICENSE").write_text(
|
|
171
|
+
TEMPLATE_LICENSE_MIT.format(year=datetime.now(tz=UTC).year, authors_name=authors_name)
|
|
172
|
+
)
|
|
173
|
+
(root / ".gitignore").write_text(TEMPLATE_GITIGNORE)
|
|
174
|
+
(root / ".python-version").write_text(f"{python_version}\n")
|
|
175
|
+
(root / "Dockerfile").write_text(TEMPLATE_DOCKERFILE.format(python_version=python_version))
|
|
176
|
+
(root / ".dockerignore").write_text(TEMPLATE_DOCKERIGNORE)
|
|
177
|
+
(root / "compose.yaml").write_text(TEMPLATE_COMPOSE)
|
|
178
|
+
|
|
179
|
+
# Create VSCode config (detect OS for python path)
|
|
180
|
+
if platform.system() == "Windows":
|
|
181
|
+
python_interpreter_path = "${workspaceFolder}\\.venv\\Scripts\\python.exe"
|
|
182
|
+
else:
|
|
183
|
+
python_interpreter_path = "${workspaceFolder}/.venv/bin/python"
|
|
184
|
+
|
|
185
|
+
(root / ".vscode" / "settings.json").write_text(
|
|
186
|
+
TEMPLATE_VSCODE_SETTINGS.format(python_interpreter_path=python_interpreter_path)
|
|
187
|
+
)
|
|
188
|
+
(root / ".vscode" / "extensions.json").write_text(TEMPLATE_VSCODE_EXTENSIONS)
|
|
189
|
+
|
|
190
|
+
# Create main app
|
|
191
|
+
(root / "src" / "app" / "__init__.py").touch()
|
|
192
|
+
(root / "src" / "app" / "main.py").write_text(TEMPLATE_MAIN_APP)
|
|
193
|
+
(root / "tests" / "__init__.py").touch()
|
|
194
|
+
|
|
195
|
+
# Cleanup script
|
|
196
|
+
clean_script = root / "scripts" / "project_clean.sh"
|
|
197
|
+
clean_script.write_text(TEMPLATE_CLEAN_SCRIPT)
|
|
198
|
+
clean_script.chmod(0o755)
|
|
199
|
+
|
|
200
|
+
# Create initial packages
|
|
201
|
+
if packages:
|
|
202
|
+
for pkg in packages:
|
|
203
|
+
create_package(root, pkg, python_version)
|
|
204
|
+
|
|
205
|
+
# Initialize with uv
|
|
206
|
+
print("Initializing with uv...")
|
|
207
|
+
if not run_command(["uv", "sync"], cwd=root):
|
|
208
|
+
print("Warning: failed to run 'uv sync'")
|
|
209
|
+
|
|
210
|
+
# Initialize git
|
|
211
|
+
print("Initializing git...")
|
|
212
|
+
run_command(["git", "init"], cwd=root)
|
|
213
|
+
|
|
214
|
+
print(f"\n✓ Project created at: {root}")
|
|
215
|
+
print("\nNext steps:")
|
|
216
|
+
print(f" cd {name}")
|
|
217
|
+
print(" uv sync")
|
|
218
|
+
print(" uv run python src/app/main.py")
|
|
219
|
+
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def main() -> int:
|
|
224
|
+
parser = argparse.ArgumentParser(
|
|
225
|
+
prog="uvpg",
|
|
226
|
+
description="uvpg - UV Project generator for Python monorepos with uv workspaces.",
|
|
227
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
228
|
+
epilog=f"""\
|
|
229
|
+
Examples:
|
|
230
|
+
%(prog)s my-project # Create new project (Python {PYTHON_VERSION_DEFAULT})
|
|
231
|
+
%(prog)s my-project --python {PYTHON_VERSION_DEFAULT}
|
|
232
|
+
%(prog)s my-project -p utils -p core # With initial packages
|
|
233
|
+
%(prog)s my-project --author "John Doe" --email "john@example.com"
|
|
234
|
+
%(prog)s . -p new-lib # Add package to current project
|
|
235
|
+
""",
|
|
236
|
+
)
|
|
237
|
+
parser.add_argument(
|
|
238
|
+
"-v",
|
|
239
|
+
"--version",
|
|
240
|
+
action="version",
|
|
241
|
+
version=f"%(prog)s {VERSION}",
|
|
242
|
+
)
|
|
243
|
+
parser.add_argument("name", help="Project name or '.' for current directory")
|
|
244
|
+
parser.add_argument(
|
|
245
|
+
"-p",
|
|
246
|
+
"--package",
|
|
247
|
+
action="append",
|
|
248
|
+
dest="packages",
|
|
249
|
+
metavar="NAME",
|
|
250
|
+
help="Package name to create (can be repeated)",
|
|
251
|
+
)
|
|
252
|
+
parser.add_argument(
|
|
253
|
+
"--python",
|
|
254
|
+
default=PYTHON_VERSION_DEFAULT,
|
|
255
|
+
metavar="VERSION",
|
|
256
|
+
help=f"Python version (default: {PYTHON_VERSION_DEFAULT})",
|
|
257
|
+
)
|
|
258
|
+
parser.add_argument(
|
|
259
|
+
"--author",
|
|
260
|
+
default=AUTHORS_NAME,
|
|
261
|
+
metavar="NAME",
|
|
262
|
+
help=f"Author name (default: {AUTHORS_NAME})",
|
|
263
|
+
)
|
|
264
|
+
parser.add_argument(
|
|
265
|
+
"--email",
|
|
266
|
+
default=AUTHORS_EMAIL,
|
|
267
|
+
metavar="EMAIL",
|
|
268
|
+
help=f"Author email (default: {AUTHORS_EMAIL})",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
args = parser.parse_args()
|
|
272
|
+
|
|
273
|
+
# Use current directory if '.'
|
|
274
|
+
name = args.name
|
|
275
|
+
if name == ".":
|
|
276
|
+
name = str(Path.cwd())
|
|
277
|
+
|
|
278
|
+
success = create_project(name, args.packages, args.python, args.author, args.email)
|
|
279
|
+
return 0 if success else 1
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
sys.exit(main())
|
uvpg/templates.py
ADDED
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"""Templates for uvpg project generator."""
|
|
2
|
+
|
|
3
|
+
TEMPLATE_PYPROJECT_ROOT = """\
|
|
4
|
+
# ============================
|
|
5
|
+
# UV Instructions
|
|
6
|
+
# ============================
|
|
7
|
+
# # On macOS and Linux.
|
|
8
|
+
# curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
9
|
+
# # On Windows (PowerShell).
|
|
10
|
+
# powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
11
|
+
# uv --version
|
|
12
|
+
# # Initialized project
|
|
13
|
+
# uv init
|
|
14
|
+
# # Initialized project with python version
|
|
15
|
+
# uv init --python {python_version}
|
|
16
|
+
# # Update uv to the latest version
|
|
17
|
+
# uv self update
|
|
18
|
+
# # Resolve and install dependencies
|
|
19
|
+
# uv sync
|
|
20
|
+
# # Upgrade all packages in the workspace
|
|
21
|
+
# uv sync --upgrade
|
|
22
|
+
# # Cache directory
|
|
23
|
+
# uv cache dir
|
|
24
|
+
# # Install python versions
|
|
25
|
+
# uv python install {python_version}
|
|
26
|
+
# # Set project python version
|
|
27
|
+
# uv python pin {python_version}
|
|
28
|
+
|
|
29
|
+
# ============================
|
|
30
|
+
# Project Metadata
|
|
31
|
+
# ============================
|
|
32
|
+
[project]
|
|
33
|
+
name = "{name}"
|
|
34
|
+
version = "1.0.0"
|
|
35
|
+
description = ""
|
|
36
|
+
readme = "README.md"
|
|
37
|
+
license = "MIT"
|
|
38
|
+
authors = [{{ name = "{authors_name}", email = "{authors_email}" }}]
|
|
39
|
+
requires-python = ">={python_version}"
|
|
40
|
+
dependencies = ["uvicorn", "fastapi"]
|
|
41
|
+
|
|
42
|
+
# ============================
|
|
43
|
+
# Dependency Groups
|
|
44
|
+
# dependencies: "ruff", "ty", "watchfiles", "pytest", "pytest-cov"
|
|
45
|
+
# ============================
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = ["ruff", "ty", "watchfiles"]
|
|
48
|
+
|
|
49
|
+
# # ============================
|
|
50
|
+
# # UV Configuration
|
|
51
|
+
# # ============================
|
|
52
|
+
# [tool.uv]
|
|
53
|
+
# link-mode = "copy"
|
|
54
|
+
|
|
55
|
+
# ============================
|
|
56
|
+
# Workspace Configuration
|
|
57
|
+
# https://docs.astral.sh/uv/concepts/projects/workspaces/#workspace-layouts
|
|
58
|
+
# ============================
|
|
59
|
+
[tool.uv.workspace]
|
|
60
|
+
members = ["packages/*"]
|
|
61
|
+
|
|
62
|
+
[tool.uv.sources]
|
|
63
|
+
|
|
64
|
+
# ============================
|
|
65
|
+
# Scripts Entry Points
|
|
66
|
+
# ============================
|
|
67
|
+
[project.scripts]
|
|
68
|
+
app = "app.main:main"
|
|
69
|
+
|
|
70
|
+
# ============================
|
|
71
|
+
# Build System Configuration
|
|
72
|
+
# ============================
|
|
73
|
+
[build-system]
|
|
74
|
+
requires = ["hatchling"]
|
|
75
|
+
build-backend = "hatchling.build"
|
|
76
|
+
|
|
77
|
+
[tool.hatch.build.targets.wheel]
|
|
78
|
+
packages = ["src/app"]
|
|
79
|
+
|
|
80
|
+
# ============================
|
|
81
|
+
# Linting Configuration (Ruff)
|
|
82
|
+
# uv add ruff --dev
|
|
83
|
+
# ruff --version
|
|
84
|
+
# uv sync --upgrade-package ruff
|
|
85
|
+
# ============================
|
|
86
|
+
[tool.ruff]
|
|
87
|
+
line-length = 100
|
|
88
|
+
target-version = "{py_target}"
|
|
89
|
+
fix = true
|
|
90
|
+
show-fixes = true
|
|
91
|
+
indent-width = 4
|
|
92
|
+
exclude = ["venv", ".venv", "env", ".env", "node_modules", "__pycache__"]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint]
|
|
95
|
+
select = ["ALL"]
|
|
96
|
+
ignore = [
|
|
97
|
+
"T201", # Checks for print statements,
|
|
98
|
+
"COM812", # Checks for the absence of trailing commas.
|
|
99
|
+
"INP001", # Checks for packages that are missing an __init__.py file.
|
|
100
|
+
"D", # All pydocstyle (D)
|
|
101
|
+
"ANN401", # Checks that function arguments are annotated with a more specific type than Any.
|
|
102
|
+
"ERA001", # Checks for commented-out Python code.
|
|
103
|
+
"A004", # Shadowing Python Builtin
|
|
104
|
+
"EXE001", # Shebang is present but file is not executable.
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.ruff.lint.per-file-ignores]
|
|
108
|
+
"tests/**/*.py" = ["ANN201", "S101", "ANN001"]
|
|
109
|
+
"packages/**/tests/*.py" = ["ANN201", "S101", "ANN001"]
|
|
110
|
+
|
|
111
|
+
# ============================
|
|
112
|
+
# Typing Configuration (Ty)
|
|
113
|
+
# uv add ty --dev
|
|
114
|
+
# ty --version
|
|
115
|
+
# uv sync --upgrade-package ty
|
|
116
|
+
# ============================
|
|
117
|
+
[tool.ty.rules]
|
|
118
|
+
possibly-unresolved-reference = "warn"
|
|
119
|
+
# division-by-zero = "ignore"
|
|
120
|
+
|
|
121
|
+
# # ============================
|
|
122
|
+
# # Testing Configuration (pytest)
|
|
123
|
+
# # uv add pytest --dev
|
|
124
|
+
# # pytest --version
|
|
125
|
+
# # uv sync --upgrade-package pytest
|
|
126
|
+
# # ============================
|
|
127
|
+
# [tool.pytest.ini_options]
|
|
128
|
+
# addopts = "-vs --color=yes --tb=short --cov=packages --cov=src --cov-report=term-missing"
|
|
129
|
+
# testpaths = ["packages", "tests"]
|
|
130
|
+
|
|
131
|
+
# # ============================
|
|
132
|
+
# # Coverage Configuration (pytest-cov)
|
|
133
|
+
# # uv add pytest-cov --dev
|
|
134
|
+
# # pytest --version
|
|
135
|
+
# # uv sync --upgrade-package pytest-cov
|
|
136
|
+
# # ============================
|
|
137
|
+
# [tool.coverage.run]
|
|
138
|
+
# source = ["packages", "src"]
|
|
139
|
+
# omit = ["*/__pycache__/*", "*/tests/*"]
|
|
140
|
+
|
|
141
|
+
# [tool.coverage.report]
|
|
142
|
+
# exclude_lines = [
|
|
143
|
+
# "pragma: no cover", # Ignore pragma no cover ex.: def foo(): # pragma: no cover
|
|
144
|
+
# "if __name__ == .__main__.:", # Ignore if __name__ == "__main__" blocks
|
|
145
|
+
# ]
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
TEMPLATE_PYPROJECT_PACKAGE = """\
|
|
149
|
+
# ============================
|
|
150
|
+
# Project Metadata
|
|
151
|
+
# ============================
|
|
152
|
+
[project]
|
|
153
|
+
name = "{name}"
|
|
154
|
+
version = "1.0.0"
|
|
155
|
+
description = ""
|
|
156
|
+
requires-python = ">={python_version}"
|
|
157
|
+
dependencies = []
|
|
158
|
+
|
|
159
|
+
# # ============================
|
|
160
|
+
# # Scripts Entry Points
|
|
161
|
+
# # ============================
|
|
162
|
+
# [project.scripts]
|
|
163
|
+
# {package_name} = "{package_name}.main:hello"
|
|
164
|
+
|
|
165
|
+
# ============================
|
|
166
|
+
# Build System Configuration
|
|
167
|
+
# ============================
|
|
168
|
+
[build-system]
|
|
169
|
+
requires = ["hatchling"]
|
|
170
|
+
build-backend = "hatchling.build"
|
|
171
|
+
|
|
172
|
+
[tool.hatch.build.targets.wheel]
|
|
173
|
+
packages = ["src/{package_name}"]
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
TEMPLATE_README = """\
|
|
177
|
+
# {name}
|
|
178
|
+
|
|
179
|
+
## Structure
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
{name}/
|
|
183
|
+
├── src/app/ # FastAPI application
|
|
184
|
+
├── packages/ # Internal libraries (uv workspace)
|
|
185
|
+
├── scripts/ # Utility scripts
|
|
186
|
+
├── tests/ # Test files
|
|
187
|
+
├── Dockerfile # Multi-stage Docker build
|
|
188
|
+
└── compose.yaml # Docker Compose config
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Getting Started
|
|
192
|
+
|
|
193
|
+
### Local Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Install dependencies
|
|
197
|
+
uv sync
|
|
198
|
+
|
|
199
|
+
# Run application
|
|
200
|
+
uv run app
|
|
201
|
+
|
|
202
|
+
# Or run directly
|
|
203
|
+
uv run python src/app/main.py
|
|
204
|
+
|
|
205
|
+
# Run with auto-reload
|
|
206
|
+
uv run watchfiles 'python src/app/main.py'
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Docker
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# Build and run
|
|
213
|
+
docker compose up --build
|
|
214
|
+
|
|
215
|
+
# Run with watch mode (auto-reload on file changes)
|
|
216
|
+
docker compose up --watch
|
|
217
|
+
|
|
218
|
+
# Stop
|
|
219
|
+
docker compose down
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## API
|
|
223
|
+
|
|
224
|
+
Once running, access:
|
|
225
|
+
|
|
226
|
+
- **API:** http://localhost:8000
|
|
227
|
+
- **Docs:** http://localhost:8000/docs
|
|
228
|
+
- **ReDoc:** http://localhost:8000/redoc
|
|
229
|
+
|
|
230
|
+
## Development Commands
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Run tests
|
|
234
|
+
uv run pytest
|
|
235
|
+
|
|
236
|
+
# Lint
|
|
237
|
+
uv run ruff check .
|
|
238
|
+
|
|
239
|
+
# Type check
|
|
240
|
+
uv run ty check
|
|
241
|
+
|
|
242
|
+
# Format
|
|
243
|
+
uv run ruff format .
|
|
244
|
+
|
|
245
|
+
# Clean project
|
|
246
|
+
bash scripts/project_clean.sh
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Add new package
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
uvpg . --package package-name
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Build
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
uv build
|
|
259
|
+
```
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
TEMPLATE_MAIN_APP = """\
|
|
263
|
+
\"\"\"Main application.\"\"\"
|
|
264
|
+
|
|
265
|
+
from datetime import UTC, datetime
|
|
266
|
+
|
|
267
|
+
import uvicorn
|
|
268
|
+
from fastapi import FastAPI
|
|
269
|
+
|
|
270
|
+
app = FastAPI()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@app.get("/")
|
|
274
|
+
async def home() -> dict[str, str | int]:
|
|
275
|
+
return {"message": "Hello, world!", "now": datetime.now(tz=UTC).isoformat()}
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def main() -> None:
|
|
279
|
+
uvicorn.run(app, host="0.0.0.0", port=8000) # noqa: S104
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if __name__ == "__main__":
|
|
283
|
+
main()
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
TEMPLATE_PACKAGE_MAIN = """\
|
|
287
|
+
\"\"\"{name} main module.\"\"\"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def hello() -> str:
|
|
291
|
+
return "Hello from {name}!"
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
TEMPLATE_CLEAN_SCRIPT = """\
|
|
295
|
+
#!/bin/bash
|
|
296
|
+
# Clean temporary files and caches
|
|
297
|
+
|
|
298
|
+
set -e
|
|
299
|
+
|
|
300
|
+
echo "Cleaning project..."
|
|
301
|
+
|
|
302
|
+
# Python caches
|
|
303
|
+
find . -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null || true
|
|
304
|
+
find . -type d -name '*.egg-info' -exec rm -rf {} + 2>/dev/null || true
|
|
305
|
+
find . -type f -name '*.pyc' -delete 2>/dev/null || true
|
|
306
|
+
find . -type d -name '.pytest_cache' -exec rm -rf {} + 2>/dev/null || true
|
|
307
|
+
find . -type d -name '.ruff_cache' -exec rm -rf {} + 2>/dev/null || true
|
|
308
|
+
find . -type f -name '*.Identifier' -delete 2>/dev/null || true
|
|
309
|
+
|
|
310
|
+
# Build artifacts
|
|
311
|
+
find . -type d -name "dist" -exec rm -rf {} + 2>/dev/null || true
|
|
312
|
+
find . -type d -name "build" -exec rm -rf {} + 2>/dev/null || true
|
|
313
|
+
|
|
314
|
+
echo "Cleanup complete!"
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
TEMPLATE_LICENSE_MIT = """\
|
|
318
|
+
MIT License
|
|
319
|
+
|
|
320
|
+
Copyright (c) {year} {authors_name}
|
|
321
|
+
|
|
322
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
323
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
324
|
+
in the Software without restriction, including without limitation the rights
|
|
325
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
326
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
327
|
+
furnished to do so, subject to the following conditions:
|
|
328
|
+
|
|
329
|
+
The above copyright notice and this permission notice shall be included in all
|
|
330
|
+
copies or substantial portions of the Software.
|
|
331
|
+
|
|
332
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
333
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
334
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
335
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
336
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
337
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
338
|
+
SOFTWARE.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
TEMPLATE_GITIGNORE = """\
|
|
342
|
+
# Python
|
|
343
|
+
__pycache__/
|
|
344
|
+
*.py[cod]
|
|
345
|
+
*$py.class
|
|
346
|
+
*.egg-info/
|
|
347
|
+
dist/
|
|
348
|
+
build/
|
|
349
|
+
.eggs/
|
|
350
|
+
|
|
351
|
+
# Virtual environments
|
|
352
|
+
.env
|
|
353
|
+
.envrc
|
|
354
|
+
.venv
|
|
355
|
+
env/
|
|
356
|
+
venv/
|
|
357
|
+
ENV/
|
|
358
|
+
env.bak/
|
|
359
|
+
venv.bak/
|
|
360
|
+
|
|
361
|
+
# uv
|
|
362
|
+
.python-version
|
|
363
|
+
|
|
364
|
+
# Testing
|
|
365
|
+
htmlcov/
|
|
366
|
+
.tox/
|
|
367
|
+
.nox/
|
|
368
|
+
.coverage
|
|
369
|
+
.coverage.*
|
|
370
|
+
.cache
|
|
371
|
+
nosetests.xml
|
|
372
|
+
coverage.xml
|
|
373
|
+
*.cover
|
|
374
|
+
*.py.cover
|
|
375
|
+
.hypothesis/
|
|
376
|
+
.pytest_cache/
|
|
377
|
+
cover
|
|
378
|
+
|
|
379
|
+
# Ruff
|
|
380
|
+
.ruff_cache/
|
|
381
|
+
|
|
382
|
+
# IDEs
|
|
383
|
+
.idea/
|
|
384
|
+
*.swp
|
|
385
|
+
*.swo
|
|
386
|
+
|
|
387
|
+
# OS
|
|
388
|
+
.DS_Store
|
|
389
|
+
Thumbs.db
|
|
390
|
+
*.Identifier
|
|
391
|
+
"""
|
|
392
|
+
|
|
393
|
+
TEMPLATE_VSCODE_SETTINGS = """\
|
|
394
|
+
{{
|
|
395
|
+
// VsCode general settings
|
|
396
|
+
"editor.fontSize": 14,
|
|
397
|
+
"window.zoomLevel": 0,
|
|
398
|
+
"explorer.compactFolders": false,
|
|
399
|
+
// Action Buttons settings
|
|
400
|
+
"actionButtons": {{
|
|
401
|
+
"defaultColor": "Lime",
|
|
402
|
+
"reloadButton": "$(refresh)",
|
|
403
|
+
"commands": [
|
|
404
|
+
{{
|
|
405
|
+
"name": "$(sync)",
|
|
406
|
+
"tooltip": "Reload Windows",
|
|
407
|
+
"useVsCodeApi": true,
|
|
408
|
+
"command": "workbench.action.reloadWindow"
|
|
409
|
+
}},
|
|
410
|
+
{{
|
|
411
|
+
"name": "$(trash)",
|
|
412
|
+
"tooltip": "Clean Project",
|
|
413
|
+
"singleInstance": true,
|
|
414
|
+
"cwd": "${{workspaceFolder}}",
|
|
415
|
+
"command": "bash scripts/project_clean.sh"
|
|
416
|
+
}},
|
|
417
|
+
{{
|
|
418
|
+
"name": "$(beaker)",
|
|
419
|
+
"tooltip": "Run Tests",
|
|
420
|
+
"singleInstance": true,
|
|
421
|
+
"cwd": "${{workspaceFolder}}",
|
|
422
|
+
"command": "uv run pytest"
|
|
423
|
+
}},
|
|
424
|
+
{{
|
|
425
|
+
"name": "$(terminal)",
|
|
426
|
+
"tooltip": "Open New Terminal",
|
|
427
|
+
"useVsCodeApi": true,
|
|
428
|
+
"command": "workbench.action.terminal.new"
|
|
429
|
+
}},
|
|
430
|
+
{{
|
|
431
|
+
"name": "$(versions)",
|
|
432
|
+
"tooltip": "Python Version",
|
|
433
|
+
"singleInstance": true,
|
|
434
|
+
"cwd": "${{workspaceFolder}}",
|
|
435
|
+
"command": "uv run python --version"
|
|
436
|
+
}},
|
|
437
|
+
{{
|
|
438
|
+
"name": "$(package)",
|
|
439
|
+
"tooltip": "UV Sync",
|
|
440
|
+
"singleInstance": true,
|
|
441
|
+
"cwd": "${{workspaceFolder}}",
|
|
442
|
+
"command": "uv sync"
|
|
443
|
+
}},
|
|
444
|
+
{{
|
|
445
|
+
"name": "$(build)",
|
|
446
|
+
"tooltip": "UV Build",
|
|
447
|
+
"singleInstance": true,
|
|
448
|
+
"cwd": "${{workspaceFolder}}",
|
|
449
|
+
"command": "uv build"
|
|
450
|
+
}},
|
|
451
|
+
{{
|
|
452
|
+
"name": "$(run)",
|
|
453
|
+
"tooltip": "UV Run",
|
|
454
|
+
"singleInstance": true,
|
|
455
|
+
"cwd": "${{workspaceFolder}}",
|
|
456
|
+
"command": "uv run src/app/main.py"
|
|
457
|
+
}},
|
|
458
|
+
{{
|
|
459
|
+
"name": "$(debug-alt)",
|
|
460
|
+
"tooltip": "Run Watch Files",
|
|
461
|
+
"singleInstance": true,
|
|
462
|
+
"cwd": "${{workspaceFolder}}",
|
|
463
|
+
"command": "uv run watchfiles 'python src/app/main.py'"
|
|
464
|
+
}}
|
|
465
|
+
]
|
|
466
|
+
}},
|
|
467
|
+
// Code Runner settings
|
|
468
|
+
"code-runner.clearPreviousOutput": true,
|
|
469
|
+
"code-runner.ignoreSelection": true,
|
|
470
|
+
"code-runner.saveFileBeforeRun": true,
|
|
471
|
+
"code-runner.runInTerminal": true,
|
|
472
|
+
"code-runner.preserveFocus": false,
|
|
473
|
+
"code-runner.executorMap": {{
|
|
474
|
+
"python": "clear ; python -u"
|
|
475
|
+
}},
|
|
476
|
+
// Python settings
|
|
477
|
+
"[python]": {{
|
|
478
|
+
"editor.formatOnSave": true,
|
|
479
|
+
"editor.tabSize": 4,
|
|
480
|
+
"editor.insertSpaces": true,
|
|
481
|
+
"editor.codeActionsOnSave": {{
|
|
482
|
+
"source.fixAll": "explicit",
|
|
483
|
+
"source.organizeImports": "explicit"
|
|
484
|
+
}},
|
|
485
|
+
"editor.defaultFormatter": "charliermarsh.ruff"
|
|
486
|
+
}},
|
|
487
|
+
"python.defaultInterpreterPath": "{python_interpreter_path}",
|
|
488
|
+
"python.analysis.autoImportCompletions": true,
|
|
489
|
+
"python.terminal.activateEnvInCurrentTerminal": true,
|
|
490
|
+
"python.terminal.activateEnvironment": true,
|
|
491
|
+
"python.languageServer": "None",
|
|
492
|
+
"python.venvPath": ".venv",
|
|
493
|
+
// Ty settings (https://docs.astral.sh/ty/reference/editor-settings)
|
|
494
|
+
"ty.disableLanguageServices": false,
|
|
495
|
+
"ty.diagnosticMode": "workspace"
|
|
496
|
+
}}
|
|
497
|
+
"""
|
|
498
|
+
|
|
499
|
+
TEMPLATE_VSCODE_EXTENSIONS = """\
|
|
500
|
+
{
|
|
501
|
+
// Search for extensions in the Marketplace: @recommended
|
|
502
|
+
"recommendations": [
|
|
503
|
+
"dracula-theme.theme-dracula",
|
|
504
|
+
"pkief.material-icon-theme",
|
|
505
|
+
"seunlanlege.action-buttons",
|
|
506
|
+
"mhutchie.git-graph",
|
|
507
|
+
"ms-python.python",
|
|
508
|
+
"formulahendry.code-runner",
|
|
509
|
+
"tamasfe.even-better-toml",
|
|
510
|
+
"charliermarsh.ruff",
|
|
511
|
+
"astral-sh.ty",
|
|
512
|
+
"tal7aouy.rainbow-bracket",
|
|
513
|
+
"ms-vscode-remote.remote-wsl"
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
TEMPLATE_DOCKERFILE = """\
|
|
519
|
+
FROM debian:trixie-slim AS builder
|
|
520
|
+
|
|
521
|
+
RUN apt-get update \\
|
|
522
|
+
&& apt-get install -y --no-install-recommends build-essential \\
|
|
523
|
+
&& apt-get clean \\
|
|
524
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
525
|
+
|
|
526
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
|
527
|
+
|
|
528
|
+
ENV UV_COMPILE_BYTECODE=1 \\
|
|
529
|
+
UV_LINK_MODE=copy \\
|
|
530
|
+
UV_PYTHON_PREFERENCE=only-managed \\
|
|
531
|
+
UV_NO_DEV=1 \\
|
|
532
|
+
UV_PYTHON_INSTALL_DIR=/python
|
|
533
|
+
|
|
534
|
+
RUN uv python install {python_version}
|
|
535
|
+
|
|
536
|
+
WORKDIR /app
|
|
537
|
+
|
|
538
|
+
RUN --mount=type=cache,target=/root/.cache/uv \\
|
|
539
|
+
--mount=type=bind,source=uv.lock,target=uv.lock \\
|
|
540
|
+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \\
|
|
541
|
+
--mount=type=bind,source=packages,target=packages \\
|
|
542
|
+
uv sync --frozen --no-install-project
|
|
543
|
+
|
|
544
|
+
COPY . /app
|
|
545
|
+
|
|
546
|
+
RUN --mount=type=cache,target=/root/.cache/uv \\
|
|
547
|
+
uv sync --frozen
|
|
548
|
+
|
|
549
|
+
################################################################################
|
|
550
|
+
FROM debian:trixie-slim AS runtime
|
|
551
|
+
|
|
552
|
+
ARG UID=1000
|
|
553
|
+
ARG GID=1000
|
|
554
|
+
ARG USERNAME=python
|
|
555
|
+
|
|
556
|
+
ENV PYTHONUNBUFFERED=1 \\
|
|
557
|
+
PATH="/app/.venv/bin:${{PATH}}"
|
|
558
|
+
|
|
559
|
+
RUN groupadd --gid ${{GID}} ${{USERNAME}} \\
|
|
560
|
+
&& useradd --uid ${{UID}} --gid ${{USERNAME}} --shell /bin/bash --create-home ${{USERNAME}}
|
|
561
|
+
|
|
562
|
+
COPY --from=builder --chown=${{UID}}:${{GID}} /python /python
|
|
563
|
+
COPY --from=builder --chown=${{UID}}:${{GID}} /app /app
|
|
564
|
+
|
|
565
|
+
USER ${{USERNAME}}
|
|
566
|
+
|
|
567
|
+
WORKDIR /app
|
|
568
|
+
|
|
569
|
+
EXPOSE 8000
|
|
570
|
+
|
|
571
|
+
CMD ["app"]
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
TEMPLATE_DOCKERIGNORE = """\
|
|
575
|
+
# ADDED
|
|
576
|
+
tests/
|
|
577
|
+
scripts/
|
|
578
|
+
docs/
|
|
579
|
+
Dockerfile*
|
|
580
|
+
*compose*.yaml
|
|
581
|
+
*compose*.yml
|
|
582
|
+
.python-version
|
|
583
|
+
model_cache/
|
|
584
|
+
TEMP.md
|
|
585
|
+
TMP.md
|
|
586
|
+
temp/
|
|
587
|
+
LICENSE
|
|
588
|
+
.ruff_cache/
|
|
589
|
+
# Git
|
|
590
|
+
.git
|
|
591
|
+
.gitignore
|
|
592
|
+
.gitattributes
|
|
593
|
+
# CI
|
|
594
|
+
.codeclimate.yml
|
|
595
|
+
.travis.yml
|
|
596
|
+
.taskcluster.yml
|
|
597
|
+
# Docker
|
|
598
|
+
docker-compose.yml
|
|
599
|
+
Dockerfile
|
|
600
|
+
.docker
|
|
601
|
+
.dockerignore
|
|
602
|
+
# Byte-compiled / optimized / DLL files
|
|
603
|
+
**/__pycache__/
|
|
604
|
+
**/*.py[cod]
|
|
605
|
+
# C extensions
|
|
606
|
+
*.so
|
|
607
|
+
# Distribution / packaging
|
|
608
|
+
.Python
|
|
609
|
+
env/
|
|
610
|
+
build/
|
|
611
|
+
develop-eggs/
|
|
612
|
+
dist/
|
|
613
|
+
downloads/
|
|
614
|
+
eggs/
|
|
615
|
+
lib/
|
|
616
|
+
lib64/
|
|
617
|
+
parts/
|
|
618
|
+
sdist/
|
|
619
|
+
var/
|
|
620
|
+
*.egg-info/
|
|
621
|
+
.installed.cfg
|
|
622
|
+
*.egg
|
|
623
|
+
# PyInstaller
|
|
624
|
+
# Usually these files are written by a python script from a template
|
|
625
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
626
|
+
*.manifest
|
|
627
|
+
*.spec
|
|
628
|
+
# Installer logs
|
|
629
|
+
pip-log.txt
|
|
630
|
+
pip-delete-this-directory.txt
|
|
631
|
+
# Unit test / coverage reports
|
|
632
|
+
htmlcov/
|
|
633
|
+
.tox/
|
|
634
|
+
.coverage
|
|
635
|
+
.cache
|
|
636
|
+
nosetests.xml
|
|
637
|
+
coverage.xml
|
|
638
|
+
# Translations
|
|
639
|
+
*.mo
|
|
640
|
+
*.pot
|
|
641
|
+
# Django stuff:
|
|
642
|
+
*.log
|
|
643
|
+
# Sphinx documentation
|
|
644
|
+
docs/_build/
|
|
645
|
+
# PyBuilder
|
|
646
|
+
target/
|
|
647
|
+
# Virtual environment
|
|
648
|
+
.env
|
|
649
|
+
.venv/
|
|
650
|
+
venv/
|
|
651
|
+
# PyCharm
|
|
652
|
+
.idea
|
|
653
|
+
# Python mode for VIM
|
|
654
|
+
.ropeproject
|
|
655
|
+
**/.ropeproject
|
|
656
|
+
# Vim swap files
|
|
657
|
+
**/*.swp
|
|
658
|
+
# VS Code
|
|
659
|
+
.vscode/
|
|
660
|
+
"""
|
|
661
|
+
|
|
662
|
+
TEMPLATE_COMPOSE = """\
|
|
663
|
+
services:
|
|
664
|
+
app:
|
|
665
|
+
pull_policy: never
|
|
666
|
+
image: app
|
|
667
|
+
container_name: app
|
|
668
|
+
restart: unless-stopped
|
|
669
|
+
build:
|
|
670
|
+
context: .
|
|
671
|
+
dockerfile: Dockerfile
|
|
672
|
+
target: runtime
|
|
673
|
+
ports:
|
|
674
|
+
- 8000:8000
|
|
675
|
+
develop:
|
|
676
|
+
watch:
|
|
677
|
+
- action: sync+restart # this depends on the project
|
|
678
|
+
path: .
|
|
679
|
+
target: /app
|
|
680
|
+
|
|
681
|
+
ignore:
|
|
682
|
+
- .venv/
|
|
683
|
+
- __pycache__/
|
|
684
|
+
|
|
685
|
+
- action: rebuild
|
|
686
|
+
path: ./uv.lock
|
|
687
|
+
"""
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uvpg
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: UV Project Generator - Create Python monorepos with workspaces.
|
|
5
|
+
Author-email: Lucas Maziero <lucas.mazie.ro@hotmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# uvpg - UV Project Generator
|
|
12
|
+
|
|
13
|
+
[](https://pypi.org/project/uvpg/)
|
|
14
|
+
[](https://github.com/lucasmaziero/uvpg/blob/main/LICENSE)
|
|
15
|
+
[](https://github.com/lucasmaziero/uvpg/actions/workflows/release.yml)
|
|
16
|
+
|
|
17
|
+
A CLI tool to scaffold Python monorepo projects using [uv](https://docs.astral.sh/uv/) workspaces.
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- 🚀 Creates monorepo structure with uv workspaces
|
|
22
|
+
- 📦 Adds internal packages with automatic dependency registration
|
|
23
|
+
- 🐍 Configurable Python version
|
|
24
|
+
- 👤 Configurable author name and email
|
|
25
|
+
- 📄 MIT License auto-generated
|
|
26
|
+
- 🔧 Pre-configured tools: Ruff, Ty, pytest, coverage
|
|
27
|
+
- 💻 VSCode settings with recommended extensions
|
|
28
|
+
- 🖥️ Auto-detects OS for correct Python interpreter path
|
|
29
|
+
- 🐳 Docker support with multi-stage build and compose
|
|
30
|
+
- ⚡ FastAPI + Uvicorn ready
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Install from PyPI
|
|
36
|
+
pip install uvpg
|
|
37
|
+
|
|
38
|
+
# Or install from PyPI with uv
|
|
39
|
+
uv tool install uvpg
|
|
40
|
+
|
|
41
|
+
# Or clone the repository
|
|
42
|
+
git clone https://github.com/lucasmaziero/uvpg.git
|
|
43
|
+
cd uvpg
|
|
44
|
+
|
|
45
|
+
# Install with uv
|
|
46
|
+
uv sync
|
|
47
|
+
uv build
|
|
48
|
+
uv tool install dist/uvpg-*.whl
|
|
49
|
+
|
|
50
|
+
# Or install with pip
|
|
51
|
+
pip install dist/uvpg-*.whl --break-system-packages
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Uninstall
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Via uv
|
|
58
|
+
uv tool uninstall uvpg
|
|
59
|
+
|
|
60
|
+
# Via pip
|
|
61
|
+
pip uninstall uvpg
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### Create a new project
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Basic project
|
|
70
|
+
uvpg my-project
|
|
71
|
+
|
|
72
|
+
# With specific Python version
|
|
73
|
+
uvpg my-project --python 3.14
|
|
74
|
+
|
|
75
|
+
# With author information
|
|
76
|
+
uvpg my-project --author "John Doe" --email "john@example.com"
|
|
77
|
+
|
|
78
|
+
# With initial packages
|
|
79
|
+
uvpg my-project -p core -p utils -p api
|
|
80
|
+
|
|
81
|
+
# Combining options
|
|
82
|
+
uvpg my-project --python 3.14 --author "John Doe" --email "john@example.com" -p core
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Add packages to existing project
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd my-project
|
|
89
|
+
uvpg . -p new-package
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Show version
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uvpg --version
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Show help
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uvpg --help
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Generated Structure
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
my-project/
|
|
108
|
+
├── .vscode/
|
|
109
|
+
│ ├── settings.json # VSCode settings (Ruff, Ty, Action Buttons)
|
|
110
|
+
│ └── extensions.json # Recommended extensions
|
|
111
|
+
├── packages/
|
|
112
|
+
│ └── core/
|
|
113
|
+
│ ├── pyproject.toml
|
|
114
|
+
│ ├── src/
|
|
115
|
+
│ │ └── core/
|
|
116
|
+
│ │ ├── __init__.py
|
|
117
|
+
│ │ └── main.py
|
|
118
|
+
│ └── tests/
|
|
119
|
+
├── scripts/
|
|
120
|
+
│ └── project_clean.sh # Cleanup script
|
|
121
|
+
├── src/
|
|
122
|
+
│ └── app/
|
|
123
|
+
│ ├── __init__.py
|
|
124
|
+
│ └── main.py # FastAPI application
|
|
125
|
+
├── tests/
|
|
126
|
+
├── .dockerignore
|
|
127
|
+
├── .gitignore
|
|
128
|
+
├── .python-version
|
|
129
|
+
├── compose.yaml # Docker Compose config
|
|
130
|
+
├── Dockerfile # Multi-stage Docker build
|
|
131
|
+
├── LICENSE # MIT License
|
|
132
|
+
├── pyproject.toml # Root config with workspace
|
|
133
|
+
├── README.md
|
|
134
|
+
└── uv.lock
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
The generated `pyproject.toml` includes:
|
|
140
|
+
|
|
141
|
+
- **FastAPI + Uvicorn** - Web framework and ASGI server
|
|
142
|
+
- **Ruff** - Linting with `select = ["ALL"]`
|
|
143
|
+
- **Ty** - Type checking
|
|
144
|
+
- **pytest** - Testing (commented, ready to enable)
|
|
145
|
+
- **coverage** - Code coverage (commented, ready to enable)
|
|
146
|
+
- **uv workspaces** - Monorepo package management
|
|
147
|
+
- **hatchling** - Build system
|
|
148
|
+
|
|
149
|
+
## Docker
|
|
150
|
+
|
|
151
|
+
Run with Docker Compose:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Build and run
|
|
155
|
+
docker compose up --build
|
|
156
|
+
|
|
157
|
+
# Run with watch mode (auto-reload)
|
|
158
|
+
docker compose up --watch
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Requirements
|
|
162
|
+
|
|
163
|
+
- Python >= 3.12
|
|
164
|
+
- [uv](https://docs.astral.sh/uv/) package manager
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# Clone
|
|
170
|
+
git clone https://github.com/lucasmaziero/uvpg.git
|
|
171
|
+
cd uvpg
|
|
172
|
+
|
|
173
|
+
# Install dependencies
|
|
174
|
+
uv sync
|
|
175
|
+
|
|
176
|
+
# Run locally
|
|
177
|
+
uv run uvpg --help
|
|
178
|
+
|
|
179
|
+
# Run tests
|
|
180
|
+
uv run pytest
|
|
181
|
+
|
|
182
|
+
# Build
|
|
183
|
+
uv build
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT License - See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
uvpg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
uvpg/main.py,sha256=doSBwNONLCQNItximr0MrKkkgTTBMEGm8OO92R_LJjY,9325
|
|
3
|
+
uvpg/templates.py,sha256=7oOcalfQAwIpFTg_RBQkWd9PwI9xZH4GdZ2hNw6-LPg,15887
|
|
4
|
+
uvpg-1.0.1.dist-info/METADATA,sha256=BhTk_NEOJkoeC0vMUDzD4-ftRhGQJZZKlX9fDy_umwE,4191
|
|
5
|
+
uvpg-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
uvpg-1.0.1.dist-info/entry_points.txt,sha256=A2qYLAP0vHidOtba62Y8i665RHYt8sC9xMd2aeSwoB0,40
|
|
7
|
+
uvpg-1.0.1.dist-info/licenses/LICENSE,sha256=m-3xgK4dm033H8KWdXDGwqZb3nutJAR3MhDj6hX6WzE,1070
|
|
8
|
+
uvpg-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lucas Maziero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|