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

[pull] master from python-semantic-release:master #99

Merged
merged 21 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
335d266
refactor(parser-base): re-define default option initialization
codejedi365 Apr 27, 2024
365308d
refactor(parser-angular): implement `get_default_options()`
codejedi365 Apr 27, 2024
1f7eab5
refactor(parser-emoji): implement `get_default_options()`
codejedi365 Apr 27, 2024
565eb05
refactor(parser-scipy): implement `get_default_options()`
codejedi365 Apr 27, 2024
ca33915
refactor(parser-tag): implement `get_default_options()`
codejedi365 Apr 27, 2024
7dc4be9
refactor(cli-config): use `get_default_options()` for parser option i…
codejedi365 Apr 27, 2024
03a0ab8
refactor(parser-scipy): improve maintainability of parser options
codejedi365 Apr 27, 2024
83c5146
test(fixtures): redefine parser fixtures with new default option func…
codejedi365 Apr 27, 2024
0ac2e42
test(fixtures): refactor make_commit_obj into fixture
codejedi365 Apr 27, 2024
b7a7de7
test(parser-angular): adapt tests to updated fixtures & option initia…
codejedi365 Apr 27, 2024
0b29e0a
test(parser-emoji): adapt tests to updated fixtures & option initiali…
codejedi365 Apr 27, 2024
5485d45
test(parser-tag): adapt tests to updated fixtures & option initializa…
codejedi365 Apr 27, 2024
99859c0
test(parser): adapt tests to new create commit fixture
codejedi365 Apr 27, 2024
a805183
test(parser-scipy): refactor for decreased scipy testing duration
codejedi365 Apr 27, 2024
41ce1cb
style: beautify formatting
codejedi365 Apr 27, 2024
f683bc5
refactor(parser-base): maintain current compatibility w/ custom parsers
codejedi365 Apr 28, 2024
5f8b7a1
test(fixtures): add fixture for custom parser use
codejedi365 Apr 29, 2024
f783b0e
test(parser-custom): add custom parser import & initialize validation
codejedi365 Apr 29, 2024
67f6038
fix(parser-custom): gracefully handle custom parser import errors
codejedi365 Apr 29, 2024
e535e00
style: beautify formatting
codejedi365 Apr 29, 2024
02fa4d2
9.6.0
invalid-email-address Apr 29, 2024
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
83 changes: 83 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,89 @@



## v9.6.0 (2024-04-29)

### Feature

