Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove incremental #491

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ jobs:

- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel
python -m pip install --upgrade pip wheel
python -m pip install pep517

- name: Display structure of files to be pushed
Expand Down
13 changes: 6 additions & 7 deletions RELEASE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ The same branch is used for the release candidated and the final release.
In the end, the release branch is merged into the main branch.

Update the version to the release candidate with the first being ``rc1`` (as opposed to 0).
In ``src/towncrier/_version.py`` the version is set using ``incremental`` such as::
In ``pyproject.toml`` the version is set like::

__version__ = Version('towncrier', 19, 9, 0, release_candidate=1)
version = "19.9.0.rc1"

Run ``venv/bin/towncrier build --yes`` to generate the news release NEWS file.
Commit and push to the primary repository, not a fork.
Expand Down Expand Up @@ -59,9 +59,9 @@ Final release
Once the PR is approved, you can trigger the final release.

Update the version to the final version.
In ``src/towncrier/_version.py`` the version is set using ``incremental`` such as::
In ``pyproject.toml`` the version is set like::

__version__ = Version('towncrier', 19, 9, 0)
version = "19.9.0"

Manually update the `NEWS.rst` file to include the final release version and date.
Usually it will look like this::
Expand All @@ -88,10 +88,9 @@ Similar to the release candidate, with the difference:
No need for another review request.

Update the version to the development version.
In ``src/towncrier/_version.py`` the version is set using ``incremental`` such as::

__version__ = Version('towncrier', 19, 9, 1, dev=0)
In ``pyproject.toml`` the version is set like::

version = "19.9.1.dev0"

Commit and push the changes.

Expand Down
32 changes: 0 additions & 32 deletions admin/canonicalize_version.py

This file was deleted.

13 changes: 6 additions & 7 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
from datetime import date
import importlib.metadata as importlib_metadata

from towncrier import __version__ as towncrier_version
from datetime import date


towncrier_version = importlib_metadata.version("towncrier")
extensions = []

# Add any paths that contain templates here, relative to this directory.
Expand All @@ -53,19 +54,17 @@
project = "Towncrier"
copyright = "{}, Towncrier contributors. Ver {}".format(
_today.year,
towncrier_version.public(),
importlib_metadata.version("towncrier"),
)
author = "Amber Brown"

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
# The short X.Y version.
version = "{}.{}.{}".format(
towncrier_version.major, towncrier_version.minor, towncrier_version.micro
)
version = ".".join(towncrier_version.split(".")[:3])
# The full version, including alpha/beta/rc tags.
release = towncrier_version.public()
release = towncrier_version

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
Expand Down
16 changes: 11 additions & 5 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ The ``.gitignore`` will remain and keep Git from not tracking the directory.
Detecting Dates & Versions
--------------------------

``towncrier`` needs to know what version your project is, and there are two ways you can give it:
``towncrier`` needs to know what version your project is.

- For Python 2/3-compatible projects, a ``__version__`` in the top level package.
This can be either a string literal, a tuple, or an `Incremental <https://github.com/twisted/incremental>`_ version.
- Manually passing ``--version=<myversionhere>`` when interacting with ``towncrier``.
For Python projects, the version can be automatically determined in one of the following ways:

As an example, if your package doesn't have a ``__version__``, you can manually specify it when calling ``towncrier`` on the command line with the ``--version`` flag::
- if the project is installed, the version can be read from the package's metadata
- the version can be provided in a ``__version__`` attribute of the top level package (as a string literal, a tuple, or an `Incremental <https://github.com/twisted/incremental>`_ version)

For other projects, you can store the version in the ``towncrier.toml`` file::

[tool.towncrier]
version = "1.0.0"

If you don't want to store the version, you can manually pass ``--version=<myversionhere>`` whenever interacting with ``towncrier``. For example::

$ towncrier build --version=1.2.3post4

Expand Down
16 changes: 2 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[build-system]
requires = [
"hatchling ~= 1.17.1",
"incremental == 22.10.0",
"wheel ~= 0.38.4",
]
build-backend = "hatchling.build"


[project]
dynamic = ["version"]
name = "towncrier"
version = "23.6.1.dev0"
description = "Building newsfiles for your project."
readme = "README.rst"
license = "MIT"
Expand All @@ -30,7 +30,6 @@ dependencies = [
"click",
"click-default-group",
"importlib-resources>=5; python_version<'3.10'",
"incremental",
"jinja2",
"tomli; python_version<'3.11'",
]
Expand All @@ -57,11 +56,6 @@ Coverage = "https://codecov.io/gh/twisted/towncrier"
Distribution = "https://pypi.org/project/towncrier"


[tool.hatch.version]
source = "code"
path = "src/towncrier/_version.py"
expression = "_hatchling_version"

[tool.hatch.build]
exclude = [
"admin",
Expand Down Expand Up @@ -150,12 +144,6 @@ module = 'click_default_group'
# 2022-09-04: This library has no type annotations.
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = 'incremental'
# No released version with type hints.
ignore_missing_imports = true


[tool.coverage.run]
parallel = true
branch = true
Expand Down
26 changes: 0 additions & 26 deletions src/towncrier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,3 @@
"""
towncrier, a builder for your news files.
"""

from __future__ import annotations

from incremental import Version


__all__ = ["__version__"]


def __getattr__(name: str) -> Version:
if name != "__version__":
raise AttributeError(f"module {__name__} has no attribute {name}")

import warnings

from ._version import __version__

warnings.warn(
"Accessing towncrier.__version__ is deprecated and will be "
"removed in a future release. Use importlib.metadata directly "
"to query for towncrier's packaging metadata.",
DeprecationWarning,
stacklevel=2,
)

return __version__
Comment on lines -16 to -32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this should be removed. It seems like there's still a bit of software relying on pkg.__version__ such that I've starte de-deprecating it in my own packages. See also python-attrs/attrs#1136

(to be clear: it has to be ported, not just left around ;))

