Skip to content

Commit fb6af8a

Browse files
merge: python-pkg root level init
2 parents ee87ee5 + 0060599 commit fb6af8a

7 files changed

Lines changed: 163 additions & 89 deletions

File tree

.github/workflows/build.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: GitHub Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
release:
10+
name: Create GitHub Release
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: write
14+
15+
steps:
16+
- name: Checkout source code
17+
uses: actions/checkout@v4
18+
19+
- name: Set up uv
20+
uses: astral-sh/setup-uv@v4
21+
with:
22+
enable-cache: true
23+
24+
- name: Build package
25+
run: uv build
26+
27+
- name: Create GitHub Release
28+
uses: softprops/action-gh-release@v2
29+
with:
30+
files: dist/*
31+
generate_release_notes: true

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ wheels/
1818
.coverage
1919
htmlcov/
2020
.tox/
21-
.nox/
21+
.nox/
22+
23+
# specific files/dirs
24+
src/pyplatez/templates/

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ pyplatez/
2828
│ └── PULL_REQUEST_TEMPLATE.md
2929
├── assets/ # Images and static assets for docs
3030
├── src/ # Application source code
31-
│ └── pyplatez/ # The main package directory
31+
│ └── pyplatez/ # The main package directory
32+
│ └── cli.py # Command-line interface logic for scaffolding via `pyplatez init`
3233
├── tests/ # Unit tests via pytest
3334
├── CODE_OF_CONDUCT.md # Community guidelines
3435
├── CONTRIBUTING.md # Instructions for dev setup and PRs
36+
├── hatch_build.py # Custom build hook that dynamically bundles template files into the wheel
3537
├── LICENSE # Open source license
3638
├── main.py # Default application entry point
3739
├── pyproject.toml # The heart of the project configuration

hatch_build.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from pathlib import Path
2+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
3+
4+
EXCLUDE = {
5+
"src",
6+
"dist",
7+
"__pycache__",
8+
".git",
9+
".venv",
10+
"hatch_build.py",
11+
"uv.lock",
12+
".mypy_cache",
13+
".pytest_cache",
14+
".ruff_cache",
15+
"PKG-INFO",
16+
".hatch",
17+
}
18+
19+
20+
class BuildHook(BuildHookInterface):
21+
def initialize(self, _version, build_data):
22+
if self.target_name != "wheel":
23+
return
24+
# shit doesn't package unless you force it down
25+
root = Path(self.root)
26+
force_include = build_data.setdefault("force_include", {})
27+
28+
for item in root.iterdir():
29+
if item.name in EXCLUDE:
30+
continue
31+
32+
dest = f"pyplatez/templates/{item.name}"
33+
force_include[str(item)] = dest
34+
35+
print(
36+
f"[pyplatez] Bundling {len(force_include)} root items into pyplatez/templates/"
37+
)

pyproject.toml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
[project]
22
name = "pyplatez"
3-
version = "0.1.0"
4-
description = "minimalist python template for professional/hobbyist works"
3+
version = "0.1.1"
4+
description = "Batteries-included python-starter project template"
55
readme = "README.md"
66
requires-python = ">=3.14"
77
dependencies = []
88

9+
[project.scripts]
10+
pyplatez = "pyplatez.cli:main"
11+
912
[dependency-groups]
1013
dev = [
1114
"build>=1.4.2",
1215
"hatchling>=1.29.0",
13-
"pyinstaller>=6.19.0",
1416
"pytest>=9.0.2",
1517
"ruff>=0.15.8",
1618
"twine>=6.2.0",
@@ -20,5 +22,10 @@ dev = [
2022
requires = ["hatchling"]
2123
build-backend = "hatchling.build"
2224

25+
[tool.hatch.build.hooks.custom]
26+
[tool.hatch.build.targets.wheel]
27+
28+
packages = ["src/pyplatez"]
29+
2330
[tool.uv]
24-
package = true
31+
package = true

src/pyplatez/cli.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import argparse
2+
import shutil
3+
import sys
4+
from importlib import resources
5+
from pathlib import Path
6+
7+
8+
def scaffold(project_name: str, package_name: str, target: Path) -> None:
9+
with resources.as_file(
10+
resources.files("pyplatez").joinpath("templates")
11+
) as tmpl_dir:
12+
shutil.copytree(tmpl_dir, target, dirs_exist_ok=True)
13+
14+
old_pkg = target / "src" / "pyplatez"
15+
new_pkg = target / "src" / package_name
16+
if old_pkg.exists():
17+
old_pkg.rename(new_pkg)
18+
19+
pyproject = target / "pyproject.toml"
20+
if pyproject.exists():
21+
text = pyproject.read_text()
22+
text = text.replace('name = "pyplatez"', f'name = "{project_name}"')
23+
text = text.replace(
24+
'description = "minimalist python template for professional/hobbyist works"',
25+
'description = "Add your description here"',
26+
)
27+
pyproject.write_text(text)
28+
29+
print(f" '{project_name}' ready at ./{target.name}")
30+
print(f" cd {target.name} && uv sync")
31+
32+
33+
def main() -> None:
34+
parser = argparse.ArgumentParser(
35+
prog="pyplatez",
36+
description="A batteries-included Python-starter project-template",
37+
)
38+
sub = parser.add_subparsers(dest="command")
39+
40+
init = sub.add_parser("init", help="Create a new project")
41+
init.add_argument("name", help="Project name")
42+
init.add_argument(
43+
"--path", default=None, help="Where to create it (default: ./<name>)"
44+
)
45+
46+
args = parser.parse_args()
47+
48+
if args.command == "init":
49+
package_name = args.name.replace("-", "_")
50+
if not package_name.isidentifier():
51+
print(
52+
f" Error: '{args.name}' cannot be normalized to a valid Python package name.",
53+
file=sys.stderr,
54+
)
55+
sys.exit(1)
56+
57+
target = Path(args.path) if args.path else Path.cwd() / args.name
58+
59+
if target.exists():
60+
if not target.is_dir():
61+
print(
62+
f" Error: '{target}' already exists and is a file, not a directory.",
63+
file=sys.stderr,
64+
)
65+
sys.exit(1)
66+
if any(target.iterdir()):
67+
print(
68+
f" Error: '{target}' already exists and is not empty.",
69+
file=sys.stderr,
70+
)
71+
sys.exit(1)
72+
73+
target.mkdir(parents=True, exist_ok=True)
74+
scaffold(args.name, package_name, target)
75+
else:
76+
parser.print_help()

uv.lock

Lines changed: 1 addition & 83 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)