Conversation
Add --prepare-pypi flag to scaffold PyPI-ready projects: - Patches pyproject.toml with license, classifiers, keywords, authors (placeholder), project URLs, and readme field - Generates LICENSE file from canonical templates (MIT, Apache-2.0, GPL-3.0, BSD-3-Clause) with year/author substitution - Creates minimal README.md template if missing - Generates .github/workflows/publish.yml with Trusted Publisher OIDC (no API tokens), tag/version mismatch guard, separate build/publish jobs Add --license flag (works standalone or with --prepare-pypi): - "auto" mode detects license from existing LICENSE/LICENCE files (case-insensitive search) using pattern matching against canonical license phrases - "custom" mode preserves unrecognized license files as-is - Standalone --license MIT creates just the LICENSE file Also: fix uv venv --allow-existing flag, add 34 TDD tests in test_pypi_readiness.py, add venv edge case tests in test_error_handling.py, update README with new flag docs.
…ELEASING.md Fix _create_publish_workflow() to include test: job referencing ci.yml via uses: (was missing — build ran without tests). Add shell: bash to version check step for Windows compatibility. Add _add_workflow_call_trigger(): injects workflow_call: into existing ci.yml so publish.yml can reuse it. Handles edge cases: missing ci.yml, already present, compact on: format (skips with warning). Fix _add_pypi_toml_metadata() Changelog URL: add Changelog to urls on fresh projects, and add it individually when urls section already exists but Changelog key is missing (was skipping entire section). Add _create_releasing_doc(): generates RELEASING.md with version bump checklist and PyPI publishing section (Trusted Publishers, TestPyPI verification, manual fallback). Appends PyPI section to existing RELEASING.md if it lacks one, rather than skipping. Add 9 new tests (43 total in test_pypi_readiness.py).
Bug fixes: - GitIgnore.__init__: call super().__init__([]) so pathspec internal state (_backend) is initialized; delete __dict__['patterns'] to preserve lazy @cached_property loading - GitIgnore.is_ignored(): fix inverted match_file semantics — return True when match_file returns True (file IS ignored), not the inverse - Typer Option: remove deprecated is_flag=True from all 5 bool flags --prepare-pypi improvements: - Early-exit in model_post_init: --prepare-pypi now skips gitignore/venv/dependency discovery since it only needs file I/O - Auto-detect GitHub owner/repo from git remote URL (SSH + HTTPS) - Auto-detect default branch from git symbolic-ref - Auto-detect build backend (hatchling/poetry/setuptools/flit/uv) - Auto-detect Python version from requires-python - publish.yml: conditional test job (only when ci.yml exists) - RELEASING.md: adaptive build/publish commands per detected backend - All templates use detected owner instead of USERNAME placeholder Tests: 63 PyPI readiness tests (up from 43), 140 total (up from 120)
Replace toml.dump() full-file rewrite with format-preserving string
insertion. The old approach read with tomllib and wrote back with
toml.dump(), which destroyed:
- Multi-line array formatting (collapsed to single lines)
- Comments (stripped entirely)
- Section ordering (rearranged [tool.*] sections)
- Inline table syntax ({text = "X"} → [project.license])
New approach: use tomllib only to detect which fields are missing,
then insert TOML snippets at the correct position in the raw text
via _find_toml_section_range() helper.
Add 3 tests: format preservation, missing field insertion, section
range detection. Total: 66 PyPI readiness tests, 143 overall.
…lity - _detect_build_backend(): return uv build/uv publish for hatchling, setuptools, and flit (uv handles any PEP 517 backend; keeps poetry specific commands) - _create_publish_workflow(): detect outdated build commands in existing publish.yml and regenerate; use 'uv run --no-project python' for version check (tomllib is stdlib, avoids dependency resolution) - _create_releasing_doc(): detect and replace outdated build commands in existing RELEASING.md; normalize PyPI project name (PEP 503) - Help messages: replace python3 with uv run python where applicable - Tests: update expectations, add update/skip/dry-run/normalization tests (152 tests pass, 9 new)
- RELEASING.md template: use 'uv pip install' instead of bare 'pip install' for TestPyPI verification (consistent with uv-centric approach) - Tests: add 8 edge case tests for PEP 503 normalization (dots, mixed case, consecutive separators), pdm backend detection, setuptools/flit/twine update detection, uv pip install verification (160 tests pass)
…ness tests
## pyuvstarter.py — _prepare_pypi subsystem
### _detect_ci_workflow_name() (new, pyuvstarter.py:5370)
Detects the project's CI workflow filename by trying 11 candidates in
priority order instead of hardcoding 'ci.yml':
ci.yml → test.yml → tests.yml → build.yml → main.yml →
workflow.yml → workflows.yml → python.yml →
ci.yaml → test.yaml → workflow.yaml
- workflow.yml added: GitHub's web UI names new workflows this by default
- python.yml added: GitHub's Python starter workflow template uses this name
- .yaml variants included for projects not using .yml extension
### _create_publish_workflow() fixes (pyuvstarter.py:5382)
- Removed dead ci_path variable (set but never read after refactor)
- Uses detected CI filename in generated publish.yml test: job (not hardcoded ci.yml)
- Permissions block (checks: write, pull-requests: write, issues: read) on
the test: job that calls ci.yml via workflow_call — prevents GitHub startup_failure
- Permissions detection uses regex r'\n [a-z][\w-]*:' for job-boundary
instead of fragile split("build:") which broke on build-docs: job names
- needs_update detection covers missing permissions block (was only checking
outdated build commands)
### _add_workflow_call_trigger() fixes (pyuvstarter.py:~5530)
- SKIP log now uses f"{ci_filename} already has workflow_call trigger."
instead of hardcoded "ci.yml already has workflow_call trigger."
- SUCCESS log now uses f"Added workflow_call trigger to {ci_filename}."
instead of hardcoded "Added workflow_call trigger to ci.yml."
## tests/test_pypi_readiness.py — 94 tests (was 85)
New/updated tests:
- test_publish_yml_has_test_job_when_ci_exists: +3 assertions verifying
permissions: block (checks: write, pull-requests: write) follows uses: line
- test_detect_ci_workflow_name_returns_ci_yml_first: priority ordering
- test_detect_ci_workflow_name_falls_back_to_test_yml: fallback behavior
- test_detect_ci_workflow_name_returns_none_when_absent: None case
- test_publish_yml_references_detected_ci_filename: test.yml → publish uses test.yml
- test_add_workflow_call_trigger_injects_into_test_yml: injection into test.yml
- test_publish_yml_permissions_detection_not_fooled_by_build_docs_job:
regex guard against split("build:") false-matching build-docs: job names
- test_detect_ci_workflow_name_falls_back_to_workflow_yml: workflow.yml detection
- test_detect_ci_workflow_name_falls_back_to_python_yml: python.yml detection
- test_publish_yml_references_workflow_yml_when_detected: workflow.yml → publish uses it
## .github/workflows/publish.yml (new)
pyuvstarter's own 4-job OIDC pipeline: test (reuses ci.yml) → build → publish-testpypi → publish-pypi
- All action SHAs pinned (checkout v4, setup-uv v7.3.1, upload/download-artifact v4, gh-action-pypi-publish)
- test: job has permissions: block to prevent startup_failure
- Tag-version vs pyproject.toml version verification step
## .github/workflows/ci.yml changes
- workflow_call: trigger added (line 8) so publish.yml can reuse ci.yml
- SHA-pinned actions/checkout (34e114876b0b11c390a56381ad16ebd13914f8d5 # v4)
- NO_COLOR=1 env var suppresses Rich ANSI codes for stable test assertions
- dorny/test-reporter (d61b558 # v1) replaces EnricoMi/publish-unit-test-result-action
- Windows test step: adds uv run python -m pytest tests/ --junitxml=test_results_pytest_windows.xml
so dorny/test-reporter gets XML output on Windows (was Linux-only before)
## RELEASING.md (new)
Release checklist: version bump → test → commit → reinstall → verify → tag → push
PyPI Trusted Publisher setup instructions, TestPyPI verification, troubleshooting.
All 171 tests pass: uv run python -m pytest tests/ -q
YAML validated: python3 -c "import yaml; yaml.safe_load(open('...'));" for both workflows
…(C4-C7)
## pyuvstarter.py
### C4: Content-based publish workflow exclusion (_is_publish_workflow)
- Add _CI_WORKFLOW_CANDIDATES tuple (13 candidates: ci.yml, test.yml, tests.yml,
build.yml, main.yml, workflow.yml, workflows.yml, python.yml, python-app.yml,
python-package.yml, ci.yaml, test.yaml, workflow.yaml)
- Add _CI_CANDIDATES_STR module-level constant (pre-joined for WARN messages)
- Add _is_publish_workflow(content: str) -> bool with 3-tier detection:
Tier 1: pypa/gh-action-pypi-publish substring (definitive)
Tier 2a: environment: pypi/testpypi regex — allows inline comment (pypi-staging safe)
Tier 2b: name: pypi/testpypi dict-form regex with EOL anchor
Tier 3: run: step with twine upload or uv publish — regex handles both
"- run: cmd" (YAML list item) and " run: cmd" (multi-key step) forms
via ^\s*(?:-\s+)?run:\s pattern
- Update _detect_ci_workflow_name() to use _CI_WORKFLOW_CANDIDATES constant
and call _is_publish_workflow() on each candidate; skips publish workflows
(e.g. build.yml containing pypa/gh-action-pypi-publish) so existing release
automation is never corrupted by workflow_call injection
### C5: Fix DRY RUN log hardcoded "ci.yml"
- _add_workflow_call_trigger: DRY RUN log now uses f"{ci_filename}" instead of
hardcoded "ci.yml" — reflects actual detected filename (test.yml, build.yml, etc.)
### C6: Fix WARN candidate lists
- _create_publish_workflow WARN: uses _CI_CANDIDATES_STR (was 5 names, now all 13)
- _add_workflow_call_trigger INFO: same fix
### C7: SHA-pin all action refs in generated publish.yml template
- actions/checkout@34e1148 # v4
- astral-sh/setup-uv@5a095e7 # v7
- actions/upload-artifact@ea165f8 # v4
- actions/download-artifact@d3f86a1 # v4
- pypa/gh-action-pypi-publish@ed0c539 # release/v1
(matches SHAs already used in pyuvstarter's own .github/workflows/publish.yml)
## tests/test_pypi_readiness.py (+19 tests, 113 total in file)
### _is_publish_workflow tests (15 new)
- test_is_publish_workflow_detects_pypa_action (Tier 1)
- test_is_publish_workflow_detects_pypa_action_sha_pinned (Tier 1 SHA form)
- test_is_publish_workflow_detects_pypi_environment_single_value (Tier 2a)
- test_is_publish_workflow_detects_pypi_environment_with_inline_comment (Tier 2a + comment)
- test_is_publish_workflow_does_not_match_pypi_staging (Tier 2a negative)
- test_is_publish_workflow_detects_dict_form_environment (Tier 2b)
- test_is_publish_workflow_dict_form_does_not_match_job_display_name (Tier 2b negative)
- test_is_publish_workflow_detects_twine_upload_in_run_step (Tier 3)
- test_is_publish_workflow_detects_python_m_twine_upload (Tier 3 python -m form)
- test_is_publish_workflow_twine_in_comment_does_not_match (Tier 3 comment negative)
- test_is_publish_workflow_detects_uv_publish_in_run_step (Tier 3 uv publish)
- test_is_publish_workflow_uv_publish_in_comment_does_not_match (Tier 3 comment negative)
- test_is_publish_workflow_uv_build_does_not_match (uv build ≠ uv publish)
- test_is_publish_workflow_returns_false_for_ci_content (CI content negative)
- test_is_publish_workflow_empty_content_returns_false (empty file negative)
### Content-based CI exclusion tests (2 new)
- test_detect_ci_workflow_name_skips_publish_workflow_named_build
(build.yml with pypa action → returns test.yml, not build.yml)
- test_detect_ci_workflow_name_skips_workflow_with_uv_publish
(workflow.yml with uv publish → returns ci.yml)
### DRY RUN log tests (2 new, use mock.patch since _log_action uses Rich Console)
- test_add_workflow_call_trigger_dry_run_does_not_modify_file
- test_add_workflow_call_trigger_dry_run_log_uses_ci_filename
190 total tests pass (was 171).
…y (+32)
## _is_publish_workflow: additional Tier 3 variants and full-content tests
- test_is_publish_workflow_detects_python3_m_twine_upload
(python3 -m twine — whole-word regex matches regardless of python version)
- test_is_publish_workflow_detects_uv_run_twine_upload
(uv run twine upload — whole-word match)
- test_is_publish_workflow_detects_twine_upload_with_flags
(--skip-existing, --repository flags still detected)
- test_is_publish_workflow_twine_check_does_not_match
(twine check has no 'upload' word — correct negative)
- test_is_publish_workflow_detects_compound_uv_build_and_publish
(uv build && uv publish on one line — detected)
- test_is_publish_workflow_multiline_run_block_not_caught_by_tier3
(documents design limitation: multi-line run: | block not caught by Tier 3)
- test_is_publish_workflow_multiline_run_caught_by_tier1
(same workflow caught when Tier 1 tell also present — safety net confirmed)
- test_is_publish_workflow_uv_publish_with_env_vars
(UV_PUBLISH_TOKEN=${{ secrets.PYPI_TOKEN }} uv publish — detected)
- test_is_publish_workflow_complete_realistic_publish_yaml
(full 14-line publish workflow YAML — all 3 tiers match)
- test_is_publish_workflow_complete_realistic_ci_yaml
(full 14-line CI workflow YAML — correctly returns False)
- test_is_publish_workflow_tier2a_crlf_line_endings (Windows CRLF handled)
- test_is_publish_workflow_tier2b_crlf_line_endings (dict-form CRLF handled)
- test_is_publish_workflow_pypi_in_workflow_name_does_not_match
("name: Run pypi tests", "name: pypi-tests" — EOL anchor protects both)
## _detect_ci_workflow_name: remaining candidates and edge cases
- test_detect_ci_workflow_name_falls_back_to_tests_yml (tests.yml plural)
- test_detect_ci_workflow_name_falls_back_to_python_app_yml (python-app.yml)
- test_detect_ci_workflow_name_falls_back_to_python_package_yml (python-package.yml)
- test_detect_ci_workflow_name_falls_back_to_ci_yaml (.yaml extension)
- test_detect_ci_workflow_name_yml_takes_priority_over_yaml (ci.yml > ci.yaml)
- test_detect_ci_workflow_name_all_candidates_are_publish_workflows → None
- test_detect_ci_workflow_name_ci_yml_is_publish_falls_back_to_test_yml
- test_detect_ci_workflow_name_nonexistent_directory_returns_none
- test_detect_ci_workflow_name_skips_unreadable_file
(OSError on read skipped via mock.patch; next candidate returned)
## _add_workflow_call_trigger: all remaining paths
- test_add_workflow_call_trigger_skips_when_already_present (idempotent)
- test_add_workflow_call_trigger_warns_when_no_on_block (WARN + no-op)
- test_add_workflow_call_trigger_warns_on_compact_on_format (WARN + no-op)
- test_add_workflow_call_trigger_handles_quoted_on_block ("on": form injected)
- test_add_workflow_call_trigger_returns_true_when_no_ci_found (graceful no-op)
## _create_publish_workflow: SHA pins, structural validity, params
- test_publish_yml_contains_sha_pinned_actions (all 5 SHA refs verified)
- test_publish_yml_structural_validity_with_ci (all job sections present)
- test_publish_yml_structural_validity_without_ci (no uses: ref when no CI)
- test_publish_yml_valid_yaml_with_pyyaml (skips if pyyaml not installed)
- test_publish_yml_uses_custom_python_version (python_version param)
- test_publish_yml_uses_custom_build_cmd (build_cmd param)
Total: 145 tests pass, 1 skipped (pyyaml optional) in test_pypi_readiness.py
Full suite: 222 passed, 1 skipped.
## pyproject.toml Add [dependency-groups] (PEP 735) with dev dependencies: pytest>=7.0 — test runner (was manually installed, not declared anywhere) pyyaml>=6.0 — YAML validation in tests (was missing, causing 1 skipped test) Neither is a runtime dependency of pyuvstarter itself: - pyuvstarter reads YAML files as plain text via regex (no import yaml at runtime) - pytest is only needed to run the test suite ## uv.lock Updated by `uv sync` to include pytest (9.0.2) and pyyaml (6.0.3) in the dev dependency group. Both are now resolved and locked. ## .github/workflows/ci.yml (Linux "Run unit tests" step) The previous implementation ran tests via `uv run "$test_file"` (executing each test_*.py as a plain Python script). Since test_pypi_readiness.py has no `if __name__ == "__main__"` block, ZERO tests were actually executed on Linux CI — all runs silently passed with no assertions checked. Replace the per-file loop with: uv run python -m pytest tests/ --junitxml=test_results_pytest.xml --tb=short This runs all 223 tests properly via pytest and produces JUnit XML for dorny/test-reporter. Windows CI already used this form (line 302). `uv sync` (run before tests at line 65) now installs the dev group automatically (uv includes all dependency-groups in sync by default), so pytest and pyyaml are available in CI without any additional install step. Result: test_publish_yml_valid_yaml_with_pyyaml now passes (was 1 skipped), full suite is 223 passed, 0 skipped.
pyuvstarter.py — 11 targeted fixes:
- C1: _prepare_pypi_metadata post-check calls _detect_ci_workflow_name() instead
of hardcoding ci.yml path, eliminating false WARN when test.yml is the CI workflow
- C3: _create_publish_workflow detects stale CI references when ci_filename is None
(regex r'uses:.+/.github/workflows/\S+\.ya?ml' triggers needs_update=True)
- H3: _is_publish_workflow Tier 2b regex requires >=2 spaces indentation
('^ +name:\s*(pypi|testpypi)') preventing false positive on top-level 'name: pypi'
- H4: _CI_WORKFLOW_CANDIDATES tuple expanded with 7 missing .yaml variants:
tests.yaml, build.yaml, main.yaml, workflows.yaml, python.yaml,
python-app.yaml, python-package.yaml
- H5: _create_releasing_doc gains ci_filename parameter; caller _prepare_pypi_metadata
passes detected_ci or 'ci.yml' so generated RELEASING.md names real CI workflow
- H6: _add_pypi_toml_metadata generates classifiers for all Python versions
from requires-python minimum up through 3.14 (loop over _KNOWN_PYTHON_VERSIONS)
- M2: _add_pypi_toml_metadata checks 'license-files' not in project (PEP 639)
before inserting license field, preventing duplicate license keys
- M3: _add_pypi_toml_metadata detects [tool.poetry] and logs WARN + returns True
instead of silently failing with 'Could not find [project] section' error
- M4: keywords generated as empty list with TODO comment instead of splitting
project name into low-quality word fragments
- M5: _create_publish_workflow backs up publish.yml.bak_TIMESTAMP before
regenerating, preserving any custom jobs the user added
- L2: _create_releasing_doc TestPyPI install command uses {pypi_name} not {name}
(prevents install failure when project name contains uppercase or underscores)
pyproject.toml — add missing PyPI publish metadata:
- license = {text = "Apache-2.0"} (matches LICENSE file content)
- keywords = ["uv", "python", "project-setup", "scaffolding", "pyproject", ...]
- classifiers with Development Status, License, OS, Python 3.11-3.13, Topic entries
- [project.urls] with Homepage, Repository, Issues, Changelog pointing to athundt/
- Removed template comments from version and requires-python lines
README.md — fix username and add feature docs:
- Replace all ahundt/pyuvstarter -> athundt/pyuvstarter (badges, clone/install URLs)
- Add 'Publishing to PyPI (--prepare-pypi)' section documenting what the flag
generates (LICENSE, README, pyproject metadata, publish.yml, RELEASING.md),
usage examples, and PyPI Trusted Publisher setup reference
.github/workflows/ci.yml — security and reliability:
- SHA-pin actions/setup-python@v5 -> @a26af69be951a213d495a4c3e4e4022e16d87065
- Add allow-prereleases: true to setup-python step
- Add Python 3.14 experimental: true matrix variable + continue-on-error
- Add timeout-minutes: 30 to test job to prevent 6-hour runner hangs
- Fix run_all_tests.sh: chmod before -x check (was silently skip if not executable)
tests/test_pypi_readiness.py — 13 new tests, 1 updated:
- test_prepare_pypi_no_spurious_ci_warning_with_test_yml (M6)
- test_is_publish_workflow_top_level_workflow_name_pypi_does_not_match (M7)
- test_is_publish_workflow_indented_environment_name_pypi_matches (M7)
- test_is_publish_workflow_two_space_indented_name_pypi_matches (M7)
- test_add_pypi_toml_metadata_skips_license_when_license_files_present (M8)
- test_add_pypi_toml_metadata_handles_poetry_format_gracefully (M9)
- test_detect_ci_workflow_name_falls_back_to_{tests,build,main,workflows,python,
python_app,python_package}_yaml x7 (M10)
- test_prepare_pypi_keywords_placeholder: updated to expect empty list + TODO comment
Total: 236 tests pass (was 223)
Add tests for all _CI_WORKFLOW_CANDIDATES that lacked coverage: - test_detect_ci_workflow_name_falls_back_to_main_yml - test_detect_ci_workflow_name_falls_back_to_workflows_yml - test_detect_ci_workflow_name_falls_back_to_test_yaml - test_detect_ci_workflow_name_falls_back_to_workflow_yaml All 4 use the _make_ci_wf_dir() helper defined in the same file. Total: 163 tests in test_pypi_readiness.py (was 159). All pass in 0.55s.
Progress bar (P1): - pyuvstarter.py:1425-1440: replace ncols=80 with dynamic_ncols=True (tqdm re-queries shutil.get_terminal_size() on each refresh; adapts to terminal width and resize) - Add disable=not _is_tty where _is_tty = sys.stderr.isatty() and not NO_COLOR; zero tqdm output when stderr is piped to AI tools, file redirects, or CI without PTY — prevents ANSI noise flooding AI context windows uv consistency (P3, P4, P8): - README.md:418-425: bare python -m pytest -> uv run python -m pytest - pyuvstarter.py:420-437: uv add first in typer/pydantic install error messages (uv pip install as fallback, bare pip removed) - pyuvstarter.py:66-87: module docstring python pyuvstarter.py -> pyuvstarter / uv run pyuvstarter - pyuvstarter.py:287-320: version error messages use uv run pyuvstarter instead of uv run python pyuvstarter.py Shell test runner (P2): - tests/run_all_tests.sh:285: uv run 'tests/$test' (script mode, 0 tests collected) -> uv run python -m pytest 'tests/$test' -v (pytest mode) Non-GitHub remote guard (P5): - pyuvstarter.py:4960-4970: _detect_github_owner_repo now checks "github.com" not in url early; returns (None, None) + WARN for GitLab, Bitbucket, Gitea — prevents wrong github.com URLs in generated [project.urls] Windows CI exit codes (P6): - .github/workflows/ci.yml:284-291: add $LASTEXITCODE checks after pyuvstarter --help and uv tool run pyuvstarter . in Windows step Generated classifier TODO (P7): - pyuvstarter.py:5228-5235: prepend "# TODO: Update Development Status..." comment above classifiers block in generated pyproject.toml New tests (8 tests in tests/test_pypi_readiness.py): - test_tqdm_disabled_when_stderr_not_tty: AST check for isatty + disable=not - test_tqdm_has_dynamic_ncols_not_hardcoded_ncols: AST check for dynamic_ncols - test_tqdm_disabled_when_no_color_set: AST check for NO_COLOR guard - test_detect_github_owner_repo_github_ssh_url: git@github.com SSH parse - test_detect_github_owner_repo_github_https_url: https://github.com parse - test_detect_github_owner_repo_non_github_returns_none: GitLab -> (None,None) - test_detect_github_owner_repo_bitbucket_returns_none: Bitbucket -> (None,None) - test_generated_classifiers_include_development_status_todo_comment: TODO comment present All 171 tests pass (tests/test_pypi_readiness.py).
…pytest test_extraction_fix.py is a legacy script-mode test (has main() and __main__ block, zero pytest-style test functions). The previous commit changed the runner to use uv run python -m pytest for all PYTHON_TESTS, which caused pytest to exit with code 5 (no tests collected) for this file, making run_all_tests.sh report it as FAILED. Fix: split into two lists in run_all_tests.sh: - PYTHON_TESTS: pytest-based files -> uv run python -m pytest 'tests/$test' - SCRIPT_TESTS: script-mode files -> uv run python 'tests/$test' test_extraction_fix.py moved to SCRIPT_TESTS. This restores CI green on macos-latest py3.13 and ubuntu-latest py3.14, where run_all_tests.sh was failing with COMPREHENSIVE_EXIT_CODE=1.
These files have a main()/__main__ block but zero pytest-style def test_* functions, so uv run python -m pytest exits with code 5 (no tests collected) causing run_all_tests.sh to report them as FAILED: - test_import_fixing.py - test_jupyter_pipeline.py - test_mixed_package_availability.py - test_wheel_unavailability.py - test_utils.py Moved to SCRIPT_TESTS (run via uv run python tests/$test). Remaining in PYTHON_TESTS (have def test_* functions, run via pytest): - test_dependency_migration.py (1 pytest test) - test_project_structure.py (1 pytest test) - test_configuration.py (9 pytest tests) - test_cross_platform.py (8 pytest tests) - test_error_handling.py (12 pytest tests)
Two failures in dorny/test-reporter Publish Test Results step: 1. Windows: step runs before Run unit tests (Windows) in step order, so test_results_pytest_windows.xml does not exist yet. dorny default fail-on-empty: true causes the step to fail. Fix: add fail-on-empty: false. 2. macOS: test_results.xml generated by create_demo2.sh contains unquoted attribute values (invalid XML). dorny fails with "Error: Unquoted attribute value" even with fail-on-error: false because that option only covers test assertion failures, not XML parse errors. Fix: narrow path glob from test_results*.xml to test_results_pytest*.xml so only valid pytest-generated JUnit XML (test_results_pytest.xml, test_results_pytest_windows.xml) is published. Excludes test_results.xml from create_demo2.sh.
Three AST-based tqdm tests use open("pyuvstarter.py").read() which
on Windows defaults to cp1252 encoding. pyuvstarter.py contains UTF-8
emoji characters (0x81, etc.) that cp1252 cannot decode, causing:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x81
Failing tests on Windows:
- test_tqdm_disabled_when_stderr_not_tty
- test_tqdm_has_dynamic_ncols_not_hardcoded_ncols
- test_tqdm_disabled_when_no_color_set
Fix: open("pyuvstarter.py", encoding="utf-8") in all three tests.
Two pre-existing Windows CI failures fixed: 1. test_utils.py:370 subprocess.run(..., text=True) uses Windows default encoding cp1252 to decode pyuvstarter stdout/stderr. pyuvstarter emits emoji output (🚀, ✅, ❌, 📁, etc.) which cp1252 cannot decode, causing UnicodeDecodeError → CompletedProcess.stdout=None → AttributeError: 'NoneType' object has no attribute 'lower' in 12 tests: test_configuration.py (7), test_dependency_migration.py (1), test_jupyter_pipeline.py (3), test_project_structure.py (1). Fix: add encoding="utf-8" to subprocess.run(). 2. test_cross_platform.py:457 test_special_characters_in_filenames creates file"with"double"quotes.py which is illegal on Windows NTFS (double-quote forbidden in filenames), causing OSError: [Errno 22] Invalid argument. Fix: remove the double-quote filename from the list; add Windows runtime filter for any remaining NTFS-forbidden characters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--prepare-pypiflag: new command that adds PyPI metadata topyproject.toml(license, classifiers, keywords, URLs, description) and generates a SHA-pinnedpublish.ymlOIDC workflow with aworkflow_calltrigger wired into the existing CI fileLICENSE/LICENSE.txtand maps to SPDX identifier (Apache-2.0,MIT,GPL-3.0-only, etc.) forlicense = {text = ...}and the matching PyPI classifier_is_publish_workflow(); skips publish workflows when searching for the CI file to injectworkflow_callinto; uses SHA-pinned action refs in generated templates_detect_github_owner_reponow returns(None, None)+ WARN for GitLab/Bitbucket/Gitea instead of generating wronggithub.commetadata URLsdynamic_ncols=True(adapts to terminal width on each refresh) +disable=not _is_ttywhere_is_tty = sys.stderr.isatty() and not NO_COLOR; zero tqdm output when stderr is piped to AI tools, CI log capture, or file redirects — prevents ANSI noise flooding AI context windowsuv run python -m pytesteverywhere (README, run_all_tests.sh, error messages);uv addfirst in install guidance; module docstring updated frompython pyuvstarter.pytopyuvstarter/uv run pyuvstartertests/run_all_tests.shwas running test files in script mode (0 tests collected); now usesuv run python -m pytest 'tests/$test' -vactions/setup-python, Python 3.14 experimental matrix,timeout-minutes: 30, Windows$LASTEXITCODEchecks,allow-prereleases: truetests/test_pypi_readiness.pycovering all new functionality (0 failures)Files changed
pyuvstarter.py--prepare-pypifeature, license detection, tqdm fix, uv-first error messages, non-GitHub guard, classifier TODO commenttests/test_pypi_readiness.py--prepare-pypi, CI detection, publish detection, tqdm, GitHub URL parsing.github/workflows/ci.yml.github/workflows/publish.ymlpyproject.tomlREADME.md--prepare-pypidocumentation, uv run pytest, correct repo URLsRELEASING.mdtests/run_all_tests.shuv run python -m pytestTest plan
uv run python -m pytest tests/test_pypi_readiness.py -q→ 171 passedpyuvstarter --prepare-pypigenerates validpyproject.tomlwith license, classifiers, URLsstderris piped (pyuvstarter 2>/tmp/out.txt; wc -c /tmp/out.txt→ minimal bytes)USERNAMEplaceholder in URLs, no wronggithub.commetadata