54 changes: 26 additions & 28 deletions src/towncrier/_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

from __future__ import annotations

import importlib.metadata as importlib_metadata
import sys

from importlib import import_module
from types import ModuleType

from incremental import Version as IncrementalVersion
from typing import Any


def _get_package(package_dir: str, package: str) -> ModuleType:
Expand All @@ -38,47 +38,45 @@ def _get_package(package_dir: str, package: str) -> ModuleType:


def get_version(package_dir: str, package: str) -> str:
module = _get_package(package_dir, package)
version: Any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Any? Any opts version completely out of type checking, is there a rescue for that?


module = _get_package(package_dir, package)
version = getattr(module, "__version__", None)

if not version:
raise Exception("No __version__, I don't know how else to look")
try:
Copy link
Member

@adiroiban adiroiban Jul 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like this is not only about removing incremental, but also about using importlib_metadata to get the version of the target project

I think is best to do this as part of a separate PR ..l like #502

version = importlib_metadata.version(package)
except importlib_metadata.PackageNotFoundError:
raise Exception(f"Package not installed and no {package}.__version__ found")

if isinstance(version, str):
return version.strip()

if isinstance(version, IncrementalVersion):
# FIXME:https://github.com/twisted/incremental/issues/81
# Incremental uses `.rcN`.
# importlib uses `rcN` (without a dot separation).
# Here we make incremental work like importlib.
return version.base().strip().replace(".rc", "rc")

if isinstance(version, tuple):
return ".".join(map(str, version)).strip()

# Try duck-typing as an Incremental version.
if hasattr(version, "base"):
try:
version = str(version.base()).strip()
# Incremental uses `X.Y.rcN`.
# Standardize on importlib (and PEP440) use of `X.YrcN`:
return version.replace(".rc", "rc") # type: ignore
except TypeError:
pass

raise Exception(
"I only know how to look at a __version__ that is a str, "
"an Increment Version, or a tuple. If you can't provide "
"that, use the --version argument and specify one."
"Version must be a string, tuple, or an Incremental Version."
" If you can't provide that, use the --version argument and specify one."
)


def get_project_name(package_dir: str, package: str) -> str:
module = _get_package(package_dir, package)

version = getattr(module, "__version__", None)
# Incremental has support for package names, try duck-typing it.
try:
return str(version.package) # type: ignore
except AttributeError:
pass
Comment on lines +77 to +80
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
try:
return str(version.package) # type: ignore
except AttributeError:
pass
with contextlib.suppress(AttributeError):
return str(version.package) # type: ignore

is more idiomatic, I think


if not version:
# welp idk
return package.title()

if isinstance(version, str):
return package.title()

if isinstance(version, IncrementalVersion):
# Incremental has support for package names
return version.package

raise TypeError(f"Unsupported type for __version__: {type(version)}")
return package.title()
11 changes: 9 additions & 2 deletions src/towncrier/_shell.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,25 @@

from __future__ import annotations

from importlib.metadata import PackageNotFoundError, version

import click

from click_default_group import DefaultGroup

from ._version import __version__
from .build import _main as _build_cmd
from .check import _main as _check_cmd
from .create import _main as _create_cmd


try:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can still keep the _version.py file but this time just just a _version string variable.

I this way, we don't have to repeat the importlib code each time we want to fetch the version

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this lazily using towncrier.__version__ if the block that I've commented on earlier is ported and kill two birds with one stone.

_version = version("towncrier")
except PackageNotFoundError: # pragma: no cover
_version = "unknown"


@click.group(cls=DefaultGroup, default="build", default_if_no_args=True)
@click.version_option(__version__.public())
@click.version_option(_version)
def cli() -> None:
"""
Towncrier is a utility to produce useful, summarised news files for your project.
Expand Down
16 changes: 0 additions & 16 deletions src/towncrier/_version.py

This file was deleted.

3 changes: 3 additions & 0 deletions src/towncrier/newsfragments/491.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Remove incremental dependency from towncrier.

Towncrier can still read incremental versions, it just doesn't rely on the package itself any more.
16 changes: 5 additions & 11 deletions src/towncrier/test/test_packaging.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
# Copyright (c) Amber Brown, 2015
# See LICENSE for details.

from incremental import Version
from twisted.trial.unittest import TestCase

from towncrier._version import _hatchling_version
import towncrier


class TestPackaging(TestCase):
def test_version_warning(self):
def no_version_attr(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this runs if it doesn't have a test_ prefix.

"""
Import __version__ from towncrier returns an Incremental version object
and raises a warning.
towncrier.__version__ was deprecated, now no longer exists.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if towncrier.__version__ was considered public or private API.

For private API, raising AttributeError is ok.
If it's public API, we need to raise a separate error with an info message.

"""
with self.assertWarnsRegex(
DeprecationWarning, "Accessing towncrier.__version__ is deprecated.*"
):
from towncrier import __version__

self.assertIsInstance(__version__, Version)
self.assertEqual(_hatchling_version, __version__.short())
with self.assertRaises(AttributeError):
towncrier.__version__