makepatch 0.0.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ ../.editorconfig
@@ -0,0 +1,6 @@
1
+ ### Python
2
+ **/__pycache__/
3
+ *.py[cod]
4
+
5
+ ### Patches
6
+ **/work/patched/
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: makepatch
3
+ Version: 0.2.0
4
+ Summary: Hatch build hook to easily hard-fork packages with patches!
5
+ Project-URL: source, https://github.com/AlphaKR93/packages/tree/main/python/makepatch
6
+ Project-URL: issues, https://github.com/AlphaKR93/packages/issues
7
+ Author-email: Alpha <dev@alpha93.kr>
8
+ License-Expression: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Environment :: Plugins
12
+ Classifier: Framework :: Hatch
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: hatchling>=1.29.0
22
+ Requires-Dist: pydantic>=2.13.0
23
+ Requires-Dist: typing-extensions>=4.15.0; python_version < '3.15'
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ build-backend = "hatchling.build"
3
+ requires = ["hatchling"]
4
+
5
+ [project.entry-points.hatch]
6
+ makepatch = "makepatch.__hooks__"
7
+
8
+ [project.scripts]
9
+ apply-patches = "makepatch.scripts:apply_patches"
10
+ rebuild-patches = "makepatch.scripts:rebuild_patches"
11
+
12
+
13
+ [project]
14
+ name = "makepatch"
15
+ version = "0.2.0"
16
+ authors = [{ name = "Alpha", email = "dev@alpha93.kr" }]
17
+ description = "Hatch build hook to easily hard-fork packages with patches!"
18
+ license = "MIT"
19
+ requires-python = ">=3.12"
20
+ classifiers = [
21
+ "Development Status :: 4 - Beta",
22
+ "Intended Audience :: Developers",
23
+ "Environment :: Console",
24
+ "Environment :: Plugins",
25
+ "Framework :: Hatch",
26
+
27
+ "Programming Language :: Python",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Programming Language :: Python :: 3.14",
31
+ "Programming Language :: Python :: Implementation :: CPython",
32
+
33
+ "Topic :: Software Development :: Build Tools",
34
+ ]
35
+ dependencies = [
36
+ "hatchling>=1.29.0",
37
+ "pydantic>=2.13.0",
38
+ "typing_extensions>=4.15.0; python_version < '3.15'",
39
+ ]
40
+
41
+ [dependency-groups]
42
+ dev = []
43
+
44
+ [project.urls]
45
+ # homepage = "https://alpha93.kr/project/python/makepatch"
46
+ source = "https://github.com/AlphaKR93/packages/tree/main/python/makepatch"
47
+ issues = "https://github.com/AlphaKR93/packages/issues"
48
+
@@ -0,0 +1,7 @@
1
+ from hatchling.plugin import hookimpl
2
+ from makepatch.hooks import PatcherBuildHook
3
+
4
+
5
+ @hookimpl
6
+ def hatch_register_build_hook():
7
+ return PatcherBuildHook
@@ -0,0 +1,2 @@
1
+ from .__hook__ import hatch_register_build_hook
2
+ from .hooks import PatcherBuildHook
@@ -0,0 +1,59 @@
1
+ import os
2
+ from collections.abc import Iterable
3
+ from pathlib import Path
4
+ from typing import final, Annotated, ClassVar
5
+
6
+ from pydantic import BaseModel, Field
7
+ from pydantic.json_schema import SkipJsonSchema
8
+
9
+ if __debug__ and __import__("typing").TYPE_CHECKING:
10
+
11
+ from makepatch._types.hatch import HatchHookInterface
12
+
13
+
14
+ @final
15
+ class PatcherConfig(BaseModel):
16
+ __INTERNAL: ClassVar[object] = object()
17
+ __INSTANCE: ClassVar[PatcherConfig | None] = None
18
+
19
+ module_source: Annotated[str, Field(alias="module-source")]
20
+ excludes: Annotated[Iterable[str], Field(default_factory=frozenset)]
21
+ dev_excludes: Annotated[Iterable[str], Field(alias="dev-excludes", default_factory=frozenset)]
22
+
23
+ module_name: Annotated[SkipJsonSchema[str], Field(exclude=True)]
24
+ project_root: Annotated[SkipJsonSchema[Path], Field(exclude=True)]
25
+
26
+ def __init__(self, obj: object, /, **kwargs):
27
+ if obj is not self.__INTERNAL:
28
+ raise RuntimeError("Security error: External initialization is prohibited")
29
+ if "/" in kwargs["module-source"]:
30
+ _, kwargs["module_name"] = str(kwargs["module-source"]).rsplit(os.sep, 1)
31
+ else:
32
+ kwargs["module_name"] = kwargs["module-source"]
33
+ super().__init__(**kwargs)
34
+
35
+ @classmethod
36
+ def from_hatch(cls, hook: HatchHookInterface, /) -> PatcherConfig:
37
+ if PatcherConfig.__INSTANCE:
38
+ raise RuntimeError(f"{cls.__name__} already initialized")
39
+
40
+ config = hook.config["options"]
41
+ config["project_root"] = Path(hook.root).resolve()
42
+ PatcherConfig.__INSTANCE = PatcherConfig(PatcherConfig.__INTERNAL, **config)
43
+ assert PatcherConfig.__INSTANCE
44
+ return PatcherConfig.__INSTANCE
45
+
46
+ @classmethod
47
+ def from_pyproject(cls, root: Path, /):
48
+ pyproject_toml = (root / "pyproject.toml").resolve()
49
+ if not pyproject_toml.exists() or not pyproject_toml.is_file():
50
+ raise FileNotFoundError(f"{pyproject_toml} does not exist or is not a file")
51
+
52
+ import tomllib
53
+ try:
54
+ with open(pyproject_toml, 'rb') as f:
55
+ config = tomllib.load(f)["tool"]["hatch"]["build"]["hooks"]["makepatch"]["options"]
56
+ config["project_root"] = Path(root).resolve()
57
+ return PatcherConfig(PatcherConfig.__INTERNAL, **config)
58
+ except tomllib.TOMLDecodeError as e:
59
+ raise RuntimeError(f"Cannot parse pyproject.toml") from e
@@ -0,0 +1 @@
1
+ from .hatch import HatchHookInterface
@@ -0,0 +1,57 @@
1
+ from typing import Protocol, Any
2
+
3
+ from hatchling.bridge.app import Application
4
+ from hatchling.metadata.core import ProjectMetadata
5
+
6
+
7
+ class HatchHookInterface(Protocol):
8
+ @property
9
+ def app(self, /) -> Application:
10
+ """
11
+ An instance of [Application](../utilities.md#hatchling.bridge.app.Application).
12
+ """
13
+
14
+ @property
15
+ def root(self, /) -> str:
16
+ """
17
+ The root of the project tree.
18
+ """
19
+
20
+ @property
21
+ def config(self, /) -> dict[str, Any]:
22
+ """
23
+ The cumulative hook configuration.
24
+
25
+ ```toml config-example
26
+ [tool.hatch.build.hooks.<PLUGIN_NAME>]
27
+ [tool.hatch.build.targets.<TARGET_NAME>.hooks.<PLUGIN_NAME>]
28
+ ```
29
+ """
30
+
31
+ @property
32
+ def metadata(self, /) -> ProjectMetadata:
33
+ ...
34
+
35
+ @property
36
+ def directory(self, /) -> str:
37
+ """
38
+ The build directory.
39
+ """
40
+
41
+ @property
42
+ def target_name(self, /) -> str:
43
+ """
44
+ The plugin name of the build target.
45
+ """
46
+
47
+ def dependencies(self, /) -> list[str]: # noqa: PLR6301
48
+ """
49
+ A list of extra [dependencies](../../config/dependency.md) that must be installed
50
+ prior to builds.
51
+
52
+ !!! warning
53
+ - For this to have any effect the hook dependency itself cannot be dynamic and
54
+ must always be defined in `build-system.requires`.
55
+ - As the hook must be imported to call this method, imports that require these
56
+ dependencies must be evaluated lazily.
57
+ """
@@ -0,0 +1 @@
1
+ from ._patcher import PatchApplier
@@ -0,0 +1,125 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ from typing import final
5
+
6
+ from makepatch._config import PatcherConfig
7
+
8
+
9
+ if __debug__ and __import__("typing").TYPE_CHECKING:
10
+ from pathlib import Path
11
+ from typing import Final
12
+
13
+
14
+ @final
15
+ class PatchApplier:
16
+ if __debug__ and __import__("typing").TYPE_CHECKING:
17
+ __slots__ = (
18
+ "__config",
19
+ "__excludes",
20
+ "__excludes_dev",
21
+ "__patches",
22
+ "__src",
23
+ "__sources",
24
+ )
25
+
26
+ def __init__(self, config: PatcherConfig, /) -> None:
27
+ self.__config: Final = config
28
+ self.__excludes: Final = set[str]()
29
+ self.__excludes_dev: Final = set[str]()
30
+
31
+ # resolve patches
32
+ self.__patches: Final = self.__resolve_path(config.project_root / "patches")
33
+
34
+ # INTENDED: required for later use
35
+ self.__src: Path | None = None
36
+ self.__sources: Path | None = None
37
+
38
+ def setup_required(self, /):
39
+ return not (self.__config.project_root / "work/patched").resolve().exists()
40
+
41
+ @staticmethod
42
+ def __resolve_path(path: Path, /):
43
+ path = path.resolve()
44
+ if not path.exists() or not path.is_dir():
45
+ raise RuntimeError(f"{path} does not exist or is not a directory")
46
+ return path
47
+
48
+ def resolve_src(self, /):
49
+ """
50
+ Resolves `module` src root, where the codes are placed
51
+ """
52
+ assert not self.__src
53
+ if not self.__sources:
54
+ self.__sources = self.__resolve_path(self.__config.project_root / "work/sources")
55
+ assert self.__sources
56
+ src = self.__src = self.__resolve_path(self.__sources / self.__config.module_source)
57
+
58
+ for glob in self.__config.excludes:
59
+ dirname = glob.split(os.sep, 1)[0]
60
+ for excluded in src.rglob(glob):
61
+ if (rel_str := str(excluded.relative_to(src))).startswith(dirname):
62
+ self.__excludes.add(rel_str)
63
+
64
+ def resolve_sources(self, /):
65
+ """
66
+ Resolves entire git sources
67
+ """
68
+ assert not self.__sources
69
+ sources = self.__sources = self.__resolve_path(self.__config.project_root / "work/sources")
70
+
71
+ for glob in self.__config.dev_excludes:
72
+ dirname = glob.split(os.sep, 1)[0]
73
+ for excluded in sources.rglob(glob):
74
+ if (rel_str := str(excluded.relative_to(sources))).startswith(dirname):
75
+ self.__excludes_dev.add(rel_str)
76
+
77
+ def copy(self, dst: Path, /):
78
+ if self.__excludes_dev:
79
+ src = self.__sources
80
+ excludes = self.__excludes_dev
81
+ filter_ = lambda path: path.is_relative_to(self.__src) and (str(path.relative_to(self.__src)) in self.__excludes)
82
+ else:
83
+ src = self.__src
84
+ excludes = self.__excludes
85
+ filter_ = None
86
+
87
+ assert src and excludes
88
+ for src_file in src.rglob("*"):
89
+ if not src_file.is_file():
90
+ assert src_file.exists()
91
+ continue
92
+
93
+ rel = src_file.relative_to(src)
94
+ if (str(rel) in excludes) or (filter_ and filter_(src_file)):
95
+ continue
96
+
97
+ dst_file = dst / rel
98
+ if dst_file.exists():
99
+ raise RuntimeError(f"Failed to copy file {rel}: file exists")
100
+ dst_file.parent.mkdir(parents=True, exist_ok=True)
101
+ shutil.copy2(src_file, dst_file)
102
+
103
+ def apply(self, dst: Path, /) -> bool:
104
+ total = []
105
+ for patch in self.__patches.rglob("*.patch"):
106
+ rel_patch = patch.relative_to(self.__patches)
107
+ total.append(rel_patch)
108
+
109
+ rel_dst = rel_patch.with_suffix("") # foo.py.patch -> foo.py
110
+ dst_file = dst / rel_dst
111
+ dst_file.parent.mkdir(parents=True, exist_ok=True)
112
+
113
+ res = subprocess.run(
114
+ ["patch", "--unified", str(dst_file), str(patch)],
115
+ capture_output=True,
116
+ text=True,
117
+ )
118
+ if res.returncode != 0:
119
+ print(f"Failed to apply {rel_patch}:\n\t"
120
+ + "\n\t".join(res.stdout.splitlines()))
121
+ break
122
+ else:
123
+ print(f"Successfully applied {len(total)} patches")
124
+ return True
125
+ return False
@@ -0,0 +1 @@
1
+ from ._patch import PatcherBuildHook
@@ -0,0 +1,60 @@
1
+ from typing import override
2
+
3
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
4
+
5
+ import shutil
6
+
7
+ from makepatch._config import PatcherConfig
8
+ from makepatch.core import PatchApplier
9
+
10
+ if __debug__ and __import__("typing").TYPE_CHECKING:
11
+ from typing import Any, Final
12
+
13
+
14
+ class PatcherBuildHook(BuildHookInterface):
15
+ PLUGIN_NAME = "makepatch"
16
+
17
+ def __init__(self, /, *args, **kwargs):
18
+ super().__init__(*args, **kwargs)
19
+ self.__config: Final = PatcherConfig.from_hatch(self)
20
+ self.__patcher: Final = PatchApplier(self.__config)
21
+
22
+ @override
23
+ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
24
+ config = self.__config
25
+ patcher = self.__patcher
26
+
27
+ if patcher.setup_required():
28
+ patcher.resolve_sources()
29
+ patcher.resolve_src()
30
+
31
+ dst = config.project_root / "work/patched"
32
+ if dst.exists():
33
+ if not dst.is_dir(): raise RuntimeError(f"{dst} is not a directory")
34
+ shutil.rmtree(dst)
35
+ dst.mkdir(parents=True)
36
+
37
+ patcher.copy(dst)
38
+ patcher.apply(dst / config.module_source)
39
+ self.__success = False
40
+ return
41
+
42
+ patcher.resolve_src()
43
+
44
+ dst = self.__dst = config.project_root / "src" / config.module_name
45
+ if dst.exists():
46
+ if not dst.is_dir(): raise RuntimeError(f"{dst} is not a directory")
47
+ shutil.rmtree(dst)
48
+ dst.mkdir(parents=True)
49
+
50
+ patcher.copy(dst)
51
+ self.__success = patcher.apply(dst)
52
+
53
+ @override
54
+ def finalize(self, version: str, build_data: dict[str, Any], artifact_path: str) -> None:
55
+ if not self.__success:
56
+ return
57
+
58
+ if not self.__dst.exists() or not self.__dst.is_dir():
59
+ raise RuntimeError(f"{self.__dst} does not exists or is not a directory")
60
+ shutil.rmtree(self.__dst)
@@ -0,0 +1,28 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ from makepatch._config import PatcherConfig
5
+ from makepatch.core import PatchApplier
6
+
7
+ if __debug__ and __import__("typing").TYPE_CHECKING:
8
+ from typing import Final
9
+
10
+
11
+ from ._rebuild import rebuild_patches
12
+
13
+ def init_patches():
14
+ root: Final = Path(__import__("os").getcwd())
15
+ config: Final = PatcherConfig.from_pyproject(root)
16
+ patcher: Final = PatchApplier(config)
17
+
18
+ patcher.resolve_sources()
19
+ patcher.resolve_src()
20
+
21
+ dst = root / "work" / "patched"
22
+ if dst.exists():
23
+ if not dst.is_dir(): raise RuntimeError(f"{dst} is not a directory")
24
+ shutil.rmtree(dst)
25
+ dst.mkdir(parents=True)
26
+
27
+ patcher.copy(dst)
28
+ exit(not patcher.apply(dst / config.module_source))
@@ -0,0 +1,78 @@
1
+ import os
2
+ import re
3
+ import subprocess
4
+
5
+ from pathlib import Path
6
+
7
+ from makepatch._config import PatcherConfig
8
+
9
+ if __debug__ and __import__("typing").TYPE_CHECKING:
10
+ from typing import Final
11
+
12
+
13
+ def rebuild_patches():
14
+ TIMESTAMP = re.compile(r"\t\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{9} \+\d{4}")
15
+ ARGS = (
16
+ "--ignore-trailing-space",
17
+ "--strip-trailing-cr",
18
+ )
19
+
20
+ root: Final = Path(os.getcwd())
21
+ config: Final = PatcherConfig.from_pyproject(root)
22
+
23
+ ref = root / "work/sources" / config.module_source
24
+ if not ref.exists() or not ref.is_dir():
25
+ raise RuntimeError(f"{ref} does not exists or is not a directory")
26
+
27
+ src = root / "work/patched" / config.module_source
28
+ if not ref.exists() or not ref.is_dir():
29
+ raise RuntimeError(f"{ref} does not exists or is not a directory")
30
+
31
+ patches = root / "patches"
32
+ if patches.exists():
33
+ if not patches.is_dir(): raise RuntimeError(f"{patches} is not a directory")
34
+ __import__("shutil").rmtree(patches)
35
+ patches.mkdir(parents=True)
36
+
37
+ excludes = set()
38
+ for glob in config.excludes:
39
+ dirname = glob.split(os.sep, 1)[0]
40
+ for excluded in src.rglob(glob):
41
+ if (rel_str := str(excluded.relative_to(src))).startswith(dirname):
42
+ excludes.add(rel_str)
43
+
44
+ generated = []
45
+ for target_file in sorted(src.rglob("*")):
46
+ if not target_file.is_file(): continue
47
+ if target_file.suffix == "pyc": continue
48
+ if (target_file.parent.name.startswith("__") and
49
+ target_file.parent.name.endswith("__")):
50
+ continue
51
+
52
+ rel_file = target_file.relative_to(src)
53
+ if str(rel_file) in excludes: continue
54
+
55
+ edited = target_file.relative_to(root)
56
+ origin = (ref / rel_file).relative_to(root)
57
+
58
+ result = subprocess.run(
59
+ ("diff", "--unified", *ARGS, "--new-file", str(origin), str(edited)),
60
+ capture_output=True,
61
+ text=True,
62
+ )
63
+ if result.returncode == 0:
64
+ continue
65
+ elif result.returncode != 1:
66
+ raise RuntimeError(f"Failed to generate a patch file for {edited} (return code {result.returncode}):"
67
+ f"\n{result.stderr}")
68
+ else:
69
+ patch = patches / rel_file.with_suffix(rel_file.suffix + ".patch")
70
+ patch.parent.mkdir(parents=True, exist_ok=True)
71
+ with open(patch, "x") as f:
72
+ l = result.stdout.splitlines(keepends=True)
73
+ l[0] = TIMESTAMP.sub("", l[0])
74
+ l[1] = TIMESTAMP.sub("", l[1])
75
+ f.write(''.join(l))
76
+ generated.append(patch)
77
+ else:
78
+ print(f"Successfully generated {len(generated)} patches")
@@ -0,0 +1,192 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.12"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "hatchling"
16
+ version = "1.29.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "packaging" },
20
+ { name = "pathspec" },
21
+ { name = "pluggy" },
22
+ { name = "trove-classifiers" },
23
+ ]
24
+ sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" }
25
+ wheels = [
26
+ { url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" },
27
+ ]
28
+
29
+ [[package]]
30
+ name = "makepatch"
31
+ version = "0.1.7"
32
+ source = { editable = "." }
33
+ dependencies = [
34
+ { name = "hatchling" },
35
+ { name = "pydantic" },
36
+ ]
37
+
38
+ [package.metadata]
39
+ requires-dist = [
40
+ { name = "hatchling", specifier = ">=1.29.0" },
41
+ { name = "pydantic", specifier = ">=2.13.4" },
42
+ ]
43
+
44
+ [package.metadata.requires-dev]
45
+ dev = []
46
+
47
+ [[package]]
48
+ name = "packaging"
49
+ version = "26.2"
50
+ source = { registry = "https://pypi.org/simple" }
51
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
52
+ wheels = [
53
+ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
54
+ ]
55
+
56
+ [[package]]
57
+ name = "pathspec"
58
+ version = "1.1.1"
59
+ source = { registry = "https://pypi.org/simple" }
60
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
61
+ wheels = [
62
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
63
+ ]
64
+
65
+ [[package]]
66
+ name = "pluggy"
67
+ version = "1.6.0"
68
+ source = { registry = "https://pypi.org/simple" }
69
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
70
+ wheels = [
71
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
72
+ ]
73
+
74
+ [[package]]
75
+ name = "pydantic"
76
+ version = "2.13.4"
77
+ source = { registry = "https://pypi.org/simple" }
78
+ dependencies = [
79
+ { name = "annotated-types" },
80
+ { name = "pydantic-core" },
81
+ { name = "typing-extensions" },
82
+ { name = "typing-inspection" },
83
+ ]
84
+ sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
85
+ wheels = [
86
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
87
+ ]
88
+
89
+ [[package]]
90
+ name = "pydantic-core"
91
+ version = "2.46.4"
92
+ source = { registry = "https://pypi.org/simple" }
93
+ dependencies = [
94
+ { name = "typing-extensions" },
95
+ ]
96
+ sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
97
+ wheels = [
98
+ { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
99
+ { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
100
+ { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
101
+ { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
102
+ { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
103
+ { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
104
+ { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
105
+ { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
106
+ { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
107
+ { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
108
+ { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
109
+ { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
110
+ { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
111
+ { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
112
+ { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
113
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
114
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
115
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
116
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
117
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
118
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
119
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
120
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
121
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
122
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
123
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
124
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
125
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
126
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
127
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
128
+ { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
129
+ { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
130
+ { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
131
+ { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
132
+ { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
133
+ { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
134
+ { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
135
+ { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
136
+ { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
137
+ { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
138
+ { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
139
+ { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
140
+ { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
141
+ { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
142
+ { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
143
+ { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
144
+ { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
145
+ { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
146
+ { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
147
+ { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
148
+ { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
149
+ { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
150
+ { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
151
+ { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
152
+ { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
153
+ { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
154
+ { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
155
+ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
156
+ { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
157
+ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
158
+ { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
159
+ { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
160
+ { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
161
+ { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
162
+ ]
163
+
164
+ [[package]]
165
+ name = "trove-classifiers"
166
+ version = "2026.5.7.17"
167
+ source = { registry = "https://pypi.org/simple" }
168
+ sdist = { url = "https://files.pythonhosted.org/packages/35/68/175e7c07c5be13200387d5c0995b0da1e198e360047c08eb17d1002fcd92/trove_classifiers-2026.5.7.17.tar.gz", hash = "sha256:a04a48f8f0a787cb996514d3969ac7608aa3c60cb15d073c1e02801e60533e80", size = 17041, upload-time = "2026-05-07T17:48:01.931Z" }
169
+ wheels = [
170
+ { url = "https://files.pythonhosted.org/packages/7b/e3/d81b065a2d866a33a541ac63a2a4cc5737e03ce2379ac3191c98bb8867e3/trove_classifiers-2026.5.7.17-py3-none-any.whl", hash = "sha256:5ec0800de5e2ddbd7c663cb4c0c15328f132dc168813897c18866c5c7b93db33", size = 14201, upload-time = "2026-05-07T17:48:00.488Z" },
171
+ ]
172
+
173
+ [[package]]
174
+ name = "typing-extensions"
175
+ version = "4.15.0"
176
+ source = { registry = "https://pypi.org/simple" }
177
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
178
+ wheels = [
179
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
180
+ ]
181
+
182
+ [[package]]
183
+ name = "typing-inspection"
184
+ version = "0.4.2"
185
+ source = { registry = "https://pypi.org/simple" }
186
+ dependencies = [
187
+ { name = "typing-extensions" },
188
+ ]
189
+ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
190
+ wheels = [
191
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
192
+ ]
makepatch-0.0.1/PKG-INFO DELETED
@@ -1,8 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: makepatch
3
- Version: 0.0.1
4
- Summary: Hatch build hook to easily hard-fork packages with patches!
5
- Classifier: Framework :: Hatch
6
- Requires-Dist: hatchling>=1.29.0
7
- Requires-Dist: pydantic>=2.13.4
8
- Requires-Python: >=3.12
@@ -1,26 +0,0 @@
1
- [project]
2
- name = "makepatch"
3
- version = "0.0.1"
4
- description = "Hatch build hook to easily hard-fork packages with patches!"
5
- requires-python = ">=3.12"
6
- classifiers = [
7
- "Framework :: Hatch",
8
- ]
9
- dependencies = [
10
- "hatchling>=1.29.0",
11
- "pydantic>=2.13.4",
12
- ]
13
-
14
- [dependency-groups]
15
- dev = []
16
-
17
- [build-system]
18
- build-backend = "uv_build"
19
- requires = ["uv_build>=0.11.14,<0.12"]
20
-
21
- [project.entry-points.hatch]
22
- makepatch = "makepatch.__hook__"
23
-
24
- [project.scripts]
25
- init = "makepatch.scripts:init"
26
- rebuild-patches = "makepatch.scripts:rebuild_patches"
@@ -1,7 +0,0 @@
1
- from hatchling.plugin import hookimpl as _hook
2
- from src.clippy.hooks import ClippyPatchHook
3
-
4
-
5
- @_hook
6
- def __register_hatch_build_hooks():
7
- return ClippyPatchHook
File without changes
@@ -1,18 +0,0 @@
1
- from collections.abc import Iterable
2
- from typing import final, Annotated
3
-
4
- from pydantic import BaseModel, Field
5
-
6
-
7
- __config = None
8
-
9
- @final
10
- class ClippyConfig(BaseModel):
11
- module_source: Annotated[str, Field(alias="module-source")]
12
- excludes: Annotated[Iterable[str], Field(default_factory=frozenset)]
13
- dev_excludes: Annotated[Iterable[str], Field(alias="module-source", default_factory=frozenset)]
14
-
15
- def __init__(self, /):
16
- global __config
17
- if __config:
18
- raise RuntimeError("Configuration already initialized")
@@ -1 +0,0 @@
1
- from ._patch import ClippyPatchHook
@@ -1,88 +0,0 @@
1
- import os
2
- from pathlib import Path
3
- from typing import override
4
-
5
- from hatchling.builders.hooks.plugin.interface import BuildHookInterface
6
-
7
- import shutil, subprocess, tomllib
8
-
9
- if __debug__ and __import__("typing").TYPE_CHECKING:
10
- from typing import Any
11
-
12
-
13
- class ClippyPatchHook(BuildHookInterface):
14
- @property
15
- def __root(self):
16
- return Path(self.root)
17
-
18
- @property
19
- def __config(self):
20
- with open(self.__root / "pyproject.toml", "rb") as f:
21
- pyproject = tomllib.load(f)
22
-
23
- try:
24
- config = pyproject["tool"]["patcher"]
25
- except KeyError as e:
26
- raise RuntimeError("[tool.patcher] not found in pyproject.toml") from e
27
- return config["module-source"], frozenset(config.get("excludes", set()))
28
-
29
- @override
30
- def initialize(self, version: str, build_data: dict[str, Any]) -> None:
31
- module_name, exclude_globs = self.__config
32
-
33
- src = self.__root / "work" / "sources" / module_name
34
- if not src.exists(): raise RuntimeError(f"{src} not exists")
35
- elif not src.is_dir(): raise RuntimeError(f"{src} is not a directory")
36
-
37
- excludes = set()
38
- for glob in exclude_globs:
39
- excludes |= set(str(excluded.relative_to(src)) for excluded in src.rglob(glob) if str(excluded.relative_to(src)).startswith(glob.split(os.sep)[0]))
40
-
41
- patches = self.__root / "patches"
42
- if not patches.exists(): raise RuntimeError(f"{patches} not exists")
43
- elif not patches.is_dir(): raise RuntimeError(f"{patches} is not a directory")
44
-
45
- if str(module_name).startswith("src"):
46
- module_name = module_name[4:]
47
- dst = self.__root / "src" / module_name
48
- if dst.exists():
49
- if not dst.is_dir(): raise RuntimeError(f"{dst} is not a directory")
50
- shutil.rmtree(dst)
51
- dst.mkdir(parents=True)
52
-
53
- # Copy sources, skipping excludes
54
- for src_file in src.rglob("*"):
55
- if not src_file.is_file(): continue
56
-
57
- rel = src_file.relative_to(src)
58
- if str(rel) in excludes: continue
59
-
60
- dst_file = dst / rel
61
- dst_file.parent.mkdir(parents=True, exist_ok=True)
62
- shutil.copy2(src_file, dst_file)
63
-
64
- # Apply patches
65
- for patch_file in sorted(patches.rglob("*.patch")):
66
- rel_patch = patch_file.relative_to(patches)
67
- # e.g. foo.py.patch → foo.py
68
- target_rel = rel_patch.with_suffix("")
69
- target = dst / target_rel
70
-
71
- if not target.parent.exists():
72
- target.parent.mkdir(parents=True, exist_ok=False)
73
-
74
- result = subprocess.run(
75
- ["patch", "--unified", str(target), str(patch_file)],
76
- capture_output=True,
77
- text=True,
78
- )
79
- if result.returncode != 0:
80
- raise RuntimeError(f"Failed to apply {rel_patch}:\n{result.stderr}")
81
-
82
- def finalize(self, version: str, build_data: dict[str, Any], artifact_path: str) -> None:
83
- module_source, _ = self.__config
84
- if str(module_source).startswith("src"):
85
- module_source = module_source[4:]
86
- dst = self.__root / "src" / module_source
87
- if dst.exists():
88
- shutil.rmtree(dst)
File without changes