From 0a856e6b65b729e9d79bc77070f1168398245c89 Mon Sep 17 00:00:00 2001 From: PranavU-Coder Date: Mon, 18 May 2026 21:09:57 +0530 Subject: [PATCH 1/2] feat: workflow for releases & building the pkg for init at root level like uv --- .github/workflows/build.yml | 31 ++++++++++++++ .gitignore | 5 ++- README.md | 4 +- hatch_build.py | 25 +++++++++++ pyproject.toml | 13 ++++-- src/pyplatez/cli.py | 54 ++++++++++++++++++++++++ uv.lock | 82 ------------------------------------- 7 files changed, 127 insertions(+), 87 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 hatch_build.py create mode 100644 src/pyplatez/cli.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0fef15b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +name: GitHub Release + +on: + push: + tags: + - "v*" + +jobs: + release: + name: Create GitHub Release + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Set up uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Build package + run: uv build + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: dist/* + generate_release_notes: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 876fca2..c748f68 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ wheels/ .coverage htmlcov/ .tox/ -.nox/ \ No newline at end of file +.nox/ + +# specific files/dirs +src/pyplatez/templates/ diff --git a/README.md b/README.md index b7658c7..48573d8 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,12 @@ pyplatez/ │ └── PULL_REQUEST_TEMPLATE.md ├── assets/ # Images and static assets for docs ├── src/ # Application source code -│ └── pyplatez/ # The main package directory +│ └── pyplatez/ # The main package directory +│ └── cli.py # Command-line interface logic for scaffolding via `pyplatez init` ├── tests/ # Unit tests via pytest ├── CODE_OF_CONDUCT.md # Community guidelines ├── CONTRIBUTING.md # Instructions for dev setup and PRs +├── hatch_build.py # Custom build hook that dynamically bundles template files into the wheel ├── LICENSE # Open source license ├── main.py # Default application entry point ├── pyproject.toml # The heart of the project configuration diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 0000000..1c61819 --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,25 @@ +from pathlib import Path +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + +EXCLUDE = { + "src", "dist", "__pycache__", ".git", ".venv", + "hatch_build.py", "uv.lock", ".mypy_cache", ".pytest_cache", ".ruff_cache", + "PKG-INFO", ".hatch" +} + +class BuildHook(BuildHookInterface): + def initialize(self, _version, build_data): + if self.target_name != "wheel": + return + # shit doesn't package unless you force it down + root = Path(self.root) + force_include = build_data.setdefault("force_include", {}) + + for item in root.iterdir(): + if item.name in EXCLUDE: + continue + + dest = f"pyplatez/templates/{item.name}" + force_include[str(item)] = dest + + print(f"[pyplatez] Bundling {len(force_include)} root items into pyplatez/templates/") \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 23fad9c..a75ecac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,18 @@ [project] name = "pyplatez" version = "0.1.0" -description = "minimalist python template for professional/hobbyist works" +description = "Batteries-included python-starter project template" readme = "README.md" requires-python = ">=3.14" dependencies = [] +[project.scripts] +pyplatez = "pyplatez.cli:main" + [dependency-groups] dev = [ "build>=1.4.2", "hatchling>=1.29.0", - "pyinstaller>=6.19.0", "pytest>=9.0.2", "ruff>=0.15.8", "twine>=6.2.0", @@ -20,5 +22,10 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.build.hooks.custom] +[tool.hatch.build.targets.wheel] + +packages = ["src/pyplatez"] + [tool.uv] -package = true \ No newline at end of file +package = true diff --git a/src/pyplatez/cli.py b/src/pyplatez/cli.py new file mode 100644 index 0000000..cfa5760 --- /dev/null +++ b/src/pyplatez/cli.py @@ -0,0 +1,54 @@ +import argparse +import shutil +import sys +from importlib import resources +from pathlib import Path + +def scaffold(project_name: str, target: Path) -> None: + with resources.as_file( + resources.files("pyplatez").joinpath("templates") + ) as tmpl_dir: + shutil.copytree(tmpl_dir, target, dirs_exist_ok=True) + + old_pkg = target / "src" / "pyplatez" + new_pkg = target / "src" / project_name + if old_pkg.exists(): + old_pkg.rename(new_pkg) + + pyproject = target / "pyproject.toml" + if pyproject.exists(): + text = pyproject.read_text() + text = text.replace('name = "pyplatez"', f'name = "{project_name}"') + text = text.replace( + 'description = "minimalist python template for professional/hobbyist works"', + 'description = "Add your description here"', + ) + pyproject.write_text(text) + + print(f" '{project_name}' ready at ./{target.name}") + print(f" cd {target.name} && uv sync") + + +def main() -> None: + parser = argparse.ArgumentParser( + prog="pyplatez", + description="A batteries-included Python-starter project-template", + ) + sub = parser.add_subparsers(dest="command") + + init = sub.add_parser("init", help="Create a new project") + init.add_argument("name", help="Project name") + init.add_argument("--path", default=None, help="Where to create it (default: ./)") + + args = parser.parse_args() + + if args.command == "init": + target = Path(args.path) if args.path else Path.cwd() / args.name + if target.exists() and any(target.iterdir()): + print(f" Error: '{target}' already exists and is not empty.", file=sys.stderr) + sys.exit(1) + + target.mkdir(parents=True, exist_ok=True) + scaffold(args.name, target) + else: + parser.print_help() \ No newline at end of file diff --git a/uv.lock b/uv.lock index 2ab50ed..26c3a5e 100644 --- a/uv.lock +++ b/uv.lock @@ -2,15 +2,6 @@ version = 1 revision = 3 requires-python = ">=3.14" -[[package]] -name = "altgraph" -version = "0.17.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, -] - [[package]] name = "build" version = "1.4.3" @@ -254,18 +245,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] -[[package]] -name = "macholib" -version = "1.16.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "altgraph" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, -] - [[package]] name = "markdown-it-py" version = "4.0.0" @@ -348,15 +327,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] -[[package]] -name = "pefile" -version = "2024.8.26" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -384,47 +354,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] -[[package]] -name = "pyinstaller" -version = "6.19.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "altgraph" }, - { name = "macholib", marker = "sys_platform == 'darwin'" }, - { name = "packaging" }, - { name = "pefile", marker = "sys_platform == 'win32'" }, - { name = "pyinstaller-hooks-contrib" }, - { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c8/63/fd62472b6371d89dc138d40c36d87a50dc2de18a035803bbdc376b4ffac4/pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865", size = 4036072, upload-time = "2026-02-14T18:06:28.718Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/eb/23374721fecfa72677e79800921cb6aceefa6ba48574dc404f3f6c6c3be7/pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134", size = 1040563, upload-time = "2026-02-14T18:05:22.436Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7e/dfd724b0b533f5aaec0ee5df406fe2319987ed6964480a706f85478b12ea/pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11", size = 735477, upload-time = "2026-02-14T18:05:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/88/c9/ee3a4101c31f26344e66896c73c1fd6ed8282bf871473365b7f8674af406/pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf", size = 747143, upload-time = "2026-02-14T18:05:31.488Z" }, - { url = "https://files.pythonhosted.org/packages/da/0a/fc77e9f861be8cf300ac37155f59cc92aff99b29f2ddd78546f563a5b5a6/pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122", size = 744849, upload-time = "2026-02-14T18:05:35.492Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e3/6872e020ee758afe0b821663858492c10745608b07150e5e2c824a5b3e1c/pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63", size = 741590, upload-time = "2026-02-14T18:05:39.59Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/b8db5f1a4b0fb228175f2ea0aa33f949adcc097fbe981cc524f9faf85777/pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe", size = 741448, upload-time = "2026-02-14T18:05:45.636Z" }, - { url = "https://files.pythonhosted.org/packages/6f/4d/63b0600f2694e9141b83129fbc1c488ec84d5a0770b1448ec154dcd0fee9/pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83", size = 740613, upload-time = "2026-02-14T18:05:49.726Z" }, - { url = "https://files.pythonhosted.org/packages/01/d4/e812ad36178093a0e9fd4b8127577748dd85b0cb71de912229dca21fd741/pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6", size = 740350, upload-time = "2026-02-14T18:05:54.093Z" }, - { url = "https://files.pythonhosted.org/packages/52/03/b2c2ee41fb8e10fd2a45d21f5ec2ef25852cfb978dbf762972eed59e3d63/pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2", size = 1324317, upload-time = "2026-02-14T18:06:00.085Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d3/6d5e62b8270e2b53a6065e281b3a7785079b00e9019c8019952828dd1669/pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33", size = 1384894, upload-time = "2026-02-14T18:06:06.425Z" }, - { url = "https://files.pythonhosted.org/packages/81/65/458cd523308a101a22fd2742893405030cc24994cc74b1b767cecf137160/pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea", size = 1325374, upload-time = "2026-02-14T18:06:12.804Z" }, -] - -[[package]] -name = "pyinstaller-hooks-contrib" -version = "2026.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/fe/9278c29394bf69169febc21f96b4252c3ee7c8ec22c2fc545004bed47e71/pyinstaller_hooks_contrib-2026.4.tar.gz", hash = "sha256:766c281acb1ecc32e21c8c667056d7ebf5da0aabd5e30c219f9c2a283620eeaa", size = 173050, upload-time = "2026-03-31T14:10:51.188Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/f4/035fb8c06deff827f540a9a4ed9122c54e5376fca3e42eddf0c263730775/pyinstaller_hooks_contrib-2026.4-py3-none-any.whl", hash = "sha256:1de1a5e49a878122010b88c7e295502bc69776c157c4a4dc78741a4e6178b00f", size = 455496, upload-time = "2026-03-31T14:10:49.867Z" }, -] - [[package]] name = "pyplatez" version = "0.1.0" @@ -434,7 +363,6 @@ source = { editable = "." } dev = [ { name = "build" }, { name = "hatchling" }, - { name = "pyinstaller" }, { name = "pytest" }, { name = "ruff" }, { name = "twine" }, @@ -446,7 +374,6 @@ dev = [ dev = [ { name = "build", specifier = ">=1.4.2" }, { name = "hatchling", specifier = ">=1.29.0" }, - { name = "pyinstaller", specifier = ">=6.19.0" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "ruff", specifier = ">=0.15.8" }, { name = "twine", specifier = ">=6.2.0" }, @@ -587,15 +514,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] -[[package]] -name = "setuptools" -version = "82.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, -] - [[package]] name = "trove-classifiers" version = "2026.1.14.14" From e6247d2096435782c009fb5d742f6184b50c7e84 Mon Sep 17 00:00:00 2001 From: PranavU-Coder Date: Mon, 18 May 2026 21:41:57 +0530 Subject: [PATCH 2/2] fix: formatting & possible errors --- hatch_build.py | 26 +++++++++++++++++++------- pyproject.toml | 2 +- src/pyplatez/cli.py | 40 +++++++++++++++++++++++++++++++--------- uv.lock | 2 +- 4 files changed, 52 insertions(+), 18 deletions(-) diff --git a/hatch_build.py b/hatch_build.py index 1c61819..946c7cf 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -2,24 +2,36 @@ from hatchling.builders.hooks.plugin.interface import BuildHookInterface EXCLUDE = { - "src", "dist", "__pycache__", ".git", ".venv", - "hatch_build.py", "uv.lock", ".mypy_cache", ".pytest_cache", ".ruff_cache", - "PKG-INFO", ".hatch" + "src", + "dist", + "__pycache__", + ".git", + ".venv", + "hatch_build.py", + "uv.lock", + ".mypy_cache", + ".pytest_cache", + ".ruff_cache", + "PKG-INFO", + ".hatch", } + class BuildHook(BuildHookInterface): def initialize(self, _version, build_data): if self.target_name != "wheel": return - # shit doesn't package unless you force it down + # shit doesn't package unless you force it down root = Path(self.root) force_include = build_data.setdefault("force_include", {}) - + for item in root.iterdir(): if item.name in EXCLUDE: continue - + dest = f"pyplatez/templates/{item.name}" force_include[str(item)] = dest - print(f"[pyplatez] Bundling {len(force_include)} root items into pyplatez/templates/") \ No newline at end of file + print( + f"[pyplatez] Bundling {len(force_include)} root items into pyplatez/templates/" + ) diff --git a/pyproject.toml b/pyproject.toml index a75ecac..f21bb60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pyplatez" -version = "0.1.0" +version = "0.1.1" description = "Batteries-included python-starter project template" readme = "README.md" requires-python = ">=3.14" diff --git a/src/pyplatez/cli.py b/src/pyplatez/cli.py index cfa5760..9e280da 100644 --- a/src/pyplatez/cli.py +++ b/src/pyplatez/cli.py @@ -4,14 +4,15 @@ from importlib import resources from pathlib import Path -def scaffold(project_name: str, target: Path) -> None: + +def scaffold(project_name: str, package_name: str, target: Path) -> None: with resources.as_file( resources.files("pyplatez").joinpath("templates") ) as tmpl_dir: shutil.copytree(tmpl_dir, target, dirs_exist_ok=True) old_pkg = target / "src" / "pyplatez" - new_pkg = target / "src" / project_name + new_pkg = target / "src" / package_name if old_pkg.exists(): old_pkg.rename(new_pkg) @@ -38,17 +39,38 @@ def main() -> None: init = sub.add_parser("init", help="Create a new project") init.add_argument("name", help="Project name") - init.add_argument("--path", default=None, help="Where to create it (default: ./)") + init.add_argument( + "--path", default=None, help="Where to create it (default: ./)" + ) args = parser.parse_args() if args.command == "init": - target = Path(args.path) if args.path else Path.cwd() / args.name - if target.exists() and any(target.iterdir()): - print(f" Error: '{target}' already exists and is not empty.", file=sys.stderr) + package_name = args.name.replace("-", "_") + if not package_name.isidentifier(): + print( + f" Error: '{args.name}' cannot be normalized to a valid Python package name.", + file=sys.stderr, + ) sys.exit(1) - + + target = Path(args.path) if args.path else Path.cwd() / args.name + + if target.exists(): + if not target.is_dir(): + print( + f" Error: '{target}' already exists and is a file, not a directory.", + file=sys.stderr, + ) + sys.exit(1) + if any(target.iterdir()): + print( + f" Error: '{target}' already exists and is not empty.", + file=sys.stderr, + ) + sys.exit(1) + target.mkdir(parents=True, exist_ok=True) - scaffold(args.name, target) + scaffold(args.name, package_name, target) else: - parser.print_help() \ No newline at end of file + parser.print_help() diff --git a/uv.lock b/uv.lock index 26c3a5e..ec683ca 100644 --- a/uv.lock +++ b/uv.lock @@ -356,7 +356,7 @@ wheels = [ [[package]] name = "pyplatez" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } [package.dev-dependencies]