* feat: changelog filters are specialized per vcs type (#890)

* test(github): sync pr url expectation with GitHub api documentation

* fix(github): correct changelog filter for pull request urls

* refactor(hvcs-base): change to an abstract class & simplify interface

* refactor(remote-hvcs-base): extend the base abstract class with common remote base class

* refactor(github): adapt to new abstract base class

* refactor(gitea): adapt to new abstract base class

* refactor(gitlab): adapt to new abstract base class

* refactor(bitbucket): adapt to new abstract base class

* refactor(cmds): prevent hvcs from executing if not remote hosted vcs

* feat(changelog): changelog filters are hvcs focused

* test(hvcs): add validation for issue_url generation

* feat(changelog-github): add issue url filter to changelog context

* feat(changelog-gitea): add issue url filter to changelog context

* refactor(cmd-version): consolidate asset uploads with release creation

* style: resolve ruff errors

* feat(changelog-context): add flag to jinja env for which hvcs is available

* test(changelog-context): demonstrate per hvcs filters upon render

* docs(changelog-context): explain new hvcs specific context filters

* refactor(config): adjust default token resolution w/ subclasses ([`76ed593`](https://github.com/python-semantic-release/python-semantic-release/commit/76ed593ea33c851005994f0d1a6a33cc890fb908))

### Fix

* fix(parser-custom): gracefully handle custom parser import errors ([`67f6038`](https://github.com/python-semantic-release/python-semantic-release/commit/67f60389e3f6e93443ea108c0e1b4d30126b8e06))

* fix: correct version `--prerelease` use & enable `--as-prerelease` (#647)

* test(version): add validation of `--as-prerelease` and `--prerelease opts`

* fix(version-cmd): correct `--prerelease` use

Prior to this change, `--prerelease` performed the role of converting whichever forced
version into a prerelease version declaration, which was an unintentional breaking
change to the CLI compared to v7.

`--prerelease` now forces the next version to increment the prerelease revision,
which makes it consistent with `--patch`, `--minor` and `--major`. Temporarily disabled
the ability to force a prerelease.

Resolves: #639

* feat(version-cmd): add `--as-prerelease` option to force the next version to be a prerelease

Prior to this change, `--prerelease` performed the role that `--as-prerelease` now does,
which was an unintentional breaking change to the CLI compared to v7.

`--prerelease` is used to force the next version to increment the prerelease revision,
which makes it consistent with `--patch`, `--minor` and `--major`, while `--as-prerelease`
forces for the next version to be converted to a prerelease version type before it is
applied to the project regardless of the bump level.

Resolves: #639

* docs(commands): update version command options definition about prereleases

---------

Co-authored-by: codejedi365 <codejedi365@gmail.com> ([`2acb5ac`](https://github.com/python-semantic-release/python-semantic-release/commit/2acb5ac35ae79d7ae25ca9a03fb5c6a4a68b3673))


## v9.5.0 (2024-04-23)

### Build
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "python-semantic-release"
version = "9.5.0"
version = "9.6.0"
description = "Automatic Semantic Versioning for Python projects"
requires-python = ">=3.8"
license = { text = "MIT" }
Expand Down
2 changes: 1 addition & 1 deletion semantic_release/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
tags_and_versions,
)

__version__ = "9.5.0"
__version__ = "9.6.0"


def setup_hook(argv: list[str]) -> None:
Expand Down
93 changes: 79 additions & 14 deletions semantic_release/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@
TagCommitParser,
)
from semantic_release.const import COMMIT_MESSAGE, DEFAULT_COMMIT_AUTHOR, SEMVER_REGEX
from semantic_release.errors import InvalidConfiguration, NotAReleaseBranch
from semantic_release.errors import (
InvalidConfiguration,
NotAReleaseBranch,
ParserLoadError,
)
from semantic_release.helpers import dynamic_import
from semantic_release.hvcs.remote_hvcs_base import RemoteHvcsBase
from semantic_release.version import VersionTranslator
Expand Down Expand Up @@ -232,18 +236,50 @@ def set_default_opts(self) -> Self:
parser_opts_type = None
# If the commit parser is a known one, pull the default options object from it
if self.commit_parser in _known_commit_parsers:
# TODO: BREAKING CHANGE v10
# parser_opts_type = (
# _known_commit_parsers[self.commit_parser]
# .get_default_options()
# .__class__
# )
parser_opts_type = _known_commit_parsers[
self.commit_parser
].parser_options
else:
# if its a custom parser, try to import it and pull the default options object type
custom_class = dynamic_import(self.commit_parser)
if hasattr(custom_class, "parser_options"):
parser_opts_type = custom_class.parser_options
try:
# if its a custom parser, try to import it and pull the default options object type
custom_class = dynamic_import(self.commit_parser)
# TODO: BREAKING CHANGE v10
# parser_opts_type = custom_class.get_default_options().__class__
if hasattr(custom_class, "parser_options"):
parser_opts_type = custom_class.parser_options

except ModuleNotFoundError as err:
raise ParserLoadError(
str.join(
"\n",
[
str(err),
"Unable to import your custom parser! Check your configuration!",
],
)
) from err

except AttributeError as err:
raise ParserLoadError(
str.join(
"\n",
[
str(err),
"Unable to find your custom parser class inside the given module.",
"Check your configuration!",
],
)
) from err

# from either the custom opts class or the known parser opts class, create an instance
if callable(parser_opts_type):
opts_obj = parser_opts_type()
opts_obj: Any = parser_opts_type()
# if the opts object is a dataclass, wrap it in a RootModel so it can be transformed to a Mapping
opts_obj = (
opts_obj if not is_dataclass(opts_obj) else RootModel(opts_obj)
Expand Down Expand Up @@ -383,15 +419,44 @@ def from_raw_config(
branch_config = cls.select_branch_options(raw.branches, active_branch)

# commit_parser
commit_parser_cls = (
_known_commit_parsers[raw.commit_parser]
if raw.commit_parser in _known_commit_parsers
else dynamic_import(raw.commit_parser)
)
try:
commit_parser_cls = (
_known_commit_parsers[raw.commit_parser]
if raw.commit_parser in _known_commit_parsers
else dynamic_import(raw.commit_parser)
)
except ModuleNotFoundError as err:
raise ParserLoadError(
str.join(
"\n",
[
str(err),
"Unable to import your custom parser! Check your configuration!",
],
)
) from err
except AttributeError as err:
raise ParserLoadError(
str.join(
"\n",
[
str(err),
"Unable to find the parser class inside the given module",
],
)
) from err

commit_parser = commit_parser_cls(
options=commit_parser_cls.parser_options(**raw.commit_parser_options)
)
commit_parser_opts_class = commit_parser_cls.parser_options
# TODO: Breaking change v10
# commit_parser_opts_class = commit_parser_cls.get_default_options().__class__
try:
commit_parser = commit_parser_cls(
options=commit_parser_opts_class(**raw.commit_parser_options)
)
except TypeError as err:
raise ParserLoadError(
str.join("\n", [str(err), f"Failed to initialize {raw.commit_parser}"])
) from err

# We always exclude PSR's own release commits from the Changelog
# when parsing commits
Expand Down
17 changes: 13 additions & 4 deletions semantic_release/commit_parser/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,19 @@ def __init__(self, options: parser_options) -> None:
...
"""

parser_options: type[_OPTS]

def __init__(self, options: _OPTS) -> None:
self.options = options
# TODO: Deprecate in lieu of get_default_options()
parser_options: type[ParserOptions] = ParserOptions

def __init__(self, options: _OPTS | None = None) -> None:
self.options: _OPTS = (
options if options is not None else self.get_default_options()
)

# TODO: BREAKING CHANGE v10, add abstract method for all custom parsers
# @staticmethod
# @abstractmethod
def get_default_options(self) -> _OPTS:
return self.parser_options() # type: ignore

@abstractmethod
def parse(self, commit: Commit) -> _TT: ...
9 changes: 7 additions & 2 deletions semantic_release/commit_parser/angular.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ class AngularCommitParser(CommitParser[ParseResult, AngularParserOptions]):
commits. See https://www.conventionalcommits.org/en/v1.0.0-beta.4/
"""

# TODO: Deprecate in lieu of get_default_options()
parser_options = AngularParserOptions

def __init__(self, options: AngularParserOptions) -> None:
def __init__(self, options: AngularParserOptions | None = None) -> None:
super().__init__(options)
self.re_parser = re.compile(
rf"""
(?P<type>{"|".join(options.allowed_tags)}) # e.g. feat
(?P<type>{"|".join(self.options.allowed_tags)}) # e.g. feat
(?:\((?P<scope>[^\n]+)\))? # or feat(parser)
(?P<break>!)?:\s+ # breaking if feat!:
(?P<subject>[^\n]+) # commit subject
Expand All @@ -77,6 +78,10 @@ def __init__(self, options: AngularParserOptions) -> None:
flags=re.VERBOSE | re.DOTALL,
)

@staticmethod
def get_default_options() -> AngularParserOptions:
return AngularParserOptions()

# Maybe this can be cached as an optimisation, similar to how
# mypy/pytest use their own caching directories, for very large commit
# histories?
Expand Down
5 changes: 5 additions & 0 deletions semantic_release/commit_parser/emoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,13 @@ class EmojiCommitParser(CommitParser[ParseResult, EmojiParserOptions]):
the commit subject in the changelog.
"""

# TODO: Deprecate in lieu of get_default_options()
parser_options = EmojiParserOptions

@staticmethod
def get_default_options() -> EmojiParserOptions:
return EmojiParserOptions()

def parse(self, commit: Commit) -> ParseResult:
all_emojis = (
self.options.major_tags + self.options.minor_tags + self.options.patch_tags
Expand Down
25 changes: 12 additions & 13 deletions semantic_release/commit_parser/scipy.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,26 +82,20 @@ def _logged_parse_error(commit: Commit, error: str) -> ParseError:

@dataclass
class ScipyParserOptions(ParserOptions):
major_tags: Tuple[str, ...] = ("API",)
minor_tags: Tuple[str, ...] = ("DEP", "DEV", "ENH", "REV", "FEAT")
patch_tags: Tuple[str, ...] = ("BLD", "BUG", "MAINT")
allowed_tags: Tuple[str, ...] = (
"API",
"DEP",
"ENH",
"REV",
"BUG",
"MAINT",
*major_tags,
*minor_tags,
*patch_tags,
"BENCH",
"BLD",
"DEV",
"DOC",
"STY",
"TST",
"REL",
"FEAT",
"TEST",
)
major_tags: Tuple[str, ...] = ("API",)
minor_tags: Tuple[str, ...] = ("DEP", "DEV", "ENH", "REV", "FEAT")
patch_tags: Tuple[str, ...] = ("BLD", "BUG", "MAINT")
default_level_bump: LevelBump = LevelBump.NO_RELEASE

def __post_init__(self) -> None:
Expand All @@ -117,9 +111,10 @@ def __post_init__(self) -> None:
class ScipyCommitParser(CommitParser[ParseResult, ScipyParserOptions]):
"""Parser for scipy-style commit messages"""

# TODO: Deprecate in lieu of get_default_options()
parser_options = ScipyParserOptions

def __init__(self, options: ScipyParserOptions) -> None:
def __init__(self, options: ScipyParserOptions | None = None) -> None:
super().__init__(options)
self.re_parser = re.compile(
rf"(?P<tag>{_COMMIT_FILTER})?"
Expand All @@ -130,6 +125,10 @@ def __init__(self, options: ScipyParserOptions) -> None:
re.DOTALL,
)

@staticmethod
def get_default_options() -> ScipyParserOptions:
return ScipyParserOptions()

def parse(self, commit: Commit) -> ParseResult:
message = str(commit.message)
parsed = self.re_parser.match(message)
Expand Down
5 changes: 5 additions & 0 deletions semantic_release/commit_parser/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ class TagCommitParser(CommitParser[ParseResult, TagParserOptions]):
first line as changelog content.
"""

# TODO: Deprecate in lieu of get_default_options()
parser_options = TagParserOptions

@staticmethod
def get_default_options() -> TagParserOptions:
return TagParserOptions()

def parse(self, commit: Commit) -> ParseResult:
message = str(commit.message)

Expand Down
7 changes: 7 additions & 0 deletions semantic_release/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,10 @@ class AssetUploadError(SemanticReleaseBaseError):
Raised when there is a failure uploading an asset to a remote hvcs's release artifact
storage.
"""


class ParserLoadError(SemanticReleaseBaseError):
"""
Raised when there is a failure to find, load, or instaniate a custom parser
definition.
"""
Loading