diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..461254e9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a bug report to help us improve semver +title: '' +labels: bug +assignees: '' + +--- + + + +# Situation + + +# To Reproduce + + +# Expected Behavior + + +# Environment +- OS: [e.g. Linux, MacOS, Windows, ...] +- Python version [e.g. 3.6, 3.7, ...] +- Version of semver library [e.g. 3.0.0] + +# Additional context + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..640cced4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Community Support + url: https://github.com/python-semver/python-semver/discussions + about: Ask and answer questions in our discussion forum. + - name: Documentation + url: https://python-semver.readthedocs.io/ + about: Find more information in our documentation. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..5a24681d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + + + +# Situation + + +# Possible Solution/Idea + + + +# Additional context + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0c5d1c56 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + day: "friday" + labels: + - "enhancement" + commit-message: + prefix: "pip" + # Allow up to 10 open pull requests for pip dependencies + open-pull-requests-limit: 5 diff --git a/.github/workflows/black-formatting.yml b/.github/workflows/black-formatting.yml deleted file mode 100644 index 25b34f21..00000000 --- a/.github/workflows/black-formatting.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Black Formatting - -on: [pull_request] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: Output env variables - run: | - echo "GITHUB_WORKFLOW=${GITHUB_WORKFLOW}" - echo "GITHUB_ACTION=$GITHUB_ACTION" - echo "GITHUB_ACTIONS=$GITHUB_ACTIONS" - echo "GITHUB_ACTOR=$GITHUB_ACTOR" - echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" - echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME" - echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH" - echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" - echo "GITHUB_SHA=$GITHUB_SHA" - echo "GITHUB_REF=$GITHUB_REF" - echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF" - echo "GITHUB_BASE_REF=$GITHUB_BASE_REF" - echo "::debug::---Start content of file $GITHUB_EVENT_PATH" - cat $GITHUB_EVENT_PATH - echo "\n" - echo "::debug::---end" - - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip black - - - name: Run black - id: black - run: | - black --check . - echo "::set-output name=rc::$?" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..bff8173b --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master, maint/v2 ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '50 16 * * 5' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/python-testing.yml b/.github/workflows/python-testing.yml new file mode 100644 index 00000000..8f36dbc9 --- /dev/null +++ b/.github/workflows/python-testing.yml @@ -0,0 +1,66 @@ +--- +name: Python + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Output env variables + run: | + echo "Default branch=${default-branch}" + echo "GITHUB_WORKFLOW=${GITHUB_WORKFLOW}" + echo "GITHUB_ACTION=$GITHUB_ACTION" + echo "GITHUB_ACTIONS=$GITHUB_ACTIONS" + echo "GITHUB_ACTOR=$GITHUB_ACTOR" + echo "GITHUB_REPOSITORY=$GITHUB_REPOSITORY" + echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME" + echo "GITHUB_EVENT_PATH=$GITHUB_EVENT_PATH" + echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" + echo "GITHUB_SHA=$GITHUB_SHA" + echo "GITHUB_REF=$GITHUB_REF" + echo "GITHUB_HEAD_REF=$GITHUB_HEAD_REF" + echo "GITHUB_BASE_REF=$GITHUB_BASE_REF" + echo "::debug::---Start content of file $GITHUB_EVENT_PATH" + cat $GITHUB_EVENT_PATH + echo "\n" + echo "::debug::---end" + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: 3.6 + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip install tox tox-gh-actions + - name: Check + run: | + tox -e checks + + tests: + needs: check + runs-on: ubuntu-latest + strategy: + max-parallel: 5 + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + + steps: + - uses: actions/checkout@v1 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip install tox tox-gh-actions + - name: Test with tox + run: | + tox diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 665ebd19..00000000 --- a/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -# config file for automatic testing at travis-ci.org -language: python -cache: pip - -before_install: - - sudo apt-get install -y python3-dev - -install: - - pip install --upgrade pip setuptools - - pip install virtualenv tox wheel - - tox --version - -script: tox -v - -matrix: - include: - - - python: "3.6" - env: TOXENV=checks - - - python: "3.8" - dist: xenial - env: TOXENV=mypy - - - python: "3.6" - env: TOXENV=py36 - - - python: "3.7" - dist: xenial - env: TOXENV=py37 - - - python: "3.8" - dist: xenial - env: TOXENV=py38 - - - python: "3.9-dev" - dist: bionic - env: TOXENV=py39 - - - python: "nightly" - dist: bionic - env: TOXENV=py310 - - - python: "3.8" - dist: xenial - env: TOXENV=mypy - -jobs: - allow_failures: - - python: "nightly" diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 26f8ff79..3173507f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,92 @@ in our repository. .. towncrier release notes start + +Version 3.0.0-dev.3 +=================== + +:Released: 2022-01-19 +:Maintainer: Tom Schraitle + + +Bug Fixes +--------- + +* :gh:`310`: Rework API documentation. + Follow a more "semi-manual" attempt and add auto directives + into :file:`docs/api.rst`. + + + +Improved Documentation +---------------------- + +* :gh:`312`: Rework "Usage" section. + + * Mention the rename of :class:`~semver.version.VersionInfo` to + :class:`~semver.version.Version` class + * Remove semver. prefix in doctests to make examples shorter + * Correct some references to dunder methods like + :func:`~.semver.version.Version.__getitem__`, + :func:`~.semver.version.Version.__gt__` etc. + * Remove inconsistencies and mention module level function as + deprecated and discouraged from using + * Make empty :py:func:`super` call in :file:`semverwithvprefix.py` example + +* :gh:`315`: Improve release procedure text + + + +Trivial/Internal Changes +------------------------ + +* :gh:`309`: Some (private) functions from the :mod:`semver.version` + module has been changed. + + The following functions got renamed: + + * function ``semver.version.comparator`` got renamed to + :func:`semver.version._comparator` as it is only useful + inside the :class:`~semver.version.Version` class. + * function ``semver.version.cmp`` got renamed to + :func:`semver.version._cmp` as it is only useful + inside the :class:`~semver.version.Version` class. + + The following functions got integrated into the + :class:`~semver.version.Version` class: + + * function ``semver.version._nat_cmd`` as a classmethod + * function ``semver.version.ensure_str`` + +* :gh:`313`: Correct :file:`tox.ini` for ``changelog`` entry to skip + installation for semver. This should speed up the execution + of towncrier. + +* :gh:`316`: Comparisons of :class:`~semver.version.Version` class and other + types return now a :py:const:`NotImplemented` constant instead + of a :py:exc:`TypeError` exception. + + The `NotImplemented`_ section of the Python documentation recommends + returning this constant when comparing with ``__gt__``, ``__lt__``, + and other comparison operators to "to indicate that the operation is + not implemented with respect to the other type". + + .. _NotImplemented: https://docs.python.org/3/library/constants.html#NotImplemented + +* :gh:`319`: Introduce stages in :file:`.travis.yml` + The config file contains now two stages: check and test. If + check fails, the test stage won't be executed. This could + speed up things when some checks fails. + +* :gh:`322`: Switch from Travis CI to GitHub Actions. + +* :gh:`347`: Support Python 3.10 in GitHub Action and other config files. + + + +---- + + Version 3.0.0-dev.2 =================== diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000..5fd75ab2 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,236 @@ +.. _contributing: + +Contributing to semver +====================== + +The semver source code is managed using Git and is hosted on GitHub:: + + git clone git://github.com/python-semver/python-semver + + +Reporting Bugs and Asking Questions +----------------------------------- + +If you think you have encountered a bug in semver or have an idea for a new +feature? Great! We like to hear from you! + +There are several options to participate: + +* Open a new topic on our `GitHub discussion `_ page. + Tell us our ideas or ask your questions. + +* Look into our GitHub `issues`_ tracker or open a new issue. + + +Prerequisites +------------- + +Before you make changes to the code, we would highly appreciate if you +consider the following general requirements: + +* Make sure your code adheres to the `Semantic Versioning`_ specification. + +* Check if your feature is covered by the Semantic Versioning specification. + If not, ask on its GitHub project https://github.com/semver/semver. + + + +Modifying the Code +------------------ + +We recommend the following workflow: + +#. Fork our project on GitHub using this link: + https://github.com/python-semver/python-semver/fork + +#. Clone your forked Git repository (replace ``GITHUB_USER`` with your + account name on GitHub):: + + $ git clone git@github.com:GITHUB_USER/python-semver.git + +#. Create a new branch. You can name your branch whatever you like, but we + recommend to use some meaningful name. If your fix is based on a + existing GitHub issue, add also the number. Good examples would be: + + * ``feature/123-improve-foo`` when implementing a new feature in issue 123 + * ``bugfix/234-fix-security-bar`` a bugfixes for issue 234 + + Use this :command:`git` command:: + + $ git checkout -b feature/NAME_OF_YOUR_FEATURE + +#. Work on your branch and create a pull request: + + a. Write test cases and run the complete test suite, see :ref:`testsuite` + for details. + + b. Write a changelog entry, see section :ref:`changelog`. + + c. If you have implemented a new feature, document it into our + documentation to help our reader. See section :ref:`doc` for + further details. + + d. Create a `pull request`_. Describe in the pull request what you did + and why. If you have open questions, ask. + +#. Wait for feedback. If you receive any comments, address these. + +#. After your pull request got accepted, delete your branch. + + +.. _testsuite: + +Running the Test Suite +---------------------- + +We use `pytest`_ and `tox`_ to run tests against all supported Python +versions. All test dependencies are resolved automatically. + +You can decide to run the complete test suite or only part of it: + +* To run all tests, use:: + + $ tox + + If you have not all Python interpreters installed on your system + it will probably give you some errors (``InterpreterNotFound``). + To avoid such errors, use:: + + $ tox --skip-missing-interpreters + + It is possible to use one or more specific Python versions. Use the ``-e`` + option and one or more abbreviations (``py36`` for Python 3.6, ``py37`` for + Python 3.7 etc.):: + + $ tox -e py36 + $ tox -e py36,py37 + + To get a complete list and a short description, run:: + + $ tox -av + +* To run only a specific test, pytest requires the syntax + ``TEST_FILE::TEST_FUNCTION``. + + For example, the following line tests only the function + :func:`test_immutable_major` in the file :file:`test_bump.py` for all + Python versions:: + + $ tox -e py36 -- tests/test_bump.py::test_should_bump_major + + By default, pytest prints only a dot for each test function. To + reveal the executed test function, use the following syntax:: + + $ tox -- -v + + You can combine the specific test function with the ``-e`` option, for + example, to limit the tests for Python 3.6 and 3.7 only:: + + $ tox -e py36,py37 -- tests/test_bump.py::test_should_bump_major + +Our code is checked against formatting, style, type, and docstring issues +(`black`_, `flake8`_, `mypy`_, and `docformatter`_). +It is recommended to run your tests in combination with :command:`checks`, +for example:: + + $ tox -e checks,py36,py37 + + +.. _doc: + +Documenting semver +------------------ + +Documenting the features of semver is very important. It gives our developers +an overview what is possible with semver, how it "feels", and how it is +used efficiently. + +.. note:: + + To build the documentation locally use the following command:: + + $ tox -e docs + + The built documentation is available in :file:`docs/_build/html`. + + +A new feature is *not* complete if it isn't proberly documented. A good +documentation includes: + + * **A docstring** + + Each docstring contains a summary line, a linebreak, an optional + directive (see next item), the description of its arguments in + `Sphinx style`_, and an optional doctest. + The docstring is extracted and reused in the :ref:`api` section. + An appropriate docstring should look like this:: + + def to_tuple(self) -> VersionTuple: + """ + Convert the Version object to a tuple. + + .. versionadded:: 2.10.0 + Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to + make this function available in the public API. + + :return: a tuple with all the parts + + >>> semver.Version(5, 3, 1).to_tuple() + (5, 3, 1, None, None) + """ + + * **An optional directive** + + If you introduce a new feature, change a function/method, or remove something, + it is a good practice to introduce Sphinx directives into the docstring. + This gives the reader an idea what version is affected by this change. + + The first required argument, ``VERSION``, defines the version when this change + was introduced. You can choose from: + + * ``.. versionadded:: VERSION`` + + Use this directive to describe a new feature. + + * ``.. versionchanged:: VERSION`` + + Use this directive to describe when something has changed, for example, + new parameters were added, changed side effects, different return values, etc. + + * ``.. deprecated:: VERSION`` + + Use this directive when a feature is deprecated. Describe what should + be used instead, if appropriate. + + + Add such a directive *after* the summary line, as shown above. + + * **The documentation** + + A docstring is good, but in most cases it's too dense. API documentation + cannot replace a good user documentation. Describe how + to use your new feature in our documentation. Here you can give your + readers more examples, describe it in a broader context or show + edge cases. + + +.. _changelog: + +Adding a Changelog Entry +------------------------ + +.. include:: ../changelog.d/README.rst + :start-after: -text-begin- + + +.. _black: https://black.rtfd.io +.. _docformatter: https://pypi.org/project/docformatter/ +.. _flake8: https://flake8.rtfd.io +.. _mypy: http://mypy-lang.org/ +.. _issues: https://github.com/python-semver/python-semver/issues +.. _pull request: https://github.com/python-semver/python-semver/pulls +.. _pytest: http://pytest.org/ +.. _Semantic Versioning: https://semver.org +.. _Sphinx style: https://sphinx-rtd-tutorial.rtfd.io/en/latest/docstrings.html +.. _tox: https://tox.rtfd.org/ +.. _gh_discussions: https://github.com/python-semver/python-semver/discussions diff --git a/README.rst b/README.rst index d4f29819..5c28cc69 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,8 @@ Quickstart A Python module for `semantic versioning`_. Simplifies comparing versions. -|build-status| |python-support| |downloads| |license| |docs| |black| +|GHAction| |python-support| |downloads| |license| |docs| |black| +|openissues| |GHDiscussion| .. teaser-end @@ -99,9 +100,6 @@ There are other functions to discover. Read on! .. |latest-version| image:: https://img.shields.io/pypi/v/semver.svg :alt: Latest version on PyPI :target: https://pypi.org/project/semver -.. |build-status| image:: https://travis-ci.com/python-semver/python-semver.svg?branch=master - :alt: Build status - :target: https://travis-ci.com/python-semver/python-semver .. |python-support| image:: https://img.shields.io/pypi/pyversions/semver.svg :target: https://pypi.org/project/semver :alt: Python versions @@ -118,3 +116,14 @@ There are other functions to discover. Read on! .. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Black Formatter +.. |Gitter| image:: https://badges.gitter.im/python-semver/community.svg + :target: https://gitter.im/python-semver/community + :alt: Gitter +.. |openissues| image:: http://isitmaintained.com/badge/open/python-semver/python-semver.svg + :target: http://isitmaintained.com/project/python-semver/python-semver + :alt: Percentage of open issues +.. |GHAction| image:: https://github.com/python-semver/python-semver/workflows/Python/badge.svg + :alt: Python +.. |GHDiscussion| image:: https://shields.io/badge/GitHub-%20Discussions-green?logo=github + :target: https://github.com/python-semver/python-semver/discussions + :alt: GitHub Discussion diff --git a/changelog.d/213.improvement.rst b/changelog.d/213.improvement.rst deleted file mode 100644 index dcedc695..00000000 --- a/changelog.d/213.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Add typing information \ No newline at end of file diff --git a/docs/_api/semver.__about__.rst b/docs/_api/semver.__about__.rst deleted file mode 100644 index 22395ebd..00000000 --- a/docs/_api/semver.__about__.rst +++ /dev/null @@ -1,5 +0,0 @@ -semver.\_\_about\_\_ module -=========================== - -.. automodule:: semver.__about__ - :members: diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css index 002e6b2f..81a5906f 100644 --- a/docs/_static/css/custom.css +++ b/docs/_static/css/custom.css @@ -35,6 +35,10 @@ div.related.top nav { font-weight: 700; } +.py.class { + margin-top: 1.5em; +} + .py.method { padding-top: 0.25em; padding-bottom: 1.25em; diff --git a/docs/api.rst b/docs/api.rst index 9d884601..196e30a9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,10 +1,57 @@ .. _api: -### -API -### +API Reference +============= -.. toctree:: - :maxdepth: 4 +.. currentmodule:: semver - _api/semver \ No newline at end of file + +Metadata :mod:`semver.__about__` +-------------------------------- + +.. automodule:: semver.__about__ + + +Deprecated Functions in :mod:`semver._deprecated` +------------------------------------------------- + +.. automodule:: semver._deprecated + +.. autofunction:: semver._deprecated.deprecated + + +CLI Parsing :mod:`semver.cli` +----------------------------- + +.. automodule:: semver.cli + +.. autofunction:: semver.cli.cmd_bump + +.. autofunction:: semver.cli.cmd_check + +.. autofunction:: semver.cli.cmd_compare + +.. autofunction:: semver.cli.createparser + +.. autofunction:: semver.cli.main + +.. autofunction:: semver.cli.process + + +Entry point :mod:`semver.__main__` +---------------------------------- + +.. automodule:: semver.__main__ + + + +Version Handling :mod:`semver.version` +-------------------------------------- + +.. automodule:: semver.version + +.. autoclass:: semver.version.VersionInfo + +.. autoclass:: semver.version.Version + :members: + :special-members: __iter__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__, __getitem__, __hash__, __repr__, __str__ diff --git a/docs/conf.py b/docs/conf.py index f5e04b19..52a46704 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,8 @@ import re import sys -sys.path.insert(0, os.path.abspath("../src/")) +SRC_DIR = os.path.abspath("../src/") +sys.path.insert(0, SRC_DIR) # from semver import __version__ # noqa: E402 @@ -58,15 +59,16 @@ def find_version(*file_paths): # ones. extensions = [ "sphinx.ext.autodoc", - "sphinx.ext.autosummary", "sphinx_autodoc_typehints", "sphinx.ext.intersphinx", "sphinx.ext.extlinks", ] +# Autodoc configuration autoclass_content = "class" -autodoc_default_options = {} - +autodoc_typehints = "signature" +autodoc_member_order = "alphabetical" +add_function_parentheses = True # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/docs/development.rst b/docs/development.rst index 049fe1a3..e582053e 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -1,241 +1 @@ -.. _contributing: - -Contributing to semver -====================== - -The semver source code is managed using Git and is hosted on GitHub:: - - git clone git://github.com/python-semver/python-semver - - -Reporting Bugs and Feedback ---------------------------- - -If you think you have encountered a bug in semver or have an idea for a new -feature? Great! We like to hear from you. - -First, take the time to look into our GitHub `issues`_ tracker if -this already covered. If not, changes are good that we avoid double work. - - -Prerequisites -------------- - -Before you make changes to the code, we would highly appreciate if you -consider the following general requirements: - -* Make sure your code adheres to the `Semantic Versioning`_ specification. - -* Check if your feature is covered by the Semantic Versioning specification. - If not, ask on its GitHub project https://github.com/semver/semver. - - - -Modifying the Code ------------------- - -We recommend the following workflow: - -#. Fork our project on GitHub using this link: - https://github.com/python-semver/python-semver/fork - -#. Clone your forked Git repository (replace ``GITHUB_USER`` with your - account name on GitHub):: - - $ git clone git@github.com:GITHUB_USER/python-semver.git - -#. Create a new branch. You can name your branch whatever you like, but we - recommend to use some meaningful name. If your fix is based on a - existing GitHub issue, add also the number. Good examples would be: - - * ``feature/123-improve-foo`` when implementing a new feature in issue 123 - * ``bugfix/234-fix-security-bar`` a bugfixes for issue 234 - - Use this :command:`git` command:: - - $ git checkout -b feature/NAME_OF_YOUR_FEATURE - -#. Work on your branch and create a pull request: - - a. Write test cases and run the complete test suite, see :ref:`testsuite` - for details. - - b. Write a changelog entry, see section :ref:`changelog`. - - c. If you have implemented a new feature, document it into our - documentation to help our reader. See section :ref:`doc` for - further details. - - d. Create a `pull request`_. Describe in the pull request what you did - and why. If you have open questions, ask. - -#. Wait for feedback. If you receive any comments, address these. - -#. After your pull request got accepted, delete your branch. - - -.. _testsuite: - -Running the Test Suite ----------------------- - -We use `pytest`_ and `tox`_ to run tests against all supported Python -versions. All test dependencies are resolved automatically. - -You can decide to run the complete test suite or only part of it: - -* To run all tests, use:: - - $ tox - - If you have not all Python interpreters installed on your system - it will probably give you some errors (``InterpreterNotFound``). - To avoid such errors, use:: - - $ tox --skip-missing-interpreters - - It is possible to use only specific Python versions. Use the ``-e`` - option and one or more abbreviations (``py27`` for Python 2.7, ``py34`` for - Python 3.4 etc.):: - - $ tox -e py34 - $ tox -e py27,py34 - - To get a complete list, run:: - - $ tox -l - -* To run only a specific test, pytest requires the syntax - ``TEST_FILE::TEST_FUNCTION``. - - For example, the following line tests only the function - :func:`test_immutable_major` in the file :file:`test_semver.py` for all - Python versions:: - - $ tox test_semver.py::test_immutable_major - - By default, pytest prints a dot for each test function only. To - reveal the executed test function, use the following syntax:: - - $ tox -- -v - - You can combine the specific test function with the ``-e`` option, for - example, to limit the tests for Python 2.7 and 3.6 only:: - - $ tox -e py27,py36 test_semver.py::test_immutable_major - -Our code is checked against `flake8`_ for style guide issues. It is recommended -to run your tests in combination with :command:`flake8`, for example:: - - $ tox -e py27,py36,flake8 - - -.. _doc: - -Documenting semver ------------------- - -Documenting the features of semver is very important. It gives our developers -an overview what is possible with semver, how it "feels", and how it is -used efficiently. - -.. note:: - - To build the documentation locally use the following command:: - - $ tox -e docs - - The built documentation is available in :file:`dist/docs`. - - -A new feature is *not* complete if it isn't proberly documented. A good -documentation includes: - - * **A docstring** - - Each docstring contains a summary line, a linebreak, an optional - directive (see next item), the description of its arguments in - `Sphinx style`_, and an optional doctest. - The docstring is extracted and reused in the :ref:`api` section. - An appropriate docstring should look like this:: - - def compare(ver1, ver2): - """Compare two versions - - :param ver1: version string 1 - :param ver2: version string 2 - :return: The return value is negative if ver1 < ver2, - zero if ver1 == ver2 and strictly positive if ver1 > ver2 - :rtype: int - - >>> semver.compare("1.0.0", "2.0.0") - -1 - >>> semver.compare("2.0.0", "1.0.0") - 1 - >>> semver.compare("2.0.0", "2.0.0") - 0 - - """ - - * **An optional directive** - - If you introduce a new feature, change a function/method, or remove something, - it is a good practice to introduce Sphinx directives into the docstring. - This gives the reader an idea what version is affected by this change. - - The first required argument, ``VERSION``, defines the version when this change - was introduced. You can choose from: - - * ``.. versionadded:: VERSION`` - - Use this directive to describe a new feature. - - * ``.. versionchanged:: VERSION`` - - Use this directive to describe when something has changed, for example, - new parameters were added, changed side effects, different return values, etc. - - * ``.. deprecated:: VERSION`` - - Use this directive when a feature is deprecated. Describe what should - be used instead, if appropriate. - - - Add such a directive *after* the summary line, if needed. - An appropriate directive could look like this:: - - def to_tuple(self): - """ - Convert the VersionInfo object to a tuple. - - .. versionadded:: 2.10.0 - Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to - make this function available in the public API. - [...] - """ - - * **The documentation** - - A docstring is good, but in most cases it's too dense. Describe how - to use your new feature in our documentation. Here you can give your - readers more examples, describe it in a broader context or show - edge cases. - - -.. _changelog: - -Adding a Changelog Entry ------------------------- - -.. include:: ../changelog.d/README.rst - :start-after: -text-begin- - - -.. _flake8: https://flake8.readthedocs.io -.. _issues: https://github.com/python-semver/python-semver/issues -.. _pull request: https://github.com/python-semver/python-semver/pulls -.. _pytest: http://pytest.org/ -.. _Semantic Versioning: https://semver.org -.. _Sphinx style: https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html -.. _tox: https://tox.readthedocs.org/ - +.. include:: ../CONTRIBUTING.rst diff --git a/docs/migratetosemver3.rst b/docs/migratetosemver3.rst index d6d90954..d977bc03 100644 --- a/docs/migratetosemver3.rst +++ b/docs/migratetosemver3.rst @@ -20,7 +20,7 @@ Use Version instead of VersionInfo The :class:`VersionInfo` has been renamed to :class:`Version` to have a more succinct name. An alias has been created to preserve compatibility but -using old name has been deprecated. +using the old name has been deprecated. If you still need the old version, use this line: @@ -39,4 +39,4 @@ import it from :mod:`semver.cli` in the future: .. code-block:: python - from semver.cli import cmd_bump \ No newline at end of file + from semver.cli import cmd_bump diff --git a/docs/semverwithvprefix.py b/docs/semverwithvprefix.py index 304ce772..5e375031 100644 --- a/docs/semverwithvprefix.py +++ b/docs/semverwithvprefix.py @@ -7,15 +7,13 @@ class SemVerWithVPrefix(Version): """ @classmethod - def parse(cls, version): + def parse(cls, version: str) -> "SemVerWithVPrefix": """ Parse version string to a Version instance. :param version: version string with "v" or "V" prefix - :type version: str :raises ValueError: when version does not start with "v" or "V" :return: a new instance - :rtype: :class:`SemVerWithVPrefix` """ if not version[0] in ("v", "V"): raise ValueError( @@ -23,9 +21,8 @@ def parse(cls, version): v=version ) ) - self = super(SemVerWithVPrefix, cls).parse(version[1:]) - return self + return super().parse(version[1:]) - def __str__(self): + def __str__(self) -> str: # Reconstruct the tag - return "v" + super(SemVerWithVPrefix, self).__str__() + return "v" + super().__str__() diff --git a/docs/usage.rst b/docs/usage.rst index 94a115a8..f6983d17 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,7 +1,7 @@ Using semver ============ -The ``semver`` module can store a version in the :class:`semver.Version` class. +The :mod:`semver` module can store a version in the :class:`~semver.version.Version` class. For historical reasons, a version can be also stored as a string or dictionary. Each type can be converted into the other, if the minimum requirements @@ -26,76 +26,60 @@ Getting the Version of semver To know the version of semver itself, use the following construct:: >>> semver.__version__ - '3.0.0-dev.2' + '3.0.0-dev.3' Creating a Version ------------------ -Due to historical reasons, the semver project offers two ways of -creating a version: +.. versionchanged:: 3.0.0 -* through an object oriented approach with the :class:`semver.Version` - class. This is the preferred method when using semver. + The former :class:`~semver.version.VersionInfo` + has been renamed to :class:`~semver.version.Version`. -* through module level functions and builtin datatypes (usually string - and dict). - This method is still available for compatibility reasons, but are - marked as deprecated. Using it will emit a :class:`DeprecationWarning`. +The preferred way to create a new version is with the class +:class:`~semver.version.Version`. +.. note:: -.. warning:: **Deprecation Warning** + In the previous major release semver 2 it was possible to + create a version with module level functions. + However, module level functions are marked as *deprecated* + since version 2.x.y now. + These functions will be removed in semver 3.1.0. + For details, see the sections :ref:`sec_replace_deprecated_functions` + and :ref:`sec_display_deprecation_warnings`. - Module level functions are marked as *deprecated* in version 2.x.y now. - These functions will be removed in semver 3. - For details, see the sections :ref:`sec_replace_deprecated_functions` and - :ref:`sec_display_deprecation_warnings`. +A :class:`~semver.version.Version` instance can be created in different ways: +* From a Unicode string:: -A :class:`semver.Version` instance can be created in different ways: - -* From a string (a Unicode string in Python 2):: - - >>> semver.Version.parse("3.4.5-pre.2+build.4") + >>> from semver.version import Version + >>> Version.parse("3.4.5-pre.2+build.4") Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - >>> semver.Version.parse(u"5.3.1") + >>> Version.parse(u"5.3.1") Version(major=5, minor=3, patch=1, prerelease=None, build=None) * From a byte string:: - >>> semver.Version.parse(b"2.3.4") + >>> Version.parse(b"2.3.4") Version(major=2, minor=3, patch=4, prerelease=None, build=None) * From individual parts by a dictionary:: >>> d = {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - >>> semver.Version(**d) + >>> Version(**d) Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') Keep in mind, the ``major``, ``minor``, ``patch`` parts has to - be positive. + be positive integers or strings: - >>> semver.Version(-1) + >>> d = {'major': -3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} + >>> Version(**d) Traceback (most recent call last): ... ValueError: 'major' is negative. A version can only be positive. - As a minimum requirement, your dictionary needs at least the - be positive. - - >>> semver.Version(-1) - Traceback (most recent call last): - ... - ValueError: 'major' is negative. A version can only be positive. - - As a minimum requirement, your dictionary needs at least the - be positive. - - >>> semver.Version(-1) - Traceback (most recent call last): - ... - ValueError: 'major' is negative. A version can only be positive. - As a minimum requirement, your dictionary needs at least the ``major`` key, others can be omitted. You get a ``TypeError`` if your dictionary contains invalid keys. @@ -105,20 +89,23 @@ A :class:`semver.Version` instance can be created in different ways: * From a tuple:: >>> t = (3, 5, 6) - >>> semver.Version(*t) + >>> Version(*t) Version(major=3, minor=5, patch=6, prerelease=None, build=None) You can pass either an integer or a string for ``major``, ``minor``, or ``patch``:: - >>> semver.Version("3", "5", 6) + >>> Version("3", "5", 6) Version(major=3, minor=5, patch=6, prerelease=None, build=None) -The old, deprecated module level functions are still available. If you -need them, they return different builtin objects (string and dictionary). +The old, deprecated module level functions are still available but +using them are discoraged. They are available to convert old code +to semver3. + +If you need them, they return different builtin objects (string and dictionary). Keep in mind, once you have converted a version into a string or dictionary, it's an ordinary builtin object. It's not a special version object like -the :class:`semver.Version` class anymore. +the :class:`~semver.version.Version` class anymore. Depending on your use case, the following methods are available: @@ -136,7 +123,7 @@ Depending on your use case, the following methods are available: >>> semver.parse("3.4.5-pre.2+build.4") OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', 'pre.2'), ('build', 'build.4')]) - If you pass an invalid version string you will get a ``ValueError``:: + If you pass an invalid version string you will get a :py:exc:`ValueError`:: >>> semver.parse("1.2") Traceback (most recent call last): @@ -148,36 +135,23 @@ Parsing a Version String ------------------------ "Parsing" in this context means to identify the different parts in a string. +Use the function :func:`Version.parse `:: - -* With :func:`semver.parse_version_info`:: - - >>> semver.parse_version_info("3.4.5-pre.2+build.4") - Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') - -* With :func:`semver.Version.parse` (basically the same as - :func:`semver.parse_version_info`):: - - >>> semver.Version.parse("3.4.5-pre.2+build.4") + >>> Version.parse("3.4.5-pre.2+build.4") Version(major=3, minor=4, patch=5, prerelease='pre.2', build='build.4') -* With :func:`semver.parse`:: - - >>> semver.parse("3.4.5-pre.2+build.4") == {'major': 3, 'minor': 4, 'patch': 5, 'prerelease': 'pre.2', 'build': 'build.4'} - True - Checking for a Valid Semver Version ----------------------------------- If you need to check a string if it is a valid semver version, use the -classmethod :func:`semver.Version.isvalid`: +classmethod :func:`Version.isvalid `: .. code-block:: python - >>> semver.Version.isvalid("1.0.0") + >>> Version.isvalid("1.0.0") True - >>> semver.Version.isvalid("invalid") + >>> Version.isvalid("invalid") False @@ -186,12 +160,12 @@ classmethod :func:`semver.Version.isvalid`: Accessing Parts of a Version Through Names ------------------------------------------ -The :class:`semver.Version` contains attributes to access the different +The :class:`~semver.version.Version` class contains attributes to access the different parts of a version: .. code-block:: python - >>> v = semver.Version.parse("3.4.5-pre.2+build.4") + >>> v = Version.parse("3.4.5-pre.2+build.4") >>> v.major 3 >>> v.minor @@ -203,8 +177,8 @@ parts of a version: >>> v.build 'build.4' -However, the attributes are read-only. You cannot change an attribute. -If you do, you get an ``AttributeError``:: +However, the attributes are read-only. You cannot change any of the above attributes. +If you do, you get an :py:exc:`AttributeError`:: >>> v.minor = 5 Traceback (most recent call last): @@ -213,16 +187,16 @@ If you do, you get an ``AttributeError``:: If you need to replace different parts of a version, refer to section :ref:`sec.replace.parts`. -In case you need the different parts of a version stepwise, iterate over the :class:`semver.Version` instance:: +In case you need the different parts of a version stepwise, iterate over the :class:`~semver.version.Version` instance:: - >>> for item in semver.Version.parse("3.4.5-pre.2+build.4"): + >>> for item in Version.parse("3.4.5-pre.2+build.4"): ... print(item) 3 4 5 pre.2 build.4 - >>> list(semver.Version.parse("3.4.5-pre.2+build.4")) + >>> list(Version.parse("3.4.5-pre.2+build.4")) [3, 4, 5, 'pre.2', 'build.4'] @@ -234,15 +208,15 @@ Accessing Parts Through Index Numbers .. versionadded:: 2.10.0 Another way to access parts of a version is to use an index notation. The underlying -:class:`Version ` object allows to access its data through -the magic method :func:`__getitem__ `. +:class:`~semver.version.Version` object allows to access its data through +the magic method :func:`~semver.version.Version.__getitem__`. For example, the ``major`` part can be accessed by index number 0 (zero). Likewise the other parts: .. code-block:: python - >>> ver = semver.Version.parse("10.3.2-pre.5+build.10") + >>> ver = Version.parse("10.3.2-pre.5+build.10") >>> ver[0], ver[1], ver[2], ver[3], ver[4] (10, 3, 2, 'pre.5', 'build.10') @@ -261,11 +235,11 @@ Or, as an alternative, you can pass a :func:`slice` object: >>> ver[sl] (10, 3, 2) -Negative numbers or undefined parts raise an :class:`IndexError` exception: +Negative numbers or undefined parts raise an :py:exc:`IndexError` exception: .. code-block:: python - >>> ver = semver.Version.parse("10.3.2") + >>> ver = Version.parse("10.3.2") >>> ver[3] Traceback (most recent call last): ... @@ -281,9 +255,9 @@ Replacing Parts of a Version ---------------------------- If you want to replace different parts of a version, but leave other parts -unmodified, use the function :func:`semver.Version.replace` or :func:`semver.replace`: +unmodified, use the function :func:`replace `: -* From a :class:`semver.Version` instance:: +* From a :class:`Version ` instance:: >>> version = semver.Version.parse("1.4.5-pre.1+build.6") >>> version.replace(major=2, minor=2) @@ -312,25 +286,25 @@ If you pass invalid keys you get an exception:: Converting a Version instance into Different Types ------------------------------------------------------ -Sometimes it is needed to convert a :class:`semver.Version` instance into +Sometimes it is needed to convert a :class:`Version ` instance into a different type. For example, for displaying or to access all parts. -It is possible to convert a :class:`semver.Version` instance: +It is possible to convert a :class:`Version ` instance: * Into a string with the builtin function :func:`str`:: - >>> str(semver.Version.parse("3.4.5-pre.2+build.4")) + >>> str(Version.parse("3.4.5-pre.2+build.4")) '3.4.5-pre.2+build.4' -* Into a dictionary with :func:`semver.Version.to_dict`:: +* Into a dictionary with :func:`to_dict `:: - >>> v = semver.Version(major=3, minor=4, patch=5) + >>> v = Version(major=3, minor=4, patch=5) >>> v.to_dict() OrderedDict([('major', 3), ('minor', 4), ('patch', 5), ('prerelease', None), ('build', None)]) -* Into a tuple with :func:`semver.Version.to_tuple`:: +* Into a tuple with :func:`to_tuple `:: - >>> v = semver.Version(major=5, minor=4, patch=2) + >>> v = Version(major=5, minor=4, patch=2) >>> v.to_tuple() (5, 4, 2, None, None) @@ -341,27 +315,27 @@ Raising Parts of a Version The ``semver`` module contains the following functions to raise parts of a version: -* :func:`semver.Version.bump_major`: raises the major part and set all other parts to +* :func:`Version.bump_major `: raises the major part and set all other parts to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`semver.Version.bump_minor`: raises the minor part and sets ``patch`` to zero. +* :func:`Version.bump_minor `: raises the minor part and sets ``patch`` to zero. Set ``prerelease`` and ``build`` to ``None``. -* :func:`semver.Version.bump_patch`: raises the patch part. Set ``prerelease`` and +* :func:`Version.bump_patch `: raises the patch part. Set ``prerelease`` and ``build`` to ``None``. -* :func:`semver.Version.bump_prerelease`: raises the prerelease part and set +* :func:`Version.bump_prerelease `: raises the prerelease part and set ``build`` to ``None``. -* :func:`semver.Version.bump_build`: raises the build part. +* :func:`Version.bump_build `: raises the build part. .. code-block:: python - >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_major()) + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_major()) '4.0.0' - >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_minor()) + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_minor()) '3.5.0' - >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_patch()) + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_patch()) '3.4.6' - >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_prerelease()) + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_prerelease()) '3.4.5-pre.3' - >>> str(semver.Version.parse("3.4.5-pre.2+build.4").bump_build()) + >>> str(Version.parse("3.4.5-pre.2+build.4").bump_build()) '3.4.5-pre.2+build.5' Likewise the module level functions :func:`semver.bump_major`. @@ -371,23 +345,23 @@ Increasing Parts of a Version Taking into Account Prereleases ------------------------------------------------------------- .. versionadded:: 2.10.0 - Added :func:`semver.Version.next_version`. + Added :func:`Version.next_version `. If you want to raise your version and take prereleases into account, -the function :func:`semver.Version.next_version` would perhaps a -better fit. +the function :func:`next_version ` +would perhaps a better fit. .. code-block:: python - >>> v = semver.Version.parse("3.4.5-pre.2+build.4") + >>> v = Version.parse("3.4.5-pre.2+build.4") >>> str(v.next_version(part="prerelease")) '3.4.5-pre.3' - >>> str(semver.Version.parse("3.4.5-pre.2+build.4").next_version(part="patch")) + >>> str(Version.parse("3.4.5-pre.2+build.4").next_version(part="patch")) '3.4.5' - >>> str(semver.Version.parse("3.4.5+build.4").next_version(part="patch")) + >>> str(Version.parse("3.4.5+build.4").next_version(part="patch")) '3.4.5' - >>> str(semver.Version.parse("0.1.4").next_version("prerelease")) + >>> str(Version.parse("0.1.4").next_version("prerelease")) '0.1.5-rc.1' @@ -410,23 +384,23 @@ To compare two versions depends on your type: The return value is negative if ``version1 < version2``, zero if ``version1 == version2`` and strictly positive if ``version1 > version2``. -* **Two** :class:`semver.Version` **instances** +* **Two** :class:`Version ` **instances** Use the specific operator. Currently, the operators ``<``, ``<=``, ``>``, ``>=``, ``==``, and ``!=`` are supported:: - >>> v1 = semver.Version.parse("3.4.5") - >>> v2 = semver.Version.parse("3.5.1") + >>> v1 = Version.parse("3.4.5") + >>> v2 = Version.parse("3.5.1") >>> v1 < v2 True >>> v1 > v2 False -* **A** :class:`semver.Version` **type and a** :func:`tuple` **or** :func:`list` +* **A** :class:`Version ` **type and a** :func:`tuple` **or** :func:`list` - Use the operator as with two :class:`semver.Version` types:: + Use the operator as with two :class:`Version ` types:: - >>> v = semver.Version.parse("3.4.5") + >>> v = Version.parse("3.4.5") >>> v > (1, 0) True >>> v < [3, 5] @@ -439,7 +413,7 @@ To compare two versions depends on your type: >>> [3, 5] > v True -* **A** :class:`semver.Version` **type and a** :func:`str` +* **A** :class:`Version ` **type and a** :func:`str` You can use also raw strings to compare:: @@ -455,14 +429,14 @@ To compare two versions depends on your type: >>> "3.5.0" > v True - However, if you compare incomplete strings, you get a :class:`ValueError` exception:: + However, if you compare incomplete strings, you get a :py:exc:`ValueError` exception:: >>> v > "1.0" Traceback (most recent call last): ... ValueError: 1.0 is not valid SemVer string -* **A** :class:`semver.Version` **type and a** :func:`dict` +* **A** :class:`Version ` **type and a** :func:`dict` You can also use a dictionary. In contrast to strings, you can have an "incomplete" version (as the other parts are set to zero):: @@ -475,7 +449,7 @@ To compare two versions depends on your type: >>> dict(major=1) < v True - If the dictionary contains unknown keys, you get a :class:`TypeError` exception:: + If the dictionary contains unknown keys, you get a :py:exc:`TypeError` exception:: >>> v > dict(major=1, unknown=42) Traceback (most recent call last): @@ -499,16 +473,16 @@ Version equality means for semver, that major, minor, patch, and prerelease parts are equal in both versions you compare. The build part is ignored. For example:: - >>> v = semver.Version.parse("1.2.3-rc4+1e4664d") + >>> v = Version.parse("1.2.3-rc4+1e4664d") >>> v == "1.2.3-rc4+dedbeef" True -This also applies when a :class:`semver.Version` is a member of a set, or a +This also applies when a :class:`Version ` is a member of a set, or a dictionary key:: >>> d = {} - >>> v1 = semver.Version.parse("1.2.3-rc4+1e4664d") - >>> v2 = semver.Version.parse("1.2.3-rc4+dedbeef") + >>> v1 = Version.parse("1.2.3-rc4+1e4664d") + >>> v2 = Version.parse("1.2.3-rc4+dedbeef") >>> d[v1] = 1 >>> d[v2] 1 @@ -554,34 +528,36 @@ Getting Minimum and Maximum of Multiple Versions The functions :func:`semver.max_ver` and :func:`semver.min_ver` are deprecated in favor of their builtin counterparts :func:`max` and :func:`min`. -Since :class:`semver.Version` implements :func:`__gt__()` and :func:`__lt__()`, it can be used with builtins requiring +Since :class:`Version ` implements +:func:`__gt__ ` and +:func:`__lt__ `, it can be used with builtins requiring: .. code-block:: python - >>> max([semver.Version(0, 1, 0), semver.Version(0, 2, 0), semver.Version(0, 1, 3)]) + >>> max([Version(0, 1, 0), Version(0, 2, 0), Version(0, 1, 3)]) Version(major=0, minor=2, patch=0, prerelease=None, build=None) - >>> min([semver.Version(0, 1, 0), semver.Version(0, 2, 0), semver.Version(0, 1, 3)]) + >>> min([Version(0, 1, 0), Version(0, 2, 0), Version(0, 1, 3)]) Version(major=0, minor=1, patch=0, prerelease=None, build=None) Incidentally, using :func:`map`, you can get the min or max version of any number of versions of the same type -(convertible to :class:`semver.Version`). +(convertible to :class:`Version `). For example, here are the maximum and minimum versions of a list of version strings: .. code-block:: python - >>> str(max(map(semver.Version.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))) + >>> max(['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'], key=Version.parse) '2.1.0' - >>> str(min(map(semver.Version.parse, ['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99']))) + >>> min(['1.1.0', '1.2.0', '2.1.0', '0.5.10', '0.4.99'], key=Version.parse) '0.4.99' And the same can be done with tuples: .. code-block:: python - >>> max(map(lambda v: semver.Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() + >>> max(map(lambda v: Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() (2, 1, 0, None, None) - >>> min(map(lambda v: semver.Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() + >>> min(map(lambda v: Version(*v), [(1, 1, 0), (1, 2, 0), (2, 1, 0), (0, 5, 10), (0, 4, 99)])).to_tuple() (0, 4, 99, None, None) For dictionaries, it is very similar to finding the max version tuple: see :ref:`sec.convert.versions`. @@ -616,7 +592,7 @@ information and returns a tuple with two items: :language: python -The function returns a *tuple*, containing a :class:`Version` +The function returns a *tuple*, containing a :class:`Version ` instance or None as the first element and the rest as the second element. The second element (the rest) can be used to make further adjustments. @@ -649,7 +625,7 @@ them with code which is compatible for future versions: * :func:`semver.bump_major`, :func:`semver.bump_minor`, :func:`semver.bump_patch`, :func:`semver.bump_prerelease`, :func:`semver.bump_build` - Replace them with the respective methods of the :class:`semver.Version` + Replace them with the respective methods of the :class:`Version ` class. For example, the function :func:`semver.bump_major` is replaced by :func:`semver.Version.bump_major` and calling the ``str(versionobject)``: @@ -657,7 +633,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.bump_major("3.4.5") - >>> s2 = str(semver.Version.parse("3.4.5").bump_major()) + >>> s2 = str(Version.parse("3.4.5").bump_major()) >>> s1 == s2 True @@ -681,7 +657,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.format_version(5, 4, 3, 'pre.2', 'build.1') - >>> s2 = str(semver.Version(5, 4, 3, 'pre.2', 'build.1')) + >>> s2 = str(Version(5, 4, 3, 'pre.2', 'build.1')) >>> s1 == s2 True @@ -692,7 +668,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.max_ver("1.2.3", "1.2.4") - >>> s2 = str(max(map(semver.Version.parse, ("1.2.3", "1.2.4")))) + >>> s2 = str(max(map(Version.parse, ("1.2.3", "1.2.4")))) >>> s1 == s2 True @@ -703,7 +679,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.min_ver("1.2.3", "1.2.4") - >>> s2 = str(min(map(semver.Version.parse, ("1.2.3", "1.2.4")))) + >>> s2 = str(min(map(Version.parse, ("1.2.3", "1.2.4")))) >>> s1 == s2 True @@ -715,7 +691,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> v1 = semver.parse("1.2.3") - >>> v2 = semver.Version.parse("1.2.3").to_dict() + >>> v2 = Version.parse("1.2.3").to_dict() >>> v1 == v2 True @@ -726,7 +702,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> v1 = semver.parse_version_info("3.4.5") - >>> v2 = semver.Version.parse("3.4.5") + >>> v2 = Version.parse("3.4.5") >>> v1 == v2 True @@ -737,7 +713,7 @@ them with code which is compatible for future versions: .. code-block:: python >>> s1 = semver.replace("1.2.3", major=2, patch=10) - >>> s2 = str(semver.Version.parse('1.2.3').replace(major=2, patch=10)) + >>> s2 = str(Version.parse('1.2.3').replace(major=2, patch=10)) >>> s1 == s2 True @@ -785,7 +761,7 @@ Creating Subclasses from Version If you do not like creating functions to modify the behavior of semver (as shown in section :ref:`sec_dealing_with_invalid_versions`), you can -also create a subclass of the :class:`Version` class. +also create a subclass of the :class:`Version ` class. For example, if you want to output a "v" prefix before a version, but the other behavior is the same, use the following code: @@ -811,4 +787,3 @@ the original class: Traceback (most recent call last): ... ValueError: '1.2.4': not a valid semantic version tag. Must start with 'v' or 'V' - diff --git a/pyproject.toml b/pyproject.toml index 1b406dac..769b13d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,22 +9,15 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 88 -target-version = ['py36', 'py37', 'py38'] +target-version = ['py36', 'py37', 'py38', 'py39', 'py310'] # diff = true -exclude = ''' -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.mypy_cache - | \.tox - | \.venv - | \.env - | _build - | build - | dist - )/ -) +extend-exclude = ''' +# A regex preceded with ^/ will apply only to files and directories +# in the root of the project. +^/*.py +''' +include = ''' +^/setup.py ''' [tool.towncrier] diff --git a/release-procedure.md b/release-procedure.md index db9ed1b5..251f23f0 100644 --- a/release-procedure.md +++ b/release-procedure.md @@ -5,22 +5,31 @@ create a new release. ## Prepare the Release -1. Verify that issues about new release are closed https://github.com/python-semver/python-semver/issues. +1. Verify: -1. Verify that no pull requests that should be included in this release haven't been left out https://github.com/python-semver/python-semver/pulls. + * all issues for a new release are closed: . -1. Verify that continuous integration for latest build was passing https://travis-ci.com/python-semver/python-semver. + * that all pull requests that should be included in this release are merged: . -1. Create a new branch `release/VERSION`. + * that continuous integration for latest build was passing: . + +1. Create a new branch `release/`. 1. If one or several supported Python versions have been removed or added, verify that the 3 following files have been updated: - * [setup.py](https://github.com/python-semver/python-semver/blob/master/setup.py) - * [tox.ini](https://github.com/python-semver/python-semver/blob/master/tox.ini) - * [.travis.yml](https://github.com/python-semver/python-semver/blob/master/.travis.yml) + * `setup.cfg` + * `tox.ini` + * `.git/workflows/pythonpackage.yml` + +1. Verify that the version has been updated and follow + : + + * `src/semver/__about__.py` + * `docs/usage.rst` 1. Add eventually new contributor(s) to [CONTRIBUTORS](https://github.com/python-semver/python-semver/blob/master/CONTRIBUTORS). -1. Verify that `__version__` in [semver.py](https://github.com/python-semver/python-semver/blob/master/semver.py) have been updated and follow https://semver.org. + +1. Check if all changelog entries are created. If some are missing, [create them](https://python-semver.readthedocs.io/en/latest/development.html#adding-a-changelog-entry). 1. Show the new draft [CHANGELOG](https://github.com/python-semver/python-semver/blob/master/CHANGELOG.rst) entry for the latest release with: @@ -36,32 +45,47 @@ create a new release. $ tox -e docs +1. Commit all changes, push, and create a pull request. + ## Create the New Release -1. Ensure that long description (ie [README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` +1. Ensure that long description ([README.rst](https://github.com/python-semver/python-semver/blob/master/README.rst)) can be correctly rendered by Pypi using `restview --long-description` + +1. Clean up your local Git repository. Be careful, + as it **will remove all files** which are not + versioned by Git: + + $ git clean -xfd + + Before you create your distribution files, clean + the directory too: + + $ rm dist/* + +1. Create the distribution files (wheel and source): + + $ tox -e prepare-dist 1. Upload the wheel and source to TestPyPI first: - ```bash - $ git clean -xfd - $ rm dist/* - $ python3 setup.py sdist bdist_wheel + ```bash $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* ``` - If you have a `~/.pypirc` with a `testpyi` section, the upload can be + If you have a `~/.pypirc` with a `testpypi` section, the upload can be simplified: - $ twine upload --repository testpyi dist/* + $ twine upload --repository testpypi dist/* 1. Check if everything is okay with the wheel. + Check also the web site `https://test.pypi.org/project//` 1. Upload to PyPI: ```bash $ git clean -xfd - $ python setup.py register sdist bdist_wheel + $ tox -e prepare-dist $ twine upload dist/* ``` @@ -78,4 +102,6 @@ create a new release. document the new release. Usually it's enough to take it from a commit message or the tag description. +1. Announce it in . + You're done! Celebrate! diff --git a/setup.cfg b/setup.cfg index 681240ac..de2d226c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,8 +14,8 @@ author_email = k-bx@k-bx.com maintainer = Sebastien Celles, Tom Schraitle maintainer_email = s.celles@gmail.com url = https://github.com/python-semver/python-semver -download_url = https://github.com/python-semver/python-semver/downloads project_urls = + Changelog = https://python-semver.readthedocs.io/en/latest/changelog.html Documentation = https://python-semver.rtfd.io Releases = https://github.com/python-semver/python-semver/releases Bug Tracker = https://github.com/python-semver/python-semver/issues @@ -30,6 +30,7 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Topic :: Software Development :: Libraries :: Python Modules license = BSD diff --git a/src/semver/__about__.py b/src/semver/__about__.py index aa293425..fa448ebe 100644 --- a/src/semver/__about__.py +++ b/src/semver/__about__.py @@ -4,11 +4,19 @@ Contains information about semver's version, the implemented version of the semver specifictation, author, maintainers, and description. +.. autodata:: __author__ + +.. autodata:: __description__ + +.. autodata:: __maintainer__ + .. autodata:: __version__ + +.. autodata:: SEMVER_SPEC_VERSION """ #: Semver version -__version__ = "3.0.0-dev.2" +__version__ = "3.0.0-dev.3" #: Original semver author __author__ = "Kostiantyn Rybnikov" diff --git a/src/semver/_deprecated.py b/src/semver/_deprecated.py index 545a2438..61ceae12 100644 --- a/src/semver/_deprecated.py +++ b/src/semver/_deprecated.py @@ -7,11 +7,11 @@ import warnings from functools import partial, wraps from types import FrameType -from typing import Type, Union, Callable, cast +from typing import Type, Callable, cast from . import cli from .version import Version -from ._types import F, String +from ._types import Decorator, F, String def deprecated( @@ -19,7 +19,7 @@ def deprecated( replace: str = None, version: str = None, category: Type[Warning] = DeprecationWarning, -) -> Union[Callable[..., F], partial]: +) -> Decorator: """ Decorates a function to output a deprecation warning. diff --git a/src/semver/_types.py b/src/semver/_types.py index 823c7349..7afb6ff0 100644 --- a/src/semver/_types.py +++ b/src/semver/_types.py @@ -1,3 +1,6 @@ +"""Typing for semver.""" + +from functools import partial from typing import Union, Optional, Tuple, Dict, Iterable, Callable, TypeVar VersionPart = Union[int, Optional[str]] @@ -6,3 +9,4 @@ VersionIterator = Iterable[VersionPart] String = Union[str, bytes] F = TypeVar("F", bound=Callable) +Decorator = Union[Callable[..., F], partial] diff --git a/src/semver/cli.py b/src/semver/cli.py index ca400373..65ca5187 100644 --- a/src/semver/cli.py +++ b/src/semver/cli.py @@ -1,4 +1,14 @@ -"""CLI parsing for :command:`pysemver` command.""" +""" +CLI parsing for :command:`pysemver` command. + +Each command in :command:`pysemver` is mapped to a ``cmd_`` function. +The :func:`main ` function calls +:func:`createparser ` and +:func:`process ` to parse and process +all the commandline options. + +The result of each command is printed on stdout. +""" import argparse import sys diff --git a/src/semver/version.py b/src/semver/version.py index 64353011..9e02544f 100644 --- a/src/semver/version.py +++ b/src/semver/version.py @@ -29,41 +29,7 @@ Comparator = Callable[["Version", Comparable], bool] -def cmp(a, b): # TODO: type hints - """Return negative if ab.""" - return (a > b) - (a < b) - - -def ensure_str(s: String, encoding="utf-8", errors="strict") -> str: - # Taken from six project - """ - Coerce *s* to `str`. - - * `str` -> `str` - * `bytes` -> decoded to `str` - - :param s: the string to convert - :type s: str | bytes - :param encoding: the encoding to apply, defaults to "utf-8" - :type encoding: str - :param errors: set a different error handling scheme, - defaults to "strict". - Other possible values are `ignore`, `replace`, and - `xmlcharrefreplace` as well as any other name - registered with :func:`codecs.register_error`. - :type errors: str - :raises TypeError: if ``s`` is not str or bytes type - :return: the converted string - :rtype: str - """ - if isinstance(s, bytes): - s = s.decode(encoding, errors) - elif not isinstance(s, String.__args__): # type: ignore - raise TypeError("not expecting type '%s'" % type(s)) - return s - - -def comparator(operator: Comparator) -> Comparator: +def _comparator(operator: Comparator) -> Comparator: """Wrap a Version binary op method in a type-check.""" @wraps(operator) @@ -76,39 +42,15 @@ def wrapper(self: "Version", other: Comparable) -> bool: *String.__args__, # type: ignore ) if not isinstance(other, comparable_types): - raise TypeError( - "other type %r must be in %r" % (type(other), comparable_types) - ) + return NotImplemented return operator(self, other) return wrapper -def _nat_cmp(a, b): # TODO: type hints - def convert(text): - return int(text) if re.match("^[0-9]+$", text) else text - - def split_key(key): - return [convert(c) for c in key.split(".")] - - def cmp_prerelease_tag(a, b): - if isinstance(a, int) and isinstance(b, int): - return cmp(a, b) - elif isinstance(a, int): - return -1 - elif isinstance(b, int): - return 1 - else: - return cmp(a, b) - - a, b = a or "", b or "" - a_parts, b_parts = split_key(a), split_key(b) - for sub_a, sub_b in zip(a_parts, b_parts): - cmp_result = cmp_prerelease_tag(sub_a, sub_b) - if cmp_result != 0: - return cmp_result - else: - return cmp(len(a), len(b)) +def _cmp(a, b): # TODO: type hints + """Return negative if ab.""" + return (a > b) - (a < b) class Version: @@ -171,6 +113,29 @@ def __init__( self._prerelease = None if prerelease is None else str(prerelease) self._build = None if build is None else str(build) + @classmethod + def _nat_cmp(cls, a, b): # TODO: type hints + def cmp_prerelease_tag(a, b): + if isinstance(a, int) and isinstance(b, int): + return _cmp(a, b) + elif isinstance(a, int): + return -1 + elif isinstance(b, int): + return 1 + else: + return _cmp(a, b) + + a, b = a or "", b or "" + a_parts, b_parts = a.split("."), b.split(".") + a_parts = [int(x) if re.match(r"^\d+$", x) else x for x in a_parts] + b_parts = [int(x) if re.match(r"^\d+$", x) else x for x in b_parts] + for sub_a, sub_b in zip(a_parts, b_parts): + cmp_result = cmp_prerelease_tag(sub_a, sub_b) + if cmp_result != 0: + return cmp_result + else: + return _cmp(len(a), len(b)) + @property def major(self) -> int: """The major part of a version (read-only).""" @@ -218,7 +183,7 @@ def build(self, value): def to_tuple(self) -> VersionTuple: """ - Convert the VersionInfo object to a tuple. + Convert the Version object to a tuple. .. versionadded:: 2.10.0 Renamed ``VersionInfo._astuple`` to ``VersionInfo.to_tuple`` to @@ -233,7 +198,7 @@ def to_tuple(self) -> VersionTuple: def to_dict(self) -> VersionDict: """ - Convert the VersionInfo object to an OrderedDict. + Convert the Version object to an OrderedDict. .. versionadded:: 2.10.0 Renamed ``VersionInfo._asdict`` to ``VersionInfo.to_dict`` to @@ -257,7 +222,7 @@ def to_dict(self) -> VersionDict: ) def __iter__(self) -> VersionIterator: - """Implement iter(self).""" + """Return iter(self).""" yield from self.to_tuple() @staticmethod @@ -300,7 +265,6 @@ def bump_minor(self) -> "Version": :return: new object with the raised minor part - >>> ver = semver.parse("3.4.5") >>> ver.bump_minor() Version(major=3, minor=5, patch=0, prerelease=None, build=None) @@ -313,8 +277,7 @@ def bump_patch(self) -> "Version": Raise the patch part of the version, return a new object but leave self untouched. - :return: new object with the raised patch part - + :return: new object with the raised patch part >>> ver = semver.parse("3.4.5") >>> ver.bump_patch() @@ -328,7 +291,7 @@ def bump_prerelease(self, token: str = "rc") -> "Version": Raise the prerelease part of the version, return a new object but leave self untouched. - :param token: defaults to 'rc' + :param token: defaults to ``rc`` :return: new object with the raised prerelease part >>> ver = semver.parse("3.4.5") @@ -345,7 +308,7 @@ def bump_build(self, token: str = "build") -> "Version": Raise the build part of the version, return a new object but leave self untouched. - :param token: defaults to 'build' + :param token: defaults to ``build`` :return: new object with the raised build part >>> ver = semver.parse("3.4.5-rc.1+build.9") @@ -365,7 +328,6 @@ def compare(self, other: Comparable) -> int: :return: The return value is negative if ver1 < ver2, zero if ver1 == ver2 and strictly positive if ver1 > ver2 - >>> semver.compare("2.0.0") -1 >>> semver.compare("1.0.0") @@ -390,12 +352,12 @@ def compare(self, other: Comparable) -> int: v1 = self.to_tuple()[:3] v2 = other.to_tuple()[:3] - x = cmp(v1, v2) + x = _cmp(v1, v2) if x: return x rc1, rc2 = self.prerelease, other.prerelease - rccmp = _nat_cmp(rc1, rc2) + rccmp = self._nat_cmp(rc1, rc2) if not rccmp: return 0 @@ -453,27 +415,27 @@ def next_version(self, part: str, prerelease_token: str = "rc") -> "Version": version = version.bump_patch() return version.bump_prerelease(prerelease_token) - @comparator + @_comparator def __eq__(self, other: Comparable) -> bool: # type: ignore return self.compare(other) == 0 - @comparator + @_comparator def __ne__(self, other: Comparable) -> bool: # type: ignore return self.compare(other) != 0 - @comparator + @_comparator def __lt__(self, other: Comparable) -> bool: return self.compare(other) < 0 - @comparator + @_comparator def __le__(self, other: Comparable) -> bool: return self.compare(other) <= 0 - @comparator + @_comparator def __gt__(self, other: Comparable) -> bool: return self.compare(other) > 0 - @comparator + @_comparator def __ge__(self, other: Comparable) -> bool: return self.compare(other) >= 0 @@ -481,14 +443,17 @@ def __getitem__( self, index: Union[int, slice] ) -> Union[int, Optional[str], Tuple[Union[int, str], ...]]: """ - self.__getitem__(index) <==> self[index] Implement getitem. If the part - requested is undefined, or a part of the range requested is undefined, - it will throw an index error. Negative indices are not supported. + self.__getitem__(index) <==> self[index] Implement getitem. + + If the part requested is undefined, or a part of the range requested + is undefined, it will throw an index error. + Negative indices are not supported. :param Union[int, slice] index: a positive integer indicating the offset or a :func:`slice` object :raises IndexError: if index is beyond the range or a part is None :return: the requested part of the version at position index + >>> ver = semver.Version.parse("3.4.5") >>> ver[0], ver[1], ver[2] (3, 4, 5) @@ -519,7 +484,6 @@ def __repr__(self) -> str: return "%s(%s)" % (type(self).__name__, s) def __str__(self) -> str: - """str(self)""" version = "%d.%d.%d" % (self.major, self.minor, self.patch) if self.prerelease: version += "-%s" % self.prerelease @@ -533,7 +497,9 @@ def __hash__(self) -> int: def finalize_version(self) -> "Version": """ Remove any prerelease and build metadata from the version. + :return: a new instance with the finalized version string + >>> str(semver.Version.parse('1.2.3-rc.5').finalize_version()) '1.2.3' """ @@ -545,12 +511,12 @@ def match(self, match_expr: str) -> bool: Compare self to match a match expression. :param match_expr: operator and version; valid operators are - < smaller than - > greater than - >= greator or equal than - <= smaller or equal than - == equal - != not equal + ``<``` smaller than + ``>`` greater than + ``>=`` greator or equal than + ``<=`` smaller or equal than + ``==`` equal + ``!=`` not equal :return: True if the expression matches the version, otherwise False >>> semver.Version.parse("2.0.0").match(">=1.0.0") @@ -589,24 +555,29 @@ def match(self, match_expr: str) -> bool: @classmethod def parse(cls, version: String) -> "Version": """ - Parse version string to a VersionInfo instance. + Parse version string to a Version instance. .. versionchanged:: 2.11.0 Changed method from static to classmethod to allow subclasses. :param version: version string - :return: a :class:`VersionInfo` instance + :return: a new :class:`Version` instance :raises ValueError: if version is invalid + :raises TypeError: if version contains the wrong type >>> semver.Version.parse('3.4.5-pre.2+build.4') - VersionInfo(major=3, minor=4, patch=5, \ + Version(major=3, minor=4, patch=5, \ prerelease='pre.2', build='build.4') """ - version_str = ensure_str(version) - match = cls._REGEX.match(version_str) + if isinstance(version, bytes): + version = version.decode("UTF-8") + elif not isinstance(version, String.__args__): # type: ignore + raise TypeError("not expecting type '%s'" % type(version)) + + match = cls._REGEX.match(version) if match is None: - raise ValueError(f"{version_str} is not valid SemVer string") + raise ValueError(f"{version} is not valid SemVer string") matched_version_parts: Dict[str, Any] = match.groupdict() @@ -624,7 +595,7 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version": ``major``, ``minor``, ``patch``, ``prerelease``, or ``build`` :return: the new :class:`Version` object with the changed parts - :raises TypeError: if ``parts`` contains invalid keys + :raises TypeError: if ``parts`` contain invalid keys """ version = self.to_dict() version.update(parts) @@ -632,7 +603,7 @@ def replace(self, **parts: Union[int, Optional[str]]) -> "Version": return Version(**version) # type: ignore except TypeError: unknownkeys = set(parts) - set(self.to_dict()) - error = "replace() got %d unexpected keyword " "argument(s): %s" % ( + error = "replace() got %d unexpected keyword argument(s): %s" % ( len(unknownkeys), ", ".join(unknownkeys), ) diff --git a/tests/conftest.py b/tests/conftest.py index f7f927cf..0450e0ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,7 @@ @pytest.fixture(autouse=True) def add_semver(doctest_namespace): - doctest_namespace["Version"] = semver.Version + doctest_namespace["Version"] = semver.version.Version doctest_namespace["semver"] = semver doctest_namespace["coerce"] = coerce doctest_namespace["SemVerWithVPrefix"] = SemVerWithVPrefix diff --git a/tests/test_typeerror-274.py b/tests/test_typeerror-274.py index 61480bcf..326304b8 100644 --- a/tests/test_typeerror-274.py +++ b/tests/test_typeerror-274.py @@ -1,95 +1,14 @@ -import sys - import pytest - import semver -import semver.version - -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 - - -def ensure_binary(s, encoding="utf-8", errors="strict"): - """ - Coerce ``s`` to bytes. - - * `str` -> encoded to `bytes` - * `bytes` -> `bytes` - - :param s: the string to convert - :type s: str | bytes - :param encoding: the encoding to apply, defaults to "utf-8" - :type encoding: str - :param errors: set a different error handling scheme; - other possible values are `ignore`, `replace`, and - `xmlcharrefreplace` as well as any other name - registered with :func:`codecs.register_error`. - Defaults to "strict". - :type errors: str - :raises TypeError: if ``s`` is not str or bytes type - :return: the converted string - :rtype: str - """ - if isinstance(s, str): - return s.encode(encoding, errors) - elif isinstance(s, bytes): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) -def test_should_work_with_string_and_unicode(): +def test_should_work_with_string_and_bytes(): result = semver.compare("1.1.0", b"1.2.2") assert result == -1 result = semver.compare(b"1.1.0", "1.2.2") assert result == -1 -class TestEnsure: - # From six project - # grinning face emoji - UNICODE_EMOJI = "\U0001F600" - BINARY_EMOJI = b"\xf0\x9f\x98\x80" - - def test_ensure_binary_raise_type_error(self): - with pytest.raises(TypeError): - semver.version.ensure_str(8) - - def test_errors_and_encoding(self): - ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="ignore") - with pytest.raises(UnicodeEncodeError): - ensure_binary(self.UNICODE_EMOJI, encoding="latin-1", errors="strict") - - def test_ensure_binary_raise(self): - converted_unicode = ensure_binary( - self.UNICODE_EMOJI, encoding="utf-8", errors="strict" - ) - converted_binary = ensure_binary( - self.BINARY_EMOJI, encoding="utf-8", errors="strict" - ) - - # PY3: str -> bytes - assert converted_unicode == self.BINARY_EMOJI and isinstance( - converted_unicode, bytes - ) - # PY3: bytes -> bytes - assert converted_binary == self.BINARY_EMOJI and isinstance( - converted_binary, bytes - ) - - def test_ensure_str(self): - converted_unicode = semver.version.ensure_str( - self.UNICODE_EMOJI, encoding="utf-8", errors="strict" - ) - converted_binary = semver.version.ensure_str( - self.BINARY_EMOJI, encoding="utf-8", errors="strict" - ) - - # PY3: str -> str - assert converted_unicode == self.UNICODE_EMOJI and isinstance( - converted_unicode, str - ) - # PY3: bytes -> str - assert converted_binary == self.UNICODE_EMOJI and isinstance( - converted_unicode, str - ) +def test_should_not_work_with_invalid_args(): + with pytest.raises(TypeError): + semver.version.Version.parse(8) diff --git a/tox.ini b/tox.ini index 73fbfc58..8c7eb5e5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,17 @@ [tox] envlist = - flake8 + checks py{36,37,38,39,310} - docs - mypy isolated_build = True +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 + 3.9: py39 + 3.10: py310 + [testenv] description = Run test suite for {basepython} @@ -67,15 +73,7 @@ deps = -r{toxinidir}/docs/requirements.txt skip_install = true allowlist_externals = make - rm echo - sed -commands_pre = - sphinx-apidoc --module-first -f --separate -H semver -o docs/_api src/semver src/semver/_types.py src/semver/_deprecated.py - # we don't need this, it just add another level and it's all in docs/api.rst - - rm docs/_api/modules.rst - # Include the semver.__about__ module before semver.cli: - sed -i '/semver\.cli/i\ \ \ semver.__about__' docs/_api/semver.rst commands = make -C docs html commands_post = @@ -103,6 +101,7 @@ commands = [testenv:changelog] description = Run towncrier to check, build, or create the CHANGELOG.rst basepython = python3 +skip_install = true deps = git+https://github.com/twisted/towncrier.git commands = pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy