Skip to content

feat: add a console script entry point and a license prompt - #34

Merged
mjun0812 merged 1 commit into
mainfrom
feat/scripts-and-license
Sep 7, 2026
Merged

mjun0812 merged 1 commit into
mainfrom
feat/scripts-and-license

Conversation

@mjun0812

@mjun0812 mjun0812 commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Overview and Background

Generated projects now expose a console script named after the project, so uv run <project_name> (and python -m <package_name>) runs the application, and generation asks which license to use, writing a LICENSE file and the PEP 639 metadata to match. Previously the only entry point was the root main.py, which is not installed with the package and cannot be invoked after uv build, and projects were generated without any license file or license metadata, leaving the built wheel without licensing information.

Related Issues

None

Implementation Approach

  • Entry point: the greeting logic moves into src/<package>/__main__.py with a main() function. [project.scripts] registers "<project_name>" = "<package>.__main__:main" (the key is quoted because project names may contain dots), and the root main.py is kept as a thin delegate so existing instructions and the Compose command keep working. The if __name__ == "__main__": guard is excluded from coverage.
  • License: a new license prompt offers MIT, Apache-2.0, BSD-3-Clause, or None (default MIT). The LICENSE template embeds the canonical SPDX texts and fills the year with Copier's strftime filter and the holder with author_name. For None the file name renders empty, which Copier treats as "do not create". LICENSE is added to _skip_if_exists so copier update never rewrites a license a project has customised or re-dated.
  • pyproject.toml gets license = "<SPDX id>" and license-files = ["LICENSE"] only when a license was chosen; hatchling emits these as License-Expression and License-File in the wheel metadata.
  • The new test uses from __future__ import annotations with a TYPE_CHECKING import of pytest, which satisfies the TC rules on every supported target version (with the py314 target, Ruff otherwise reports TC002).

Changes

  • copier.yml: license prompt; LICENSE in _skip_if_exists.
  • template/{% if license != 'None' %}LICENSE{% endif %}: MIT / Apache-2.0 / BSD-3-Clause texts.
  • template/pyproject.toml: conditional license and license-files; [project.scripts]; coverage exclude_also.
  • template/src/{{package_name}}/__main__.py: main(); template/main.py delegates to it.
  • template/tests/test_main.py: captures the greeting printed by main().
  • .github/workflows/test.yml: passes license=MIT, asserts the LICENSE file exists, and runs the console script.
  • README.md, template/README.md: document the prompt and uv run <project_name>.

Impact

  • User-facing: one more prompt during generation (default MIT). uv run <project_name> and python -m <package_name> are new; python main.py and docker compose up behave as before.
  • Compatibility: projects updating from the template receive the new prompt with its default unless answered; because LICENSE is in _skip_if_exists, an existing LICENSE is never overwritten. The lockfile is unaffected (uv sync --locked passes with the new metadata).
  • Packaging: wheels and sdists now carry the license expression and file.

Validation Results

Generated projects from this branch for each license choice and for Python 3.10 and 3.14.

# Each license choice
for lic in MIT Apache-2.0 BSD-3-Clause None; do uvx copier copy --defaults ... -d license=$lic . out-$lic; done
# MIT / Apache-2.0 / BSD-3-Clause: LICENSE present with "2026 <author>", pyproject has license + license-files
# None: no LICENSE file, no license keys in pyproject

# Python 3.10 and 3.14 projects (MIT)
uv sync --locked
uv run --frozen ruff format . --check    # 8 files already formatted
uv run --frozen ruff check .             # All checks passed!
uv run --frozen ty check                 # All checks passed!
uv run --frozen pytest --cov             # 3 passed, coverage 100% (__init__ 2/2, __main__ 3/3)
uv run --frozen test-copier              # Hello from test-copier!
uv run --frozen python -m test_copier    # Hello from test-copier!
uv run --frozen python main.py           # Hello from test-copier!

# Wheel metadata
uv build
unzip -p dist/*.whl '*/METADATA' | grep -E '^License'
# License-Expression: MIT
# License-File: LICENSE
unzip -l dist/*.whl | grep -i license    # test_copier-0.0.1.dist-info/licenses/LICENSE

- Expose the greeting through src/<package>/__main__.py, register it as
  a [project.scripts] entry named after the project, and make the root
  main.py delegate to it so `uv run <project_name>` and `python -m`
  both work
- Ask for a license (MIT, Apache-2.0, BSD-3-Clause or None), render the
  matching LICENSE from the SPDX text with the author and current year,
  and set the PEP 639 license and license-files metadata
- Keep LICENSE out of template updates via _skip_if_exists, test the
  entry point, exclude the __main__ guard from coverage, and verify the
  LICENSE file and console script in the template CI
@mjun0812 mjun0812 added the enhancement New feature or request label Sep 7, 2026
@mjun0812 mjun0812 self-assigned this Sep 7, 2026
@mjun0812
mjun0812 merged commit 6f84783 into main Sep 7, 2026
7 checks passed
@mjun0812
mjun0812 deleted the feat/scripts-and-license branch September 7, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant