feat/configurable-groups #2
6 changed files with 161 additions and 22 deletions
17
README.md
17
README.md
|
@ -32,3 +32,20 @@ extra messaging that isn't part of the changelog itself.
|
||||||
### Commit format
|
### Commit format
|
||||||
|
|
||||||
Some assumptions are made about commit message formatting that will later be made more configurable. It is expected that commits conforms to the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) scheme.
|
Some assumptions are made about commit message formatting that will later be made more configurable. It is expected that commits conforms to the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) scheme.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The groupings can be configured using a YAML file formatted as follows:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
groups:
|
||||||
|
- label: <Heading to identify the group with in the generated log>
|
||||||
|
prefixes: [<prefix1>, <prefix2, ...]
|
||||||
|
- label: ...
|
||||||
|
prefixes: ...
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Each group is headed by its `label` value and contains a list of all the commits matching the prefix selection for that group.
|
||||||
|
|
||||||
|
If not supplied, a default configuration (see `modif.configuration.get_default_config`) is substituted in.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from .git import Commit
|
from .git import Commit
|
||||||
|
from .configuration import Config
|
||||||
|
|
||||||
|
|
||||||
def _bucket_by_prefix(commits: list[Commit]) -> dict[str, list[Commit]]:
|
def _bucket_by_prefix(commits: list[Commit]) -> dict[str, list[Commit]]:
|
||||||
|
@ -21,27 +22,24 @@ def _bucket_by_prefix(commits: list[Commit]) -> dict[str, list[Commit]]:
|
||||||
return bucketed
|
return bucketed
|
||||||
|
|
||||||
|
|
||||||
def build_changelog(commits: list[Commit]) -> str:
|
def build_changelog(config: Config, commits: list[Commit]) -> str:
|
||||||
|
"""Generates a Markdown changelog from the given commits, bucketed by type."""
|
||||||
|
|
||||||
categorized_commits = _bucket_by_prefix(commits)
|
categorized_commits = _bucket_by_prefix(commits)
|
||||||
|
|
||||||
features = [
|
changelog_lines = ["# Changes"]
|
||||||
f"- {commit.subject} ({commit.author})"
|
|
||||||
for commit in categorized_commits["feat"]
|
|
||||||
]
|
|
||||||
fixes = [
|
|
||||||
f"- {commit.subject} ({commit.author})" for commit in categorized_commits["fix"]
|
|
||||||
]
|
|
||||||
|
|
||||||
formatted = f"""
|
for group in config.groups:
|
||||||
# Changes
|
selected_commits = []
|
||||||
|
for prefix in group.prefixes:
|
||||||
|
selected_commits.extend(categorized_commits.get(prefix, []))
|
||||||
|
|
||||||
## Features
|
if not selected_commits:
|
||||||
|
continue
|
||||||
|
|
||||||
{"\n".join(features)}
|
changelog_lines.append(f"\n## {group.label}\n")
|
||||||
|
|
||||||
## Bug fixes
|
for commit in selected_commits:
|
||||||
|
changelog_lines.append(f"- {commit.subject} ({commit.author})")
|
||||||
|
|
||||||
{"\n".join(fixes)}
|
return "\n".join(changelog_lines)
|
||||||
"""
|
|
||||||
|
|
||||||
return formatted
|
|
||||||
|
|
35
modif/configuration.py
Normal file
35
modif/configuration.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import pydantic
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
import typing
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
class GroupConfig(pydantic.BaseModel):
|
||||||
|
label: str
|
||||||
|
prefixes: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class Config(pydantic.BaseModel):
|
||||||
|
groups: list[GroupConfig]
|
||||||
|
|
||||||
|
|
||||||
|
def load_configuration(path: str) -> Config | typing.NoReturn:
|
||||||
|
"""Loads configuration from the given file."""
|
||||||
|
config_path = pathlib.Path(path)
|
||||||
|
|
||||||
|
if not config_path.exists():
|
||||||
|
raise FileNotFoundError(f"Configuration not found at {path}")
|
||||||
|
|
||||||
|
with open(config_path, "r", encoding="utf8") as config_file:
|
||||||
|
return Config(**yaml.safe_load(config_file))
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_config() -> Config:
|
||||||
|
return Config(
|
||||||
|
groups=[
|
||||||
|
GroupConfig(label="Features", prefixes=["feat"]),
|
||||||
|
GroupConfig(label="Fixes", prefixes=["fix", "perf"]),
|
||||||
|
GroupConfig(label="Other changes", prefixes=["docs", "chore", "wip", "ci"]),
|
||||||
|
]
|
||||||
|
)
|
|
@ -2,11 +2,20 @@ import click
|
||||||
|
|
||||||
from . import git
|
from . import git
|
||||||
from . import changelog
|
from . import changelog
|
||||||
|
from . import configuration
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
def cli():
|
@click.option(
|
||||||
pass
|
"--config", "-c", "config_path", type=str, help="Path to custom configuration."
|
||||||
|
)
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx, config_path: str | None):
|
||||||
|
ctx.obj = {
|
||||||
|
"config": configuration.load_configuration(config_path)
|
||||||
|
if config_path
|
||||||
|
else configuration.get_default_config()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@ -24,9 +33,10 @@ def cli():
|
||||||
default=False,
|
default=False,
|
||||||
help="Prints the changelog output only, without headers or colours.",
|
help="Prints the changelog output only, without headers or colours.",
|
||||||
)
|
)
|
||||||
def log(from_ref: str, to_ref: str, raw: bool = False):
|
@click.pass_obj
|
||||||
|
def log(ctx, from_ref: str, to_ref: str, raw: bool = False):
|
||||||
commits = git.log(from_ref, to_ref)
|
commits = git.log(from_ref, to_ref)
|
||||||
cl = changelog.build_changelog(commits)
|
cl = changelog.build_changelog(ctx["config"], commits)
|
||||||
|
|
||||||
if not raw:
|
if not raw:
|
||||||
click.secho(
|
click.secho(
|
||||||
|
|
|
@ -6,6 +6,8 @@ readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.1.7",
|
"click>=8.1.7",
|
||||||
|
"pydantic>=2.9.2",
|
||||||
|
"pyyaml>=6.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|
79
uv.lock
79
uv.lock
|
@ -1,5 +1,18 @@
|
||||||
version = 1
|
version = 1
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version < '3.13'",
|
||||||
|
"python_full_version >= '3.13'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfgv"
|
name = "cfgv"
|
||||||
|
@ -64,6 +77,8 @@ version = "0.0.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "pyyaml" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
|
@ -73,7 +88,11 @@ dev = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [{ name = "click", specifier = ">=8.1.7" }]
|
requires-dist = [
|
||||||
|
{ name = "click", specifier = ">=8.1.7" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.9.2" },
|
||||||
|
{ name = "pyyaml", specifier = ">=6.0.2" },
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
dev = [
|
dev = [
|
||||||
|
@ -115,6 +134,55 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 },
|
{ url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.9.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.23.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.2"
|
version = "6.0.2"
|
||||||
|
@ -166,6 +234,15 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
|
{ url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.12.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "virtualenv"
|
name = "virtualenv"
|
||||||
version = "20.27.1"
|
version = "20.27.1"
|
||||||
|
|
Loading…
Reference in a new issue