diff --git a/.github/workflows/check_pr_title.yaml b/.github/workflows/check_pr_title.yaml new file mode 100644 index 0000000..73298ca --- /dev/null +++ b/.github/workflows/check_pr_title.yaml @@ -0,0 +1,14 @@ +name: Check PR title + +on: + pull_request_target: + types: [opened, edited, synchronize] + +jobs: + check_pr_title: + name: Check PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5.5.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/check_version_availability.yaml b/.github/workflows/check_version_availability.yaml deleted file mode 100644 index f408b2f..0000000 --- a/.github/workflows/check_version_availability.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: Check package version availability - -on: - workflow_call: - -jobs: - check_version_availability: - name: Check version availability - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.8" - - - name: Install dependencies - run: make install-dev - - - name: Check version availability - run: make check-version-availability diff --git a/.github/workflows/lint_and_type_checks.yaml b/.github/workflows/lint_and_type_checks.yaml deleted file mode 100644 index 9519410..0000000 --- a/.github/workflows/lint_and_type_checks.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: Lint and type checks - -on: - workflow_call: - -jobs: - lint_and_type_checks: - name: Lint and type checks - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: make install-dev - - - name: Run lint - run: make lint - - - name: Run type checks - run: make type-check diff --git a/.github/workflows/pre_release.yaml b/.github/workflows/pre_release.yaml new file mode 100644 index 0000000..2726dfb --- /dev/null +++ b/.github/workflows/pre_release.yaml @@ -0,0 +1,65 @@ +name: Create a pre-release + +on: + # Trigger a beta version release (pre-release) on push to the master branch. + push: + branches: + - master + tags-ignore: + - "**" # Ignore all tags to prevent duplicate builds when tags are pushed. + + # Or it can be triggered manually. + workflow_dispatch: + +jobs: + release_metadata: + if: "!startsWith(github.event.head_commit.message, 'docs') && !startsWith(github.event.head_commit.message, 'ci') && startsWith(github.repository, 'apify/')" + name: Prepare release metadata + runs-on: ubuntu-latest + outputs: + version_number: ${{ steps.release_metadata.outputs.version_number }} + tag_name: ${{ steps.release_metadata.outputs.tag_name }} + changelog: ${{ steps.release_metadata.outputs.changelog }} + existing_changelog_path: CHANGELOG.md + steps: + - uses: apify/workflows/git-cliff-release@main + id: release_metadata + name: Prepare release metadata + with: + release_type: prerelease + + run_code_checks: + name: Run code checks + uses: ./.github/workflows/run_code_checks.yaml + + update_changelog: + name: Update changelog + needs: [release_metadata, run_code_checks] + uses: apify/workflows/.github/workflows/python_bump_and_update_changelog.yaml@main + with: + version_number: ${{ needs.release_metadata.outputs.version_number }} + changelog: ${{ needs.release_metadata.outputs.changelog }} + secrets: + APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + publish_to_pypi: + name: Publish to PyPI + needs: [release_metadata, update_changelog] + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write # Required for OIDC authentication. + environment: + name: pypi + url: https://pypi.org/project/apify-shared + steps: + - name: Prepare distribution + uses: apify/workflows/prepare-pypi-distribution@main + with: + package_name: apify-shared + is_prerelease: "yes" + version_number: ${{ needs.release_metadata.outputs.version_number }} + ref: ${{ needs.update_changelog.outputs.changelog_commitish }} + # Publishes the package to PyPI using PyPA official GitHub action with OIDC authentication. + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5224c1b..91f04ad 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,116 +1,91 @@ -name: Check & Release +name: Create a release on: - # Push to master will publish a beta version - push: - branches: - - master - tags-ignore: - - "**" - # A release via GitHub releases will publish a stable version - release: - types: [published] - # Workflow dispatch will publish whatever you choose + # Trigger a stable version release via GitHub's UI, with the ability to specify the type of release. workflow_dispatch: inputs: release_type: description: Release type required: true type: choice - default: alpha + default: auto options: - - alpha - - beta - - final + - auto + - custom + - patch + - minor + - major + custom_version: + description: The custom version to bump to (only for "custom" type) + required: false + type: string + default: "" jobs: - lint_and_type_checks: - name: Run lint and type_checks - uses: ./.github/workflows/lint_and_type_checks.yaml - - unit_tests: - name: Run unit tests - uses: ./.github/workflows/unit_tests.yaml + release_metadata: + name: Prepare release metadata + runs-on: ubuntu-latest + outputs: + version_number: ${{ steps.release_metadata.outputs.version_number }} + tag_name: ${{ steps.release_metadata.outputs.tag_name }} + changelog: ${{ steps.release_metadata.outputs.changelog }} + release_notes: ${{ steps.release_metadata.outputs.release_notes }} + steps: + - uses: apify/workflows/git-cliff-release@main + name: Prepare release metadata + id: release_metadata + with: + release_type: ${{ inputs.release_type }} + custom_version: ${{ inputs.custom_version }} + existing_changelog_path: CHANGELOG.md + + run_code_checks: + name: Run code checks + uses: ./.github/workflows/run_code_checks.yaml + + update_changelog: + name: Update changelog + needs: [release_metadata, run_code_checks] + uses: apify/workflows/.github/workflows/python_bump_and_update_changelog.yaml@main + with: + version_number: ${{ needs.release_metadata.outputs.version_number }} + changelog: ${{ needs.release_metadata.outputs.changelog }} + secrets: + APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} + + create_github_release: + name: Create github release + needs: [release_metadata, update_changelog] + runs-on: ubuntu-latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Create release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ needs.release_metadata.outputs.tag_name }} + name: ${{ needs.release_metadata.outputs.version_number }} + target_commitish: ${{ needs.update_changelog.outputs.changelog_commitish }} + body: ${{ needs.release_metadata.outputs.release_notes }} publish_to_pypi: name: Publish to PyPI - needs: [lint_and_type_checks, unit_tests] + needs: [release_metadata, update_changelog] runs-on: ubuntu-latest permissions: contents: write - id-token: write + id-token: write # Required for OIDC authentication. environment: name: pypi - url: https://pypi.org/p/apify-shared - + url: https://pypi.org/project/apify-shared steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 + - name: Prepare distribution + uses: apify/workflows/prepare-pypi-distribution@main with: - python-version: 3.8 - - - name: Install dependencies - run: make install-dev - - - # Determine if this is a prerelease or latest release - name: Determine release type - id: get-release-type - run: | - if [ ${{ github.event_name }} = release ]; then - release_type="final" - elif [ ${{ github.event_name }} = push ]; then - release_type="beta" - elif [ ${{ github.event_name }} = workflow_dispatch ]; then - release_type=${{ github.event.inputs.release_type }} - fi - - if [ ${release_type} = final ]; then - docker_image_tag="latest" - elif [ ${release_type} = beta ]; then - docker_image_tag="beta" - else - docker_image_tag="" - fi - - echo "release_type=${release_type}" >> $GITHUB_OUTPUT - echo "docker_image_tag=${docker_image_tag}" >> $GITHUB_OUTPUT - - - # Check whether the released version is listed in CHANGELOG.md - name: Check whether the released version is listed in the changelog - if: steps.get-release-type.outputs.release_type != 'alpha' - run: make check-changelog-entry - - - # Check version consistency and increment pre-release version number for prereleases (must be the last step before build) - name: Bump pre-release version - if: steps.get-release-type.outputs.release_type != 'final' - run: python ./scripts/update_version_for_prerelease.py ${{ steps.get-release-type.outputs.release_type }} - - - # Build a source distribution and a python3-only wheel - name: Build distribution files - run: make build - - - # Check whether the package description will render correctly on PyPI - name: Check package rendering on PyPI - run: make twine-check - - - # Publish package to PyPI using their official GitHub action - name: Publish package to PyPI + package_name: apify-shared + is_prerelease: "" + version_number: ${{ needs.release_metadata.outputs.version_number }} + ref: ${{ needs.update_changelog.outputs.changelog_commitish }} + # Publishes the package to PyPI using PyPA official GitHub action with OIDC authentication. + - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - - - # Tag the current commit with the version tag if this is not made from the release event (releases are tagged with the release process) - name: Tag Version - if: github.event_name != 'release' - run: | - git_tag=v`python ./scripts/print_current_package_version.py` - git tag $git_tag - git push origin $git_tag - - - # Upload the build artifacts to the release - name: Upload the build artifacts to release - if: github.event_name == 'release' - run: gh release upload ${{ github.ref_name }} dist/* - env: - GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/run_checks.yaml b/.github/workflows/run_checks.yaml deleted file mode 100644 index ba8d7fa..0000000 --- a/.github/workflows/run_checks.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Code quality checks - -on: - pull_request: - -jobs: - check_version_availability: - name: Check version availability - uses: ./.github/workflows/check_version_availability.yaml - - lint_and_type_checks: - name: Run lint and type checks - uses: ./.github/workflows/lint_and_type_checks.yaml - - unit_tests: - name: Run unit tests - needs: [lint_and_type_checks] - uses: ./.github/workflows/unit_tests.yaml diff --git a/.github/workflows/run_code_checks.yaml b/.github/workflows/run_code_checks.yaml new file mode 100644 index 0000000..ae43e51 --- /dev/null +++ b/.github/workflows/run_code_checks.yaml @@ -0,0 +1,18 @@ +name: Run code checks + +on: + # Trigger code checks on opening a new pull request. + pull_request: + +jobs: + lint_check: + name: Lint check + uses: apify/workflows/.github/workflows/python_lint_check.yaml@main + + type_check: + name: Type check + uses: apify/workflows/.github/workflows/python_type_check.yaml@main + + unit_tests: + name: Unit tests + uses: apify/workflows/.github/workflows/python_unit_tests.yaml@main diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml deleted file mode 100644 index 585011b..0000000 --- a/.github/workflows/unit_tests.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Unit tests - -on: - workflow_call: - -jobs: - unit_tests: - name: Run unit tests - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: make install-dev - - - name: Run unit tests - run: make unit-tests diff --git a/.github/workflows/update_new_issue.yaml b/.github/workflows/update_new_issue.yaml new file mode 100644 index 0000000..1b65db0 --- /dev/null +++ b/.github/workflows/update_new_issue.yaml @@ -0,0 +1,25 @@ +name: Update new issue + +on: + issues: + types: + - opened + +jobs: + label_issues: + name: Label issues + runs-on: ubuntu-latest + permissions: + issues: write + + steps: + # Add the "t-tooling" label to all new issues + - uses: actions/github-script@v7 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["t-tooling"] + }) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6e596e3..406af37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,123 +1,107 @@ # Development +Here you'll find a contributing guide to get started with development. + ## Environment -For local development, it is required to have Python 3.8 (or a later version) installed. +For local development, it is required to have Python 3.9 (or a later version) installed. + +We use [uv](https://docs.astral.sh/uv/) for project management. Install it and set up your IDE accordingly. -It is recommended to set up a virtual environment while developing this package to isolate your development environment, -however, due to the many varied ways Python can be installed and virtual environments can be set up, -this is left up to the developers to do themselves. +## Dependencies -One recommended way is with the built-in `venv` module: +To install this package and its development dependencies, run: -```bash -python3 -m venv .venv -source .venv/bin/activate +```sh +make install-dev ``` -To improve on the experience, you can use [pyenv](https://github.com/pyenv/pyenv) to have an environment with a pinned Python version, -and [direnv](https://github.com/direnv/direnv) to automatically activate/deactivate the environment when you enter/exit the project folder. +## Code checking -## Dependencies +To execute all code checking tools together, run: -To install this package and its development dependencies, run `make install-dev`. +```sh +make check-code +``` -## Code checking +### Linting -To run all our code checking tools together, just run `make check-code`. +We utilize [ruff](https://docs.astral.sh/ruff/) for linting, which analyzes code for potential issues and enforces consistent style. Refer to `pyproject.toml` for configuration details. -### Linting +To run linting: -We use [ruff](https://docs.astral.sh/ruff/) for linting to to analyze the code for potential issues and enforce -uniformed code style. See the `pyproject.toml` for its configuration. To run the linting, just run `make lint`. +```sh +make lint +``` ### Formatting -We use [ruff](https://docs.astral.sh/ruff/) for automated code formatting. It formats the code to follow uniformed -code style and addresses auto-fixable linting issues. See the `pyproject.toml` for its configuration. To run -the formatting, just run `make format`. +Our automated code formatting also leverages [ruff](https://docs.astral.sh/ruff/), ensuring uniform style and addressing fixable linting issues. Configuration specifics are outlined in `pyproject.toml`. -### Type checking +To run formatting: + +```sh +make format +``` -We use [mypy](https://mypy.readthedocs.io/en/stable/) for type checking. See the `mypy.ini` for its configuration. -To run the type checking, just run `make type-check`. +### Type checking -### Unit tests +Type checking is handled by [mypy](https://mypy.readthedocs.io/), verifying code against type annotations. Configuration settings can be found in `pyproject.toml`. -We use [pytest](https://docs.pytest.org/) as a testing framework with many plugins. See the `pyproject.toml` for -both its configuration and the list of installed plugins. To run unit tests execute `make unit-tests`. To run unit -tests with HTML coverage report execute `make unit-tests-cov`. +To run type checking: -## Release process +```sh +make type-check +``` -Publishing new versions to [PyPI](https://pypi.org/project/apify-shared) happens automatically through GitHub Actions. +### Unit tests -On each commit to the `master` branch, a new beta release is published, taking the version number from `pyproject.toml` -and automatically incrementing the beta version suffix by 1 from the last beta release published to PyPI. +We employ pytest as our testing framework, equipped with various plugins. Check pyproject.toml for configuration details and installed plugins. -A stable version is published when a new release is created using GitHub Releases, again taking the version number from `pyproject.toml`. -The built package assets are automatically uploaded to the GitHub release. +We use [pytest](https://docs.pytest.org/) as a testing framework with many plugins. Check `pyproject.toml` for configuration details and installed plugins. -If there is already a stable version with the same version number as in `pyproject.toml` published to PyPI, the publish process fails, -so don't forget to update the version number before releasing a new version. -The release process also fails when the released version is not described in `CHANGELOG.md`, -so don't forget to describe the changes in the new version there. +To run unit tests: -### Beta release checklist +```sh +make unit-tests +``` -Beta release happens automatically after you merge a pull request or add a direct commit to the master branch. Before you do that check the following: +To run unit tests with HTML coverage report: -- Make sure that in the [pyproject.toml](./pyproject.toml) a project version is set to the latest non-published version. -- Describe your changes to the [CHANGELOG.md](./CHANGELOG.md) in the section with the latest non-published version. +```sh +make unit-tests-cov +``` -### Production release checklist +## Release process -Production release happens after the GitHub release is created. Before you do that check the following: +Publishing new versions to [PyPI](https://pypi.org/project/apify) is automated through GitHub Actions. -- Make sure [here](https://pypi.org/project/apify-shared/#history) that the beta release with the latest commit is successfully deployed. -- Make sure that all changes that happened from the last production release are described in [CHANGELOG.md](./CHANGELOG.md) (it's okay to skip DX related changes, repo setup etc). -- When drafting a new GitHub release: - - Create a new tag in the format of `v1.2.3` targeting the master branch. - - Fill in the release title in the format of `1.2.3`. - - Copy the changes from the [CHANGELOG.md](./CHANGELOG.md) and paste them into the release description. Make sure that all changes are properly categorized using headlines (`Added`, `Fixed` or `Internal changes`). - - Check the "Set as the latest release" option. +- **Beta releases**: On each commit to the master branch, a new beta release is automatically published. The version number is determined based on the latest release and conventional commits. The beta version suffix is incremented by 1 from the last beta release on PyPI. +- **Stable releases**: A stable version release may be created by triggering the `release` GitHub Actions workflow. The version number is determined based on the latest release and conventional commits (`auto` release type), or it may be overriden using the `custom` release type. -Currently, there is no explicit approval process, so when you are done with the checklist, proceed with the release. +### Publishing to PyPI manually -Once released, manually bump the version in `pyproject.toml` ([commit example](https://github.com/apify/apify-shared-python/commit/24a269bcf046df7202a8652ee788ffe9a461e58b)). +1. **Do not do this unless absolutely necessary.** In all conceivable scenarios, you should use the `release` workflow instead. +2. **Make sure you know what you're doing.** -## Maintenance +3. Update the version number: -### Removing Support for an outdated Python version +- Modify the `version` field under `project` in `pyproject.toml`. -- Todo: Fill in once Python 3.8 is deprecated. +```toml +[project] +name = "apify" +version = "x.z.y" +``` -### Adding support for a new Python version +4. Generate the distribution archives for the package: -1) Firstly, ensure that the package ( - [apify-sdk-python](https://github.com/apify/apify-sdk-python), - [apify-client-python](https://github.com/apify/apify-client-python), - [apify-shared-python](https://github.com/apify/apify-shared-python) -) is compatible with the new Python version. Both in our code base and -the dependencies we use. Then, release a new version of the package. - - For inspiration, see the PR - [apify/apify-sdk-python#121](https://github.com/apify/apify-sdk-python/pull/121), - where support for Python 3.12 was added to the Apify Python SDK. +```shell +uv build +``` -2) Next, build and publish the new versions of Python base Docker images. - - For inspiration, see the PR - [apify/apify-actor-docker#112](https://github.com/apify/apify-actor-docker/pull/112), - where support for Python 3.12 was added. - - Apify base Docker images are built using GitHub Actions, accessible at - [apify/apify-actor-docker/actions](https://github.com/apify/apify-actor-docker/actions). +5. Set up the PyPI API token for authentication and upload the package to PyPI: -3) Integrate the new Python version into the CI/CD workflows -of existing Python projects ( - [apify-sdk-python](https://github.com/apify/apify-sdk-python), - [apify-client-python](https://github.com/apify/apify-client-python), - [apify-shared-python](https://github.com/apify/apify-shared-python), - [actor-templates](https://github.com/apify/actor-templates) -). - - For inspiration, see the PR - [apify/apify-sdk-python#124](https://github.com/apify/apify-sdk-python/pull/124), - where support for Python 3.12 was added to the CI/CD of the Apify Python SDK. +```shell +uv publish --token YOUR_API_TOKEN +``` diff --git a/Makefile b/Makefile index bc6b2ac..5e38931 100644 --- a/Makefile +++ b/Makefile @@ -1,44 +1,38 @@ -.PHONY: clean install-dev build publish twine-check lint unit-tests type-check check-code format check-version-availability check-changelog-entry +.PHONY: clean install-dev build publish-to-pypi lint unit-tests unit-tests-cov \ + type-check check-code format -DIRS_WITH_CODE = src tests scripts +DIRS_WITH_CODE = src tests clean: - rm -rf build dist .mypy_cache .pytest_cache .ruff_cache src/*.egg-info __pycache__ + rm -rf build dist .mypy_cache .pytest_cache .ruff_cache src/*.egg-info __pycache__ htmlcov .coverage install-dev: - python3 -m pip install --upgrade pip - pip install --no-cache-dir -e ".[dev]" - pre-commit install + uv sync --all-extras + uv run pre-commit install build: - python3 -m build + uv build --verbose -publish: - python3 -m twine upload dist/* - -twine-check: - python3 -m twine check dist/* +# APIFY_PYPI_TOKEN_CRAWLEE is expected to be set in the environment +publish-to-pypi: + uv publish --verbose --token "${APIFY_PYPI_TOKEN_CRAWLEE}" lint: - python3 -m ruff check $(DIRS_WITH_CODE) + uv run ruff format --check $(DIRS_WITH_CODE) + uv run ruff check $(DIRS_WITH_CODE) unit-tests: - python3 -m pytest -n auto -ra tests/unit --cov=src/apify_shared + uv run pytest --numprocesses=auto --verbose --cov=src/apify_shared tests/unit unit-tests-cov: - python3 -m pytest -n auto -ra tests/unit --cov=src/apify_shared --cov-report=html + uv run pytest --numprocesses=auto --verbose --cov=src/apify_shared --cov-report=html tests/unit type-check: - python3 -m mypy $(DIRS_WITH_CODE) + uv run mypy $(DIRS_WITH_CODE) +# The check-code target runs a series of checks equivalent to those performed by pre-commit hooks check-code: lint type-check unit-tests format: - python3 -m ruff check --fix $(DIRS_WITH_CODE) - python3 -m ruff format $(DIRS_WITH_CODE) - -check-version-availability: - python scripts/check_version_availability.py - -check-changelog-entry: - python scripts/check_version_in_changelog.py + uv run ruff check --fix $(DIRS_WITH_CODE) + uv run ruff format $(DIRS_WITH_CODE) diff --git a/scripts/check_version_availability.py b/scripts/check_version_availability.py deleted file mode 100755 index 372d321..0000000 --- a/scripts/check_version_availability.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -from utils import get_current_package_version, get_published_package_versions - -# Checks whether the current package version number was not already used in a published release. -if __name__ == '__main__': - current_version = get_current_package_version() - - # Load the version numbers of the currently published versions from PyPI - published_versions = get_published_package_versions() - - # We don't want to try to publish a version with the same version number as an already released stable version - if current_version in published_versions: - raise RuntimeError(f'The current version {current_version} was already released!') diff --git a/scripts/check_version_in_changelog.py b/scripts/check_version_in_changelog.py deleted file mode 100755 index 1bc6cba..0000000 --- a/scripts/check_version_in_changelog.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -import re - -from utils import REPO_ROOT, get_current_package_version - -CHANGELOG_PATH = REPO_ROOT / 'CHANGELOG.md' - -# Checks whether the current package version has an entry in the CHANGELOG.md file -if __name__ == '__main__': - current_package_version = get_current_package_version() - - if not CHANGELOG_PATH.is_file(): - raise RuntimeError('Unable to find CHANGELOG.md file') - - with open(CHANGELOG_PATH, encoding='utf-8') as changelog_file: - for line in changelog_file: - # Ensure that the heading in the changelog entry for the specified version includes a version number - # enclosed in square brackets. This version number is formatted as a link to the corresponding - # version tag on GitHub. - if re.match(rf'## \[{current_package_version}\].*$', line): - break - else: - raise RuntimeError( - f'There is no entry in the changelog for the current package version ({current_package_version})' - ) diff --git a/scripts/print_current_package_version.py b/scripts/print_current_package_version.py deleted file mode 100755 index 9cff474..0000000 --- a/scripts/print_current_package_version.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -from utils import get_current_package_version - -# Print the current package version from the pyproject.toml file to stdout -if __name__ == '__main__': - print(get_current_package_version(), end='') diff --git a/scripts/update_version_for_prerelease.py b/scripts/update_version_for_prerelease.py deleted file mode 100755 index 75886db..0000000 --- a/scripts/update_version_for_prerelease.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import annotations - -import re -import sys - -from utils import get_current_package_version, get_published_package_versions, set_current_package_version - -# Checks whether the current package version number was not already used in a published release, -# and if not, modifies the package version number in pyproject.toml -# from a stable release version (X.Y.Z) to a prerelease version (X.Y.ZbN or X.Y.Z.aN or X.Y.Z.rcN) -if __name__ == '__main__': - if len(sys.argv) != 2: - raise RuntimeError('You must pass the prerelease type as an argument to this script!') - - prerelease_type = sys.argv[1] - if prerelease_type not in ['alpha', 'beta', 'rc']: - raise RuntimeError(f'The prerelease type must be one of "alpha", "beta" or "rc", got "{prerelease_type}"!') - - if prerelease_type == 'alpha': - prerelease_prefix = 'a' - elif prerelease_type == 'beta': - prerelease_prefix = 'b' - elif prerelease_type == 'rc': - prerelease_prefix = 'rc' - - current_version = get_current_package_version() - - # We can only transform a stable release version (X.Y.Z) to a prerelease version (X.Y.ZxxxN) - if not re.match(r'^\d+\.\d+\.\d+$', current_version): - raise RuntimeError( - f'The current version {current_version} does not match the proper semver format for stable releases (X.Y.Z)' - ) - - # Load the version numbers of the currently published versions from PyPI - published_versions = get_published_package_versions() - - # We don't want to publish a prerelease version with the same version number as an already released stable version - if current_version in published_versions: - raise RuntimeError(f'The current version {current_version} was already released!') - - # Find the highest prerelease version number that was already published - latest_prerelease = 0 - for version in published_versions: - if version.startswith(f'{current_version}{prerelease_prefix}'): - prerelease_version = int(version.split(prerelease_prefix)[1]) - if prerelease_version > latest_prerelease: - latest_prerelease = prerelease_version - - # Write the latest prerelease version number to pyproject.toml - new_prerelease_version_number = f'{current_version}{prerelease_prefix}{latest_prerelease + 1}' - set_current_package_version(new_prerelease_version_number) diff --git a/scripts/utils.py b/scripts/utils.py deleted file mode 100644 index 2d301b9..0000000 --- a/scripts/utils.py +++ /dev/null @@ -1,56 +0,0 @@ -from __future__ import annotations - -import json -import pathlib -from urllib.error import HTTPError -from urllib.request import urlopen - -PACKAGE_NAME = 'apify_shared' -REPO_ROOT = pathlib.Path(__file__).parent.resolve() / '..' -PYPROJECT_TOML_FILE_PATH = REPO_ROOT / 'pyproject.toml' - - -# Load the current version number from pyproject.toml -# It is on a line in the format `version = "1.2.3"` -def get_current_package_version() -> str: - with open(PYPROJECT_TOML_FILE_PATH, encoding='utf-8') as pyproject_toml_file: - for line in pyproject_toml_file: - if line.startswith('version = '): - delim = '"' if '"' in line else "'" - return line.split(delim)[1] - else: # noqa: PLW0120 - raise RuntimeError('Unable to find version string.') - - -# Write the given version number from pyproject.toml -# It replaces the version number on the line with the format `version = "1.2.3"` -def set_current_package_version(version: str) -> None: - with open(PYPROJECT_TOML_FILE_PATH, 'r+', encoding='utf-8') as pyproject_toml_file: - updated_pyproject_toml_file_lines = [] - version_string_found = False - for line in pyproject_toml_file: - if line.startswith('version = '): - version_string_found = True - line = f'version = "{version}"\n' # noqa: PLW2901 - updated_pyproject_toml_file_lines.append(line) - - if not version_string_found: - raise RuntimeError('Unable to find version string.') - - pyproject_toml_file.seek(0) - pyproject_toml_file.write(''.join(updated_pyproject_toml_file_lines)) - pyproject_toml_file.truncate() - - -# Load the version numbers of the currently published versions from PyPI -def get_published_package_versions() -> list: - package_info_url = f'https://pypi.org/pypi/{PACKAGE_NAME}/json' - try: - package_data = json.load(urlopen(package_info_url)) # noqa: S310 - published_versions = list(package_data['releases'].keys()) - # If the URL returns 404, it means the package has no releases yet (which is okay in our case) - except HTTPError as exc: - if exc.code != 404: - raise - published_versions = [] - return published_versions