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

Allowed fallback usage of project.name for name and package if pyproject.toml exists #687

Merged
merged 6 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ A minimal configuration for a Python project looks like this:
.. code-block:: toml

# pyproject.toml

[tool.towncrier]
package = "myproject"
[project]
name = "myproject"

A minimal configuration for a non-Python project looks like this:

Expand All @@ -36,9 +35,9 @@ Top level keys
``name``
The name of your project.

For Python projects that provide a ``package`` key, if left empty then the name will be automatically determined.
For Python projects that provide a ``package`` key, if left empty then the name will be automatically determined from the ``package`` key.

``""`` by default.
Defaults to the key ``[project.name]`` in ``pyproject.toml`` (if present), otherwise defaults to the empty string ``""``.

``version``
The version of your project.
Expand Down Expand Up @@ -167,6 +166,8 @@ Extra top level keys for Python projects
Allows ``name`` and ``version`` to be automatically determined from the Python package.
Changes the default ``directory`` to be a ``newsfragments`` directory within this package.

Defaults to the key ``[project.name]`` in ``pyproject.toml`` (if present), otherwise defaults to the empty string ``""``.

``package_dir``
The folder your package lives.

Expand Down
40 changes: 31 additions & 9 deletions src/towncrier/_settings/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,44 @@ def load_config(directory: str) -> Config | None:
towncrier_toml = os.path.join(directory, "towncrier.toml")
pyproject_toml = os.path.join(directory, "pyproject.toml")

# In case the [tool.towncrier.name|package] is not specified
# we'll read it from [project.name]

if os.path.exists(pyproject_toml):
pyproject_config = load_toml_from_file(pyproject_toml)
else:
# make it empty so it won't be used as a backup plan
pyproject_config = {}

if os.path.exists(towncrier_toml):
config_file = towncrier_toml
config_toml = towncrier_toml
elif os.path.exists(pyproject_toml):
config_file = pyproject_toml
config_toml = pyproject_toml
else:
return None

return load_config_from_file(directory, config_file)
# Read the default configuration. Depending on which exists
config = load_config_from_file(directory, config_toml)

# Fallback certain values depending on the [project.name]
if project_name := pyproject_config.get("project", {}).get("name", ""):
# Fallback to the project name for the configuration name
# and the configuration package entries.
if not config.name:
config.name = project_name
if not config.package:
config.package = project_name

def load_config_from_file(directory: str, config_file: str) -> Config:
return config


def load_toml_from_file(config_file: str) -> Mapping[str, Any]:
with open(config_file, "rb") as conffile:
config = tomllib.load(conffile)
return tomllib.load(conffile)


def load_config_from_file(directory: str, config_file: str) -> Config:
config = load_toml_from_file(config_file)

return parse_toml(directory, config)

Expand All @@ -141,10 +166,7 @@ def load_config_from_file(directory: str, config_file: str) -> Config:


def parse_toml(base_path: str, config: Mapping[str, Any]) -> Config:
if "towncrier" not in (config.get("tool") or {}):
raise ConfigError("No [tool.towncrier] section.", failing_option="all")

config = config["tool"]["towncrier"]
config = config.get("tool", {}).get("towncrier", {})
parsed_data = {}

# Check for misspelt options.
Expand Down
1 change: 1 addition & 0 deletions src/towncrier/newsfragments/687.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Enabled fallback values of [tool.towncrier.name|package] to [project.name] if pyproject.toml files are used
52 changes: 36 additions & 16 deletions src/towncrier/test/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,22 +112,6 @@ def test_template_extended(self):

self.assertEqual(config.template, ("towncrier.templates", "default.rst"))

def test_missing(self):
Copy link
Member

Choose a reason for hiding this comment

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

why was this test removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

because now towncrier does not need any tool.towncrier section for Python projects using pyproject.toml files.

A pyproject.toml file requires the name field, and hence everything can be used without problems.

The removed test checked that it broke when tool.towncrier was not present, simply because it is not required.

"""
If the config file doesn't have the correct toml key, we error.
"""
project_dir = self.mktemp_project(
pyproject_toml="""
[something.else]
blah='baz'
"""
)

with self.assertRaises(ConfigError) as e:
load_config(project_dir)

self.assertEqual(e.exception.failing_option, "all")

def test_incorrect_single_file(self):
"""
single_file must be a bool.
Expand Down Expand Up @@ -194,6 +178,42 @@ def test_towncrier_toml_preferred(self):
config = load_config(project_dir)
self.assertEqual(config.package, "a")

def test_pyproject_name_fallback_both(self):
"""
Towncrier will fallback to the [project.name] value in pyproject.toml.
Copy link
Member

Choose a reason for hiding this comment

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

Just a small suggestion to help understand the scope of this test.

Suggested change
Towncrier will fallback to the [project.name] value in pyproject.toml.
To obtain the name of the project, when there is no dedicated [tool.towncrier] section in pyproject.toml, it will fallback to the `name` option from the [project] section.

Just asking. Is towncrier usable without a configuration section ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. When it is a Python project with a pyproject.toml file, then a configuration file / setting is not required.

"""
project_dir = self.mktemp_project(
pyproject_toml="""
[project]
name = "a"
""",
)

config = load_config(project_dir)
self.assertEqual(config.package, "a")
self.assertEqual(config.name, "a")

def test_pyproject_name_fallback_towncrier(self):
"""
Towncrier will fallback to the [project.name] value in pyproject.toml.
"""
project_dir = self.mktemp_project(
towncrier_toml="""
[tool.towncrier]
package = "a"
""",
pyproject_toml="""
[project]
name = "c"
[tool.towncrier]
name = "b"
""",
)

config = load_config(project_dir)
self.assertEqual(config.package, "a")
self.assertEqual(config.name, "c")

@with_isolated_runner
def test_load_no_config(self, runner: CliRunner):
"""
Expand Down
Loading