dothat 1.0.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.
Files changed (33) hide show
  1. dothat-1.0.0/.gitignore +3 -0
  2. dothat-1.0.0/PKG-INFO +13 -0
  3. dothat-1.0.0/README.md +0 -0
  4. dothat-1.0.0/dothat/VERSION +2 -0
  5. dothat-1.0.0/dothat/__init__.py +9 -0
  6. dothat-1.0.0/dothat/cli.py +97 -0
  7. dothat-1.0.0/dothat/default_tasks/__init__.py +3 -0
  8. dothat-1.0.0/dothat/default_tasks/_actions.py +8 -0
  9. dothat-1.0.0/dothat/default_tasks/_params.py +11 -0
  10. dothat-1.0.0/dothat/default_tasks/_tasks.py +19 -0
  11. dothat-1.0.0/dothat/di.py +27 -0
  12. dothat-1.0.0/dothat/initialization/__init__.py +45 -0
  13. dothat-1.0.0/dothat/initialization/templates/_tasks/__init__.py +3 -0
  14. dothat-1.0.0/dothat/initialization/templates/_tasks/_actions.py +38 -0
  15. dothat-1.0.0/dothat/initialization/templates/_tasks/_consts.py +5 -0
  16. dothat-1.0.0/dothat/initialization/templates/_tasks/_params.py +20 -0
  17. dothat-1.0.0/dothat/initialization/templates/_tasks/_tasks.py +39 -0
  18. dothat-1.0.0/dothat/logging.py +30 -0
  19. dothat-1.0.0/dothat/models/__init__.py +9 -0
  20. dothat-1.0.0/dothat/models/actions.py +54 -0
  21. dothat-1.0.0/dothat/models/params.py +42 -0
  22. dothat-1.0.0/dothat/project_config/__init__.py +46 -0
  23. dothat-1.0.0/dothat/py.typed +0 -0
  24. dothat-1.0.0/dothat/registry.py +80 -0
  25. dothat-1.0.0/dothat/task_builder.py +138 -0
  26. dothat-1.0.0/dothat/task_generator.py +32 -0
  27. dothat-1.0.0/dothat/tasks_core/default_tasks/__init__.py +3 -0
  28. dothat-1.0.0/dothat/tasks_core/default_tasks/_actions.py +8 -0
  29. dothat-1.0.0/dothat/tasks_core/default_tasks/_params.py +11 -0
  30. dothat-1.0.0/dothat/tasks_core/default_tasks/_tasks.py +19 -0
  31. dothat-1.0.0/environment.toml +3 -0
  32. dothat-1.0.0/pyproject.toml +55 -0
  33. dothat-1.0.0/uv.lock +253 -0
@@ -0,0 +1,3 @@
1
+ *.venv*
2
+ __pycache__
3
+ *.pyc
dothat-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.5
2
+ Name: dothat
3
+ Version: 1.0.0
4
+ Summary: framework for python tasks and actions management
5
+ Author: PivLab Dev
6
+ Author-email: Arsenii Nikulin <a.nikulin@pivlab.dev>
7
+ License-Expression: MIT
8
+ Requires-Python: <4.0,>=3.11
9
+ Requires-Dist: click<9.0.0,>=8.4.1
10
+ Requires-Dist: gitpython<4.0.0,>=3.1.57
11
+ Requires-Dist: injector>=0.24.0
12
+ Requires-Dist: loguru<1.0.0,>=0.7.3
13
+ Requires-Dist: pydantic>=2.13.5
dothat-1.0.0/README.md ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ 1.0.0
2
+
@@ -0,0 +1,9 @@
1
+ from dothat.models.actions import InteractivePythonAction, InteractiveShlex
2
+ from dothat.models.params import Param, Params
3
+
4
+ __all__ = (
5
+ "InteractivePythonAction",
6
+ "InteractiveShlex",
7
+ "Params",
8
+ "Param",
9
+ )
@@ -0,0 +1,97 @@
1
+ import builtins
2
+ import dataclasses
3
+ import pathlib
4
+
5
+ import click
6
+ import injector
7
+
8
+ from dothat.di import Module
9
+ from dothat.logging import logger as LOGGER
10
+ from dothat.registry import TaskRegistry
11
+ from dothat.task_builder import Task
12
+
13
+
14
+ @dataclasses.dataclass
15
+ class DothatContext:
16
+ di: injector.Injector
17
+
18
+
19
+ def get_registry(di: injector.Injector, module: str | None) -> dict[str, Task]:
20
+ if module:
21
+ return di.get(TaskRegistry).load_tasks_from_module(module)
22
+ return di.get(TaskRegistry).tasks
23
+
24
+
25
+ @click.group()
26
+ @click.option(
27
+ "--config",
28
+ type=pathlib.Path,
29
+ envvar="ENVIRONMENT_TOML_PATH",
30
+ default=pathlib.Path(
31
+ "environment.toml",
32
+ ),
33
+ )
34
+ @click.pass_context
35
+ def cli(ctx: click.Context, config: pathlib.Path):
36
+ ctx.obj = DothatContext(di=injector.Injector([Module(config_path=config)]))
37
+
38
+
39
+ @cli.command()
40
+ @click.option("--module", default=None)
41
+ @click.pass_obj
42
+ def list(dothat_context: DothatContext, module: str | None):
43
+ _registry = get_registry(dothat_context.di, module)
44
+
45
+ for key, task in _registry.items():
46
+ click.echo(f"R {key:<50} {task.doc}")
47
+
48
+
49
+ @cli.command()
50
+ @click.argument("task_name")
51
+ @click.option("--module", default=None)
52
+ @click.pass_obj
53
+ def help(dothat_context: DothatContext, task_name: str, module: str):
54
+ _registry = get_registry(di=dothat_context.di, module=module)
55
+
56
+ task = _registry[task_name]
57
+
58
+ click.echo(f"{task_name} {task.doc}")
59
+
60
+ for param in task.params:
61
+ description: builtins.list[str] = [f"config: {param.name}"]
62
+
63
+ if param.env_var:
64
+ description.append(f"environ: {param.env_var}")
65
+ if param.type is bool and param.inverse:
66
+ description.append(f"opposite of {param.inverse}")
67
+
68
+ click.echo(f" --{param.long:<30} ({', '.join(description)})")
69
+
70
+
71
+ @cli.command(
72
+ context_settings={
73
+ "ignore_unknown_options": True,
74
+ "allow_extra_args": True,
75
+ }
76
+ )
77
+ @click.argument("task_name")
78
+ @click.pass_context
79
+ @click.option("--module", default=None)
80
+ def run(ctx: click.Context, task_name: str, module: str | None):
81
+ _registry = get_registry(di=ctx.obj.di, module=module)
82
+
83
+ task = _registry.get(task_name)
84
+
85
+ if task is None:
86
+ raise click.ClickException(f"Unknown task: {task_name}")
87
+ task = task.model_copy(deep=True)
88
+
89
+ task.parse_task_params(args=ctx.args, di=ctx.obj.di)
90
+
91
+ LOGGER.debug(f"running task: {task.basename}")
92
+
93
+ task.exec_task(inj=ctx.obj.di)
94
+
95
+
96
+ if __name__ == "__main__":
97
+ cli()
@@ -0,0 +1,3 @@
1
+ from dothat.default_tasks._tasks import CiTaskGenerator
2
+
3
+ __all__ = ("CiTaskGenerator",)
@@ -0,0 +1,8 @@
1
+ from dothat.initialization import TemplateGenerator
2
+ from dothat.models.actions import InteractivePythonAction
3
+
4
+
5
+ class InitializeDefaultProject(InteractivePythonAction):
6
+ def impl(self, *, force: bool, **_) -> None:
7
+ templategenerator = TemplateGenerator()
8
+ templategenerator.generate_project_structure(force=force)
@@ -0,0 +1,11 @@
1
+ from dothat.models.params import Param
2
+
3
+
4
+ def force() -> Param:
5
+ return Param(
6
+ name="force",
7
+ long="force",
8
+ short="f",
9
+ default=False,
10
+ type=bool,
11
+ )
@@ -0,0 +1,19 @@
1
+ from collections.abc import Iterator
2
+
3
+ from dothat.default_tasks import _actions, _params
4
+ from dothat.task_builder import Task, TaskBuilderImpl
5
+ from dothat.task_generator import TaskGeneratorImpl
6
+
7
+
8
+ class InitializeDefaultProject(TaskBuilderImpl):
9
+ _basename = "init"
10
+
11
+ def apply(self):
12
+ return self.with_actions((_actions.InitializeDefaultProject,)).with_params((_params.force(),))
13
+
14
+
15
+ class CiTaskGenerator(TaskGeneratorImpl):
16
+ task_init = InitializeDefaultProject
17
+
18
+ def load_tasks(self) -> Iterator[Task]:
19
+ yield from super().load_tasks()
@@ -0,0 +1,27 @@
1
+ import pathlib
2
+
3
+ import injector
4
+
5
+ from dothat.models.params import Params
6
+ from dothat.project_config import ProjectConfig
7
+ from dothat.registry import TaskRegistry
8
+
9
+
10
+ class ConfigPath:
11
+ def __init__(self, path: pathlib.Path) -> None:
12
+ self.path = path
13
+
14
+
15
+ class Module(injector.Module):
16
+ def __init__(self, config_path: pathlib.Path = pathlib.Path("environment.toml")) -> None:
17
+ self.config_path = config_path
18
+
19
+ def configure(self, binder: injector.Binder) -> None:
20
+ binder.bind(ConfigPath, to=ConfigPath(self.config_path))
21
+ binder.bind(TaskRegistry, scope=injector.singleton)
22
+ binder.bind(Params, scope=injector.singleton)
23
+
24
+ @injector.singleton
25
+ @injector.provider
26
+ def provide_config(self, cfg_path: ConfigPath) -> ProjectConfig:
27
+ return ProjectConfig.from_file(cfg_path.path)
@@ -0,0 +1,45 @@
1
+ import pathlib
2
+ import shutil
3
+ from importlib.resources import as_file, files
4
+
5
+ from dothat.logging import logger as LOGGER
6
+
7
+
8
+ class TemplateGenerator:
9
+ def generate_project_structure(
10
+ self,
11
+ destination: pathlib.Path = pathlib.Path("."),
12
+ force: bool = False,
13
+ ) -> None:
14
+ template = files("dothat.initialization") / "templates"
15
+
16
+ LOGGER.info("Copying project template from {}", template)
17
+
18
+ with as_file(template) as template_path:
19
+ self._copy_template(
20
+ template_path,
21
+ destination,
22
+ force=force,
23
+ )
24
+
25
+ @staticmethod
26
+ def _copy_template(
27
+ source: pathlib.Path,
28
+ destination: pathlib.Path,
29
+ *,
30
+ force: bool,
31
+ ) -> None:
32
+ for source_path in source.rglob("*"):
33
+ relative_path = source_path.relative_to(source)
34
+ destination_path = destination / relative_path
35
+
36
+ if source_path.is_dir():
37
+ destination_path.mkdir(parents=True, exist_ok=True)
38
+ continue
39
+
40
+ if destination_path.exists() and not force:
41
+ LOGGER.debug("Skipping existing file: {}", destination_path)
42
+ continue
43
+
44
+ destination_path.parent.mkdir(parents=True, exist_ok=True)
45
+ shutil.copy2(source_path, destination_path)
@@ -0,0 +1,3 @@
1
+ from _tasks._tasks import TaskGenerator
2
+
3
+ __all__ = ("TaskGenerator",)
@@ -0,0 +1,38 @@
1
+ import pathlib
2
+ import typing
3
+
4
+ from dothat import InteractivePythonAction, InteractiveShlex
5
+
6
+
7
+ class SetupSsh(InteractivePythonAction):
8
+ def impl(self, **_):
9
+ ssh_private_key = "123"
10
+
11
+ ssh_dir = pathlib.Path("/root/.ssh")
12
+ ssh_dir.mkdir(exist_ok=True)
13
+ ssh_dir.chmod(0o600)
14
+
15
+ private_key_file = ssh_dir / "id_ci"
16
+ private_key_file.write_text(ssh_private_key)
17
+ private_key_file.chmod(0o600)
18
+
19
+ ssh_config_file = ssh_dir / "config"
20
+ ssh_config_data = [
21
+ "Host gl.pivlab.dev",
22
+ " IdentityFile /root/.ssh/id_ci",
23
+ " IdentitiesOnly yes",
24
+ " StrictHostKeyChecking no",
25
+ " UserKnownHostsFile /dev/null",
26
+ ]
27
+ ssh_config_file.write_text("\n".join(ssh_config_data))
28
+ ssh_config_file.chmod(0o644)
29
+
30
+
31
+ class SetupGitInsteadOf(InteractiveShlex):
32
+ def impl(self, **_: typing.Any) -> list[list[str]]:
33
+
34
+ return [
35
+ ["git", "config", "--global", "user.email", "ci@pivlab.space"],
36
+ ["git", "config", "--global", "user.name", "CI"],
37
+ ["git", "config", "--global", "init.defaultBranch", "master"],
38
+ ]
@@ -0,0 +1,5 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Tasks(Enum):
5
+ test_task = "test-task"
@@ -0,0 +1,20 @@
1
+ from dothat import Param
2
+
3
+
4
+ def flavor() -> Param:
5
+ return Param(
6
+ name="flavor",
7
+ long="flavor",
8
+ default="release",
9
+ type=str,
10
+ )
11
+
12
+
13
+ def insecure() -> Param:
14
+ return Param(
15
+ name="insecure",
16
+ long="insecure",
17
+ default=False,
18
+ type=bool,
19
+ inverse="secure",
20
+ )
@@ -0,0 +1,39 @@
1
+ from collections.abc import Iterator
2
+
3
+ from _tasks import _actions, _params
4
+ from _tasks._consts import Tasks
5
+ from dothat.task_builder import Task, TaskBuilderImpl
6
+ from dothat.task_generator import TaskGeneratorImpl
7
+
8
+
9
+ class TestBaseTask(TaskBuilderImpl):
10
+ _basename = Tasks.test_task
11
+
12
+ def apply(self):
13
+ self.model.doc = "test task"
14
+ self.with_actions((_actions.SetupSsh,))
15
+ return self
16
+
17
+
18
+ class TaskGenerator(TaskGeneratorImpl):
19
+ task_generate_docs = TestBaseTask
20
+
21
+ def load_tasks(self) -> Iterator[Task]:
22
+ yield from super().load_tasks()
23
+
24
+ targets = self._config.extra.get("targets", [])
25
+
26
+ for target in targets:
27
+ yield (
28
+ self.builder(TestBaseTask)
29
+ .apply()
30
+ .with_name(target)
31
+ .with_params(
32
+ (
33
+ self._params.ci(),
34
+ _params.flavor(),
35
+ _params.insecure(),
36
+ )
37
+ )
38
+ .build()
39
+ )
@@ -0,0 +1,30 @@
1
+ import sys
2
+
3
+ from loguru import logger
4
+
5
+
6
+ def setup_logger(debug: bool = False) -> None:
7
+ logger.remove()
8
+
9
+ level = "DEBUG" if debug else "INFO"
10
+
11
+ logger.add(
12
+ sys.stdout,
13
+ level=level,
14
+ colorize=True,
15
+ format=(
16
+ "<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
17
+ "<level>{level: <8}</level> | "
18
+ "<cyan>{name}</cyan>:"
19
+ "<cyan>{function}</cyan>:"
20
+ "<cyan>{line}</cyan> - "
21
+ "<level>{message}</level>"
22
+ ),
23
+ backtrace=True,
24
+ diagnose=debug,
25
+ )
26
+
27
+
28
+ setup_logger(debug=True)
29
+
30
+ __all__ = ["logger"]
@@ -0,0 +1,9 @@
1
+ from dothat.models.actions import InteractivePythonAction, InteractiveShlex
2
+ from dothat.models.params import Param, Params
3
+
4
+ __all__ = (
5
+ "InteractivePythonAction",
6
+ "InteractiveShlex",
7
+ "Params",
8
+ "Param",
9
+ )
@@ -0,0 +1,54 @@
1
+ import dataclasses
2
+ import shlex
3
+ import subprocess
4
+ import typing
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Iterable
7
+
8
+ import injector
9
+
10
+ from dothat.logging import logger as LOGGER
11
+ from dothat.project_config import ProjectConfig
12
+
13
+ ActionResult = typing.Any
14
+
15
+
16
+ @injector.inject
17
+ @dataclasses.dataclass
18
+ class ActionBuilderImpl(ABC):
19
+ _di: injector.Injector
20
+ _config: ProjectConfig
21
+
22
+ @abstractmethod
23
+ def impl(self, **kwargs: typing.Any) -> ActionResult: ...
24
+
25
+ @abstractmethod
26
+ def execute(self, **kwargs: typing.Any): ...
27
+
28
+
29
+ class InteractivePythonAction(ActionBuilderImpl):
30
+ @typing.override
31
+ def impl(self, **kwargs: typing.Any) -> None:
32
+ raise NotImplementedError
33
+
34
+ @typing.override
35
+ def execute(self, **kwargs: typing.Any) -> None:
36
+ LOGGER.debug(f"running InteractivePythonAction action: {self.__class__.__name__}")
37
+ self.impl(**kwargs)
38
+
39
+
40
+ class InteractiveShlex(ActionBuilderImpl):
41
+ @typing.override
42
+ def impl(self, **kwargs: typing.Any) -> Iterable[list[str]]:
43
+ raise NotImplementedError
44
+
45
+ @typing.override
46
+ def execute(self, **kwargs: typing.Any) -> None:
47
+ LOGGER.debug(f"running InteractiveShlex action: {self.__class__.__name__}")
48
+
49
+ commands = [shlex.join(cmd) for cmd in self.impl(**kwargs)]
50
+
51
+ script = " && ".join(commands)
52
+
53
+ LOGGER.info("running: {}", script)
54
+ subprocess.run(script, shell=True, check=True)
@@ -0,0 +1,42 @@
1
+ import dataclasses
2
+ import typing
3
+
4
+ import injector
5
+ import pydantic
6
+
7
+ from dothat.project_config import ProjectConfig
8
+
9
+
10
+ class Param(pydantic.BaseModel):
11
+ name: str
12
+ default: typing.Any = None
13
+ long: str
14
+
15
+ short: typing.Optional[str] = ""
16
+ type: typing.Callable[[typing.Any], typing.Any] = lambda x: x
17
+ help: typing.Optional[str] = None
18
+ env_var: typing.Optional[str] = None
19
+ inverse: typing.Optional[str] = None
20
+
21
+
22
+ @injector.inject
23
+ @dataclasses.dataclass
24
+ class Params:
25
+ config: ProjectConfig
26
+
27
+ def ci(self):
28
+ return Param(
29
+ name="ci",
30
+ long="ci",
31
+ type=bool,
32
+ default=False,
33
+ env_var="CI",
34
+ )
35
+
36
+ def from_config(self, key: str, **kwargs):
37
+ return Param(
38
+ name=f"extra__{key}",
39
+ long=key.replace("_", "-"),
40
+ default=(self.config.extra.model_extra or {}).get(key),
41
+ **kwargs,
42
+ )
@@ -0,0 +1,46 @@
1
+ import pathlib
2
+ import tomllib
3
+ import typing
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from dothat.logging import logger as LOGGER
8
+
9
+
10
+ class System(BaseModel):
11
+ tasks_module: typing.Optional[str] = None
12
+
13
+
14
+ class Extra(BaseModel):
15
+ model_config = ConfigDict(extra="allow")
16
+
17
+ system: System = Field(default_factory=System)
18
+
19
+ def __getitem__(self, key: str) -> typing.Any:
20
+
21
+ if self.model_extra is None:
22
+ raise KeyError(key)
23
+
24
+ return self.model_extra[key]
25
+
26
+ def get(self, key: str, default: typing.Any = None) -> typing.Any:
27
+ return (self.model_extra or {}).get(key, default)
28
+
29
+
30
+ class ProjectConfig(BaseModel):
31
+ @classmethod
32
+ def from_file(
33
+ cls,
34
+ path: pathlib.Path = pathlib.Path("environment.toml"),
35
+ ) -> "ProjectConfig":
36
+ if not path.exists():
37
+ LOGGER.debug("ProjectConfig is not provided: file is not exists")
38
+ data = {}
39
+ else:
40
+ data = tomllib.loads(path.read_text())
41
+
42
+ _cfg = cls.model_validate(data)
43
+
44
+ return _cfg
45
+
46
+ extra: Extra = Field(default_factory=Extra)
File without changes
@@ -0,0 +1,80 @@
1
+ import importlib.util
2
+ import pathlib
3
+ import sys
4
+
5
+ import injector
6
+
7
+ from dothat.default_tasks import CiTaskGenerator
8
+ from dothat.logging import logger as LOGGER
9
+ from dothat.project_config import ProjectConfig
10
+ from dothat.task_builder import Task
11
+ from dothat.task_generator import TaskGenerator
12
+
13
+
14
+ class TaskRegistry:
15
+ @injector.inject
16
+ def __init__(self, config: ProjectConfig, di: injector.Injector) -> None:
17
+ self._di = di
18
+ self._config = config
19
+
20
+ self._tasks: dict[str, Task] | None = None
21
+
22
+ def _load_module(self, module: str):
23
+
24
+ module_path = pathlib.Path(module)
25
+
26
+ LOGGER.debug(f"Tasks loaded from module: {module_path}")
27
+
28
+ if module_path.is_file():
29
+ module_name = module_path.stem
30
+
31
+ spec = importlib.util.spec_from_file_location(module_name, module_path)
32
+ if spec is None or spec.loader is None:
33
+ raise ImportError(f"Could not load module from {module_path}")
34
+
35
+ module_obj = importlib.util.module_from_spec(spec)
36
+ sys.modules[module_name] = module_obj
37
+ spec.loader.exec_module(module_obj)
38
+ return module_obj
39
+
40
+ sys.path.insert(0, str(module_path.parent))
41
+
42
+ try:
43
+ module_obj = __import__(module, fromlist=["TaskGenerator"])
44
+ finally:
45
+ sys.path.remove(str(module_path.parent))
46
+
47
+ return module_obj
48
+
49
+ def _load_tasks_from_generator(self, generator: TaskGenerator) -> dict[str, Task]:
50
+ registry: dict[str, Task] = {}
51
+
52
+ for task in generator.load_tasks():
53
+ if not task.basename:
54
+ raise ValueError("Task basename is not set.")
55
+ taskname = task.basename if not task.name else ":".join([task.basename, task.name])
56
+ registry[taskname] = task
57
+
58
+ return registry
59
+
60
+ def load_tasks_from_module(self, module: str | None) -> dict[str, Task]:
61
+ if not module:
62
+ LOGGER.warning("module with tasks not found")
63
+ return {}
64
+
65
+ module_obj = self._load_module(module)
66
+
67
+ generator: TaskGenerator = self._di.create_object(module_obj.TaskGenerator)
68
+
69
+ registry: dict[str, Task] = self._load_tasks_from_generator(generator)
70
+
71
+ return registry
72
+
73
+ @property
74
+ def tasks(self) -> dict[str, Task]:
75
+ if self._tasks is None:
76
+ self._tasks = {
77
+ **self.load_tasks_from_module(self._config.extra.system.tasks_module),
78
+ **self._load_tasks_from_generator(self._di.create_object(CiTaskGenerator)),
79
+ }
80
+ return self._tasks
@@ -0,0 +1,138 @@
1
+ import dataclasses
2
+ import os
3
+ import typing
4
+ from collections.abc import Iterable
5
+ from enum import Enum
6
+
7
+ import injector
8
+ import pydantic
9
+
10
+ from dothat.models.actions import ActionBuilderImpl
11
+ from dothat.models.params import Param, Params
12
+ from dothat.project_config import ProjectConfig
13
+
14
+
15
+ class Task(pydantic.BaseModel):
16
+ basename: typing.Optional[str] = None
17
+
18
+ name: typing.Optional[str] = None
19
+
20
+ doc: str = ""
21
+
22
+ actions: typing.Optional[list[type["ActionBuilderImpl"]]] = None
23
+
24
+ parsed_params: dict[str, typing.Any] = {}
25
+
26
+ params: list[Param] = pydantic.Field(default_factory=lambda: [])
27
+
28
+ def parse_task_params(self, args: list[str], di: injector.Injector):
29
+ _config = di.get(ProjectConfig)
30
+
31
+ if _config.extra.model_extra:
32
+ for key, val in _config.extra.model_extra.items():
33
+ self.parsed_params[f"extra__{key}"] = val
34
+
35
+ args_iter = iter(args)
36
+
37
+ for arg in args_iter:
38
+ if arg.startswith("--"):
39
+ key = arg.removeprefix("--")
40
+ param = next(
41
+ (p for p in self.params if key in [p.long, p.inverse]),
42
+ None,
43
+ )
44
+ elif arg.startswith("-"):
45
+ key = arg.removeprefix("-")
46
+ param = next(
47
+ (p for p in self.params if key in [p.short, p.inverse]),
48
+ None,
49
+ )
50
+ else:
51
+ continue
52
+
53
+ if not param:
54
+ raise ValueError(f"Unknown parameter: --{key}")
55
+
56
+ if param.type is bool:
57
+ self.parsed_params[param.name] = True if key == param.long or key == param.short else False
58
+ continue
59
+
60
+ try:
61
+ value = next(args_iter)
62
+ except StopIteration as err:
63
+ raise StopIteration from err
64
+
65
+ self.parsed_params[param.name] = param.type(value)
66
+
67
+ def exec_task(self, inj: injector.Injector):
68
+ if not self.actions:
69
+ raise RuntimeError("not found actions to execute.")
70
+ for action_cls in self.actions:
71
+ action = inj.create_object(action_cls)
72
+ action.execute(**self.parsed_params)
73
+
74
+
75
+ class TaskBulder:
76
+ def build(self) -> Task:
77
+ raise NotImplementedError
78
+
79
+
80
+ @injector.inject
81
+ @dataclasses.dataclass
82
+ class TaskBuilderImpl(TaskBulder):
83
+ _basename: typing.ClassVar[str | Enum | None] = None
84
+
85
+ _params: Params
86
+ _config: ProjectConfig
87
+
88
+ model: Task = dataclasses.field(init=False, default_factory=Task)
89
+
90
+ def __post_init__(self):
91
+ "extension point for childs"
92
+
93
+ def apply(self):
94
+ return self
95
+
96
+ def build(self) -> Task:
97
+ if not self._basename:
98
+ raise ValueError("task name not set.")
99
+
100
+ if not self.model.doc:
101
+ self.model.doc = ".".join(
102
+ [
103
+ self.__class__.__module__,
104
+ self.__class__.__name__,
105
+ ]
106
+ )
107
+
108
+ if isinstance(self._basename, str):
109
+ self.model.basename = self._basename
110
+ else:
111
+ self.model.basename = self._basename.value
112
+
113
+ return self.model.model_copy(deep=True)
114
+
115
+ def with_actions(self, actions: Iterable[type[ActionBuilderImpl]]):
116
+ self.model.actions = list(actions)
117
+ return self
118
+
119
+ def with_params(self: typing.Self, params: Iterable[Param]) -> typing.Self:
120
+ self.model.params = list(params)
121
+
122
+ params_dict = {}
123
+ for param in params:
124
+ params_dict[param.name] = os.getenv(param.env_var, param.default) if param.env_var else param.default
125
+ self.model.parsed_params = params_dict
126
+
127
+ return self
128
+
129
+ def replace_param(self, param_name: str, param: str, default: str):
130
+ self.model.parsed_params[param_name] = param or default
131
+
132
+ def with_name(self, name: str):
133
+ self.model.name = name
134
+ return self
135
+
136
+ def with_doc(self, doc: str):
137
+ self.model.doc = doc
138
+ return self
@@ -0,0 +1,32 @@
1
+ import dataclasses
2
+ from collections.abc import Iterator
3
+
4
+ import injector
5
+
6
+ from dothat.models.params import Params
7
+ from dothat.project_config import ProjectConfig
8
+ from dothat.task_builder import Task, TaskBuilderImpl
9
+
10
+
11
+ class TaskGenerator:
12
+ def load_tasks(self) -> Iterator[Task]:
13
+ raise NotImplementedError
14
+
15
+
16
+ @injector.inject
17
+ @dataclasses.dataclass
18
+ class TaskGeneratorImpl(TaskGenerator):
19
+ _config: ProjectConfig
20
+ _di: injector.Injector
21
+ _params: Params
22
+
23
+ def _auto_load_tasks(self) -> Iterator[Task]:
24
+ for name, cls in vars(type(self)).items():
25
+ if name.startswith("task_") and isinstance(cls, type) and issubclass(cls, TaskBuilderImpl):
26
+ yield self._di.create_object(cls).apply().build()
27
+
28
+ def builder(self, task_class: type[TaskBuilderImpl]) -> TaskBuilderImpl:
29
+ return self._di.create_object(task_class)
30
+
31
+ def load_tasks(self) -> Iterator[Task]:
32
+ yield from self._auto_load_tasks()
@@ -0,0 +1,3 @@
1
+ from dothat.tasks_core.default_tasks._tasks import CiTaskGenerator
2
+
3
+ __all__ = ("CiTaskGenerator",)
@@ -0,0 +1,8 @@
1
+ from dothat.initialization import TemplateGenerator
2
+ from dothat.models.actions import InteractivePythonAction
3
+
4
+
5
+ class InitializeDefaultProject(InteractivePythonAction):
6
+ def impl(self, *, force: bool, **_) -> None:
7
+ templategenerator = TemplateGenerator()
8
+ templategenerator.generate_project_structure(force=force)
@@ -0,0 +1,11 @@
1
+ from dothat.models.params import Param
2
+
3
+
4
+ def force() -> Param:
5
+ return Param(
6
+ name="force",
7
+ long="force",
8
+ short="f",
9
+ default=False,
10
+ type=bool,
11
+ )
@@ -0,0 +1,19 @@
1
+ from collections.abc import Iterator
2
+
3
+ from dothat.default_tasks import _actions, _params
4
+ from dothat.task_builder import Task, TaskBuilderImpl
5
+ from dothat.task_generator import TaskGeneratorImpl
6
+
7
+
8
+ class InitializeDefaultProject(TaskBuilderImpl):
9
+ _basename = "init"
10
+
11
+ def apply(self):
12
+ return self.with_actions((_actions.InitializeDefaultProject,)).with_params((_params.force(),))
13
+
14
+
15
+ class CiTaskGenerator(TaskGeneratorImpl):
16
+ task_init = InitializeDefaultProject
17
+
18
+ def load_tasks(self) -> Iterator[Task]:
19
+ yield from super().load_tasks()
@@ -0,0 +1,3 @@
1
+ [extra.system]
2
+ #tasks_module = "_tasks"
3
+
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "dothat"
3
+ version = "1.0.0"
4
+ description = "framework for python tasks and actions management"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ authors = [
8
+ { name = "Arsenii Nikulin", email = "a.nikulin@pivlab.dev" },
9
+ { name = "PivLab Dev" },
10
+ ]
11
+ requires-python = ">=3.11,<4.0"
12
+
13
+ dependencies = [
14
+ "click>=8.4.1,<9.0.0",
15
+ "gitpython>=3.1.57,<4.0.0",
16
+ "injector>=0.24.0",
17
+ "loguru>=0.7.3,<1.0.0",
18
+ "pydantic>=2.13.5",
19
+ ]
20
+
21
+ [project.scripts]
22
+ dothat = "dothat.cli:cli"
23
+
24
+ [tool.ruff]
25
+ target-version = "py311"
26
+ line-length = 120
27
+ exclude = [
28
+ ".git",
29
+ ".mypy_cache",
30
+ ".pyenv",
31
+ ".pytest_cache",
32
+ ".pytype",
33
+ ".ruff_cache",
34
+ ".venv",
35
+ ".vscode",
36
+ "__pypackages__",
37
+ "_build",
38
+ "buck-out",
39
+ "build",
40
+ "dist",
41
+ "node_modules",
42
+ "site-packages",
43
+ "*venv*",
44
+ ]
45
+
46
+ [tool.ruff.lint]
47
+ select = ["E", "F", "W", "I", "B"]
48
+
49
+ [tool.ruff.format]
50
+ quote-style = "double"
51
+ indent-style = "space"
52
+
53
+ [build-system]
54
+ requires = ["hatchling"]
55
+ build-backend = "hatchling.build"
dothat-1.0.0/uv.lock ADDED
@@ -0,0 +1,253 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.11, <4.0"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.8.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "click"
16
+ version = "8.5.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
19
+ wheels = [
20
+ { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
21
+ ]
22
+
23
+ [[package]]
24
+ name = "colorama"
25
+ version = "0.4.6"
26
+ source = { registry = "https://pypi.org/simple" }
27
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
28
+ wheels = [
29
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
30
+ ]
31
+
32
+ [[package]]
33
+ name = "dothat"
34
+ version = "1.0.0"
35
+ source = { editable = "." }
36
+ dependencies = [
37
+ { name = "click" },
38
+ { name = "gitpython" },
39
+ { name = "injector" },
40
+ { name = "loguru" },
41
+ { name = "pydantic" },
42
+ ]
43
+
44
+ [package.metadata]
45
+ requires-dist = [
46
+ { name = "click", specifier = ">=8.4.1,<9.0.0" },
47
+ { name = "gitpython", specifier = ">=3.1.57,<4.0.0" },
48
+ { name = "injector", specifier = ">=0.24.0" },
49
+ { name = "loguru", specifier = ">=0.7.3,<1.0.0" },
50
+ { name = "pydantic", specifier = ">=2.13.5" },
51
+ ]
52
+
53
+ [[package]]
54
+ name = "gitdb"
55
+ version = "4.0.12"
56
+ source = { registry = "https://pypi.org/simple" }
57
+ dependencies = [
58
+ { name = "smmap" },
59
+ ]
60
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" }
61
+ wheels = [
62
+ { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" },
63
+ ]
64
+
65
+ [[package]]
66
+ name = "gitpython"
67
+ version = "3.1.62"
68
+ source = { registry = "https://pypi.org/simple" }
69
+ dependencies = [
70
+ { name = "gitdb" },
71
+ ]
72
+ sdist = { url = "https://files.pythonhosted.org/packages/e0/db/3ca813cbacb23ab6fe46ff38a9b5ef8e73e970c8051f2ce903aacafe0446/gitpython-3.1.62.tar.gz", hash = "sha256:1791de66309bc0c7cfca40bf8d2e3de7ca091cbf94e6051be1ad0722c61062af", size = 231728, upload-time = "2026-09-07T02:57:21.155Z" }
73
+ wheels = [
74
+ { url = "https://files.pythonhosted.org/packages/d6/0b/29d7965215f8ef830a7ca1f42997fe13e5693d85e9edb18f938d063ef5f2/gitpython-3.1.62-py3-none-any.whl", hash = "sha256:7002251225e10e29d2e1f49e6532613fe5d5d9f0b6f1f02997a52b38fe56899e", size = 222753, upload-time = "2026-09-07T02:57:19.762Z" },
75
+ ]
76
+
77
+ [[package]]
78
+ name = "injector"
79
+ version = "0.24.0"
80
+ source = { registry = "https://pypi.org/simple" }
81
+ sdist = { url = "https://files.pythonhosted.org/packages/17/6c/de0f64b2b8d7d8cd3206bd6fb54ebb0c7c2e2fe0f2d16103756ff1bf803b/injector-0.24.0.tar.gz", hash = "sha256:e85a75d1516cff2f03170f3fd1219f56acb25c9a05e307819ae0dcde3dad3d3f", size = 55988, upload-time = "2026-01-09T12:10:41.782Z" }
82
+ wheels = [
83
+ { url = "https://files.pythonhosted.org/packages/5f/db/953aeee405f1466832f212f61496ee07c29f7f027f7591b71b06bd08a418/injector-0.24.0-py3-none-any.whl", hash = "sha256:47294c7a7fdb811f0d1b442a1e0152bb2fc28b2ccaaba4cba44e5e125e0da2d0", size = 21504, upload-time = "2026-01-09T12:10:40.329Z" },
84
+ ]
85
+
86
+ [[package]]
87
+ name = "loguru"
88
+ version = "0.7.3"
89
+ source = { registry = "https://pypi.org/simple" }
90
+ dependencies = [
91
+ { name = "colorama", marker = "sys_platform == 'win32'" },
92
+ { name = "win32-setctime", marker = "sys_platform == 'win32'" },
93
+ ]
94
+ sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
95
+ wheels = [
96
+ { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
97
+ ]
98
+
99
+ [[package]]
100
+ name = "pydantic"
101
+ version = "2.13.5"
102
+ source = { registry = "https://pypi.org/simple" }
103
+ dependencies = [
104
+ { name = "annotated-types" },
105
+ { name = "pydantic-core" },
106
+ { name = "typing-extensions" },
107
+ { name = "typing-inspection" },
108
+ ]
109
+ sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" }
110
+ wheels = [
111
+ { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" },
112
+ ]
113
+
114
+ [[package]]
115
+ name = "pydantic-core"
116
+ version = "2.46.5"
117
+ source = { registry = "https://pypi.org/simple" }
118
+ dependencies = [
119
+ { name = "typing-extensions" },
120
+ ]
121
+ sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" }
122
+ wheels = [
123
+ { url = "https://files.pythonhosted.org/packages/a2/b6/81d2d19ea0be2c03664381b59f65fa72fc7969decedae00bc2c4ad835708/pydantic_core-2.46.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1dee1b804ff4d11c663636cf15d2ea47e9f79cd56c033fb1cbf08924842a48f", size = 2074737, upload-time = "2026-08-28T09:57:57.711Z" },
124
+ { url = "https://files.pythonhosted.org/packages/0c/18/b70da8300e292df4099684ea11b1958043580d2f50d2dc8bf7e542bdd84a/pydantic_core-2.46.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d625a186a65201c23a9e3b8ed9c47e90a026e03256608cc91851c6709096844f", size = 1921751, upload-time = "2026-08-28T09:57:59.265Z" },
125
+ { url = "https://files.pythonhosted.org/packages/e7/1a/0d590341b6ffa4b4aca83508e6b8db4761aaeacfc15a25ca3815876d4797/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8507560a9284e1370bb048ed4282012fbef4e8d109875b95e884d228552061", size = 1948231, upload-time = "2026-08-28T09:58:00.678Z" },
126
+ { url = "https://files.pythonhosted.org/packages/7d/1d/02eb35761c51f2f7b1b042d6ab4cda6600f0c8c88a2243b3f734376201e5/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f93c5fe914d75fbec9a49209b00da5f08e9e467d69da2b1510c81940cfd10be", size = 2020708, upload-time = "2026-08-28T09:58:02.267Z" },
127
+ { url = "https://files.pythonhosted.org/packages/4a/ea/f86073830e35d508cc8ddf9c3d9e6e6840fcb88d34bf726b0b4710186f27/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c767f552b21b10f774aeac128e828eafb796adfa1b666a18bf6321453c3a", size = 2194914, upload-time = "2026-08-28T09:58:03.934Z" },
128
+ { url = "https://files.pythonhosted.org/packages/bb/d7/fc36240d7791ce90939e51608568c33bfdae26202016f9770c229a487d86/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:701b2e04b560eeb4bddf7a25ab8ca476176e34fdbd9a0e18196f0d12d4685f0b", size = 2235622, upload-time = "2026-08-28T09:58:05.516Z" },
129
+ { url = "https://files.pythonhosted.org/packages/cf/bc/3fa2d76b83162820a17da7f645b28d1cba99fc8e1e5fc6517067ec450fa1/pydantic_core-2.46.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49776eab08766a08dfff7012f8b422dcd7e25e43b316eedf0477c24fcfa84b7c", size = 2062091, upload-time = "2026-08-28T09:58:07.135Z" },
130
+ { url = "https://files.pythonhosted.org/packages/ab/9a/095d557bb492c90cd8a70a6dd048bf793d433d03d86c81c11e912e4cd049/pydantic_core-2.46.5-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:a2468d93d181667a7abd66e1b64bb9f76f361b0fef8faddf687456453576f5ee", size = 2089904, upload-time = "2026-08-28T09:58:08.814Z" },
131
+ { url = "https://files.pythonhosted.org/packages/24/98/7b76b1ad10a19a617a52aaa1d80e159115af939b095e86f8e756fd52e0df/pydantic_core-2.46.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:53feb344243bb9510a9dec7bf3cf1b64d88a98af5dc7872a5160465f8b198c8e", size = 2132244, upload-time = "2026-08-28T09:58:10.435Z" },
132
+ { url = "https://files.pythonhosted.org/packages/20/32/7d6ca365fadba186a0c8f85de1a701663bce81efd309d9479be58687622f/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cd5214352ae68f3b5e9af7768bdc5253695ee069675db3480518420b3be881f2", size = 2143901, upload-time = "2026-08-28T09:58:12.033Z" },
133
+ { url = "https://files.pythonhosted.org/packages/f8/09/eb9a6aa57f22fd1541a9c0aa2a1f3aeef3ec65347d33e10a6da2f43e0ee9/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9432f3598db432cb51c5b37fdbf29a60fcccc79e30d37a05022776a6bc4ab689", size = 2299425, upload-time = "2026-08-28T09:58:13.614Z" },
134
+ { url = "https://files.pythonhosted.org/packages/8a/f9/548a5bb9d4ba8cd26e26daf48052236f6b38bb61e7b7241fbc3c995719eb/pydantic_core-2.46.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8feeac04b5794e513e710af2f9c87d49f31a6dc47967bb264a1fed61a8989bec", size = 2318566, upload-time = "2026-08-28T09:58:15.199Z" },
135
+ { url = "https://files.pythonhosted.org/packages/4a/20/06454d18834c02c406c9133f1a3b485305fd9ee984f9636c2f730bef6a9d/pydantic_core-2.46.5-cp311-cp311-win32.whl", hash = "sha256:892a881d5f68c2b9ea304b7a6c2c60d9343df578a311b0f86b94bc8f1ffe8129", size = 1954258, upload-time = "2026-08-28T09:58:16.813Z" },
136
+ { url = "https://files.pythonhosted.org/packages/9e/c2/718b9deb4b72453b5d8c7447a3b14cb77bef36917ef5f514e0948a4096a0/pydantic_core-2.46.5-cp311-cp311-win_amd64.whl", hash = "sha256:40375c2d05acec10323e45dfe2077ac44bc74659008614af5069034e2cfc781c", size = 2041030, upload-time = "2026-08-28T09:58:18.288Z" },
137
+ { url = "https://files.pythonhosted.org/packages/67/ea/c1d1a5b72d6e1ff7f377a4d9199f6591f095beb5b409a8a5d89f7238d939/pydantic_core-2.46.5-cp311-cp311-win_arm64.whl", hash = "sha256:28a6a556cd3b6066bea827857f9d9cce027c96f776e512f544a581f9e42161f8", size = 2009234, upload-time = "2026-08-28T09:58:19.929Z" },
138
+ { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" },
139
+ { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" },
140
+ { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" },
141
+ { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" },
142
+ { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" },
143
+ { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" },
144
+ { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" },
145
+ { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" },
146
+ { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" },
147
+ { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" },
148
+ { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" },
149
+ { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" },
150
+ { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" },
151
+ { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" },
152
+ { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" },
153
+ { url = "https://files.pythonhosted.org/packages/f5/37/5abe39a8372a61d3dc3c1338fc504281c01b32fdb3169cd7187153b56d3e/pydantic_core-2.46.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:b7ca9034437b6022f941f4857459562ee00a560b97e7cce8a0ec5a74fc6766e0", size = 2075885, upload-time = "2026-08-28T09:58:47.856Z" },
154
+ { url = "https://files.pythonhosted.org/packages/21/43/6323b1f8b217780454c61304bcd2b38ae4762f50754414124603ccc90bb2/pydantic_core-2.46.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f332f0e72a5a0400141f830744e141bf9f97917878dbe968669e8a7fefea78ff", size = 1922768, upload-time = "2026-08-28T09:58:49.58Z" },
155
+ { url = "https://files.pythonhosted.org/packages/0f/a3/c05ca796e1197618a774b01e596aeedfefc2f7d8c01ae3054e910b120e8a/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:193375f3548919d3f0b60936ca113ada3e38f264f91b9b8e0508efaad57be931", size = 1951241, upload-time = "2026-08-28T09:58:51.511Z" },
156
+ { url = "https://files.pythonhosted.org/packages/68/32/33bc39ac705c52cffc908e8389f9754fdb208aea5c69cceddf4eb3ce99af/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79bdfa52f843137045b2d081cc05c120ba6665d29b7559c2c47690906f39279f", size = 2031975, upload-time = "2026-08-28T09:58:53.166Z" },
157
+ { url = "https://files.pythonhosted.org/packages/b0/70/2333e885c0f6a67bc105c5916965dac9b57f2718ee20d81d1a06a4ebdc13/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:24922243639cbdac66c75fcb6fd6495a9cb52b213d62f9a0d16f0310b1ff8038", size = 2208542, upload-time = "2026-08-28T09:58:55.017Z" },
158
+ { url = "https://files.pythonhosted.org/packages/f7/ea/296debfb4264207bbda5936133892e027c0a58875ad53ebd512fba8ec3a2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76fe65e607be28c7fd4d56fc3c42b1583aa058ce3408b7ad0fd540171d31f9f", size = 2264692, upload-time = "2026-08-28T09:58:56.767Z" },
159
+ { url = "https://files.pythonhosted.org/packages/d3/f2/9e4de77a6271e07a76d2d58b11c091a979c191ed2939bf80067568b369d2/pydantic_core-2.46.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f7b393a8b3da82f5c1fc0751e6d01ac6c55b93c18226a60bdfba4a724efafd1", size = 2066633, upload-time = "2026-08-28T09:58:58.531Z" },
160
+ { url = "https://files.pythonhosted.org/packages/8d/db/f9e9d0c97445987b2084823d5c240de88087338f04fc2cfaa2df186b8049/pydantic_core-2.46.5-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:7ac031912d54f3d83ef3b3eb98dfabc1608802e2202263d25957eeed40b94761", size = 2105235, upload-time = "2026-08-28T09:59:00.421Z" },
161
+ { url = "https://files.pythonhosted.org/packages/07/c5/79169b047b3b2c3e99e04bc76372af9637e0bf6db638274fa927df96369e/pydantic_core-2.46.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:837b396ca3d7b74091ca623f6cbd8351bd42d670a79c2683e79fb089f06a2de5", size = 2157367, upload-time = "2026-08-28T09:59:02.442Z" },
162
+ { url = "https://files.pythonhosted.org/packages/26/b5/ba6057afb7c291bd449f51b867f95aef2072941c4ce4e5c31d6ffd132d3b/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:5ee239d575f80b08eca11f6e20f90c4c695de7825c67eefe6091fbf20dda648e", size = 2158420, upload-time = "2026-08-28T09:59:04.2Z" },
163
+ { url = "https://files.pythonhosted.org/packages/6e/28/2057abecaafdc22912afa819603a51f0a62d40643b7c4871c51721fea9be/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e80675d75ae2cd14372cb65cad5400d9347a3d3f6c13000183f22dfd027283ed", size = 2309588, upload-time = "2026-08-28T09:59:06.048Z" },
164
+ { url = "https://files.pythonhosted.org/packages/71/9d/881156dc404e27479c4246128d73538464cab4a239bec61995e227644c30/pydantic_core-2.46.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c4b71f10dd532fb7a5cbc8f58707779e64f03a258c2bf8bfbaecfcd9970b519", size = 2341866, upload-time = "2026-08-28T09:59:08.539Z" },
165
+ { url = "https://files.pythonhosted.org/packages/5a/38/d66f443a259f84d13babdceae568e572b0ed26da17ca5d0a649ebb110a67/pydantic_core-2.46.5-cp313-cp313-win32.whl", hash = "sha256:97bf8de4d541598c94a59344eeb988a94c08ff76b5723c41f6567ec18c7892ea", size = 1938580, upload-time = "2026-08-28T09:59:10.402Z" },
166
+ { url = "https://files.pythonhosted.org/packages/2c/1e/1d5371213f4cc9a7ed70c0bfcc7911de22311ee99a662a56077d7292d2ac/pydantic_core-2.46.5-cp313-cp313-win_amd64.whl", hash = "sha256:15f4a94963c95accac15b7b657bb177d3ad82bb90b0d0526d9a9b85079925db5", size = 2041980, upload-time = "2026-08-28T09:59:12.396Z" },
167
+ { url = "https://files.pythonhosted.org/packages/5a/48/4222d90b1c67568bace4dec6dca6271449c66de3595d72b6d098f5fde597/pydantic_core-2.46.5-cp313-cp313-win_arm64.whl", hash = "sha256:d22a945598fb91236b4dd793a6e42e4f3dd7740bb5aace5ebd7d4c08d13bb575", size = 1997213, upload-time = "2026-08-28T09:59:14.245Z" },
168
+ { url = "https://files.pythonhosted.org/packages/8e/8a/14596f2a8367da50cf7cbac48169ee5d9c8e11d486a3b527082384630c72/pydantic_core-2.46.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c1c43ad4339643d70ebb8124e1305a7dab423001eff58bb41a0f731adbc98355", size = 2074081, upload-time = "2026-08-28T09:59:16.141Z" },
169
+ { url = "https://files.pythonhosted.org/packages/ae/d5/d8a4eb6d6c7f66b91dd37c576d76e9e60fba900caf5372c17bcf949febc2/pydantic_core-2.46.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1a353f84de772f423b5ffb11d7ae352fbbef0f446f3c0b0af0f8236d7233606e", size = 1920497, upload-time = "2026-08-28T09:59:18.065Z" },
170
+ { url = "https://files.pythonhosted.org/packages/8e/26/092079428f86e927e030b2c0ced87df69dbb1c875cdeaa67bf42ea2be746/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5086029a57366b8cf81b130a43908738095c270c21a8d7f0e8bdfdb89718e2f3", size = 1952130, upload-time = "2026-08-28T09:59:20.476Z" },
171
+ { url = "https://files.pythonhosted.org/packages/08/c3/8ec0e290a9ebaebd64047bf5fda94be835c6b1551b02437e4b76778fbcd7/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46c25dda9d092a06c08db76ffe0a197107904d0dfac653f7d5306bbcd6d6119c", size = 2026371, upload-time = "2026-08-28T09:59:22.227Z" },
172
+ { url = "https://files.pythonhosted.org/packages/01/72/4fd20ad520fb8da0157f95b27a7eb05a72790ef08138e7701ac972c342ea/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37ea7b83c935e5b0d68c9449b82651accf78a10828b2c02b2f2d9e9496446c21", size = 2202822, upload-time = "2026-08-28T09:59:24.277Z" },
173
+ { url = "https://files.pythonhosted.org/packages/31/b0/d16e0771206b29314f0d52198b720be21e8a99ab2bf11e3bc0d7c9cebdff/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e64e88d5585bea9ce95861079de72006c7fa6d3df4e3a3b65ba31eb979c15c9f", size = 2262756, upload-time = "2026-08-28T09:59:26.608Z" },
174
+ { url = "https://files.pythonhosted.org/packages/2c/9b/59634b7ac631c63b2a37760eb6943af3e29573d6b59a4abc5e7f019d4cee/pydantic_core-2.46.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d510bac3ee52247af28ed4bb18a1e799f040ac60fd2bf5ccd4c92f1fbe786f", size = 2068352, upload-time = "2026-08-28T09:59:29.044Z" },
175
+ { url = "https://files.pythonhosted.org/packages/08/7c/570abb1ad2155348dc754ea91be22e5aaa18eb6d69a6068f7c6f2679a6ed/pydantic_core-2.46.5-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a2a5e1d0ff29adddc9f6d6821a66302e4493f8ca898b715b6b1182c2c201ea0a", size = 2104777, upload-time = "2026-08-28T09:59:30.95Z" },
176
+ { url = "https://files.pythonhosted.org/packages/8e/25/5bf74adc65a1ac5b7be3f6cb0bcb5433615c1598a801c19d830d84c98ded/pydantic_core-2.46.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03b9666e41e35d8909852ba191a0607520f81b74eaf12ccf8737005dbb313821", size = 2156312, upload-time = "2026-08-28T09:59:32.604Z" },
177
+ { url = "https://files.pythonhosted.org/packages/90/6a/2ef38830675e050121040618135564ed56b860b45433b02d9b4ebece46f3/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:a91c17edf6eea2402cb5457b4c89e99bc5ed1004aa34c4adf1d4258c1a5c22c2", size = 2150067, upload-time = "2026-08-28T09:59:34.453Z" },
178
+ { url = "https://files.pythonhosted.org/packages/90/ef/a7dbb03a14a64c2a4621f989c615ed9a892535a6cad938fc27079f919d80/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b49924c73a235e969511bf2aabdff3beebf9820931f646c80274d5d780010c47", size = 2304516, upload-time = "2026-08-28T09:59:36.194Z" },
179
+ { url = "https://files.pythonhosted.org/packages/68/f8/6bb4c4b80e8a6fde1904c64a51c62a1d04fcdfa3ea521a66b2ddefa1d885/pydantic_core-2.46.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd9a5eff05e51c447c34dfa4632145b26b09120cf04bd0c871e44c1a5e1c9a", size = 2335223, upload-time = "2026-08-28T09:59:37.931Z" },
180
+ { url = "https://files.pythonhosted.org/packages/2a/80/f46b8c681195190b2c1f1c7c0a81abce60663e987613e09ef64d433dd96b/pydantic_core-2.46.5-cp314-cp314-win32.whl", hash = "sha256:2d5d76654becf5efd62c9e51c3756c67b49498b0c9a40884934c40807adbd074", size = 1934827, upload-time = "2026-08-28T09:59:39.836Z" },
181
+ { url = "https://files.pythonhosted.org/packages/f7/3c/60674207246bc0a4009d2391b7c7251c7159f279c8d2ab8aae8ef46f3dee/pydantic_core-2.46.5-cp314-cp314-win_amd64.whl", hash = "sha256:fa10ef4112775900e7a0661068635eb67b2ab824fbde764de6e0e21982a93db0", size = 2042648, upload-time = "2026-08-28T09:59:41.792Z" },
182
+ { url = "https://files.pythonhosted.org/packages/69/0c/117c562c7c1babdf44576b72a5e496906506c93690387ecfbca7c729ae2e/pydantic_core-2.46.5-cp314-cp314-win_arm64.whl", hash = "sha256:045ab3b6d308439e32b81cc173bba5b9018bc6ed896afd0c65b3b009b1699af5", size = 1989652, upload-time = "2026-08-28T09:59:43.702Z" },
183
+ { url = "https://files.pythonhosted.org/packages/e8/66/9336ae58f9eb68c41d121894e52c4c89eccb07eb8f602a04ee9c3f37736a/pydantic_core-2.46.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8816f3d218beb4b787de5c9759c259b8fa61f9dec42dc7811f320a33771778b7", size = 2065829, upload-time = "2026-08-28T09:59:45.364Z" },
184
+ { url = "https://files.pythonhosted.org/packages/c5/02/bc19b47a96c2d3109760711acf22369e56bd7e405ca52f7ade164d2ead57/pydantic_core-2.46.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bce57638e08ac148e5778cce7feb968307a727d66f8e2274a543d0cf0c9ad6a3", size = 1905716, upload-time = "2026-08-28T09:59:47.18Z" },
185
+ { url = "https://files.pythonhosted.org/packages/52/a4/70b47c0509923dd98ccfed04fb3e32ea3849c82a0ff2205bb41009b43c00/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:976e1128455aa595ea04c79ccfedff1aaeab96ee013fcc916bed120c4f0ad94f", size = 1934216, upload-time = "2026-08-28T09:59:49.241Z" },
186
+ { url = "https://files.pythonhosted.org/packages/52/ab/aa03b65f7bb198585edf806b906c3223ecf1795543e39e23aec4cce27ad2/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b891faeedeafba41b2983e5001a81b6a915b69544c7e7570d1989ce1c36ac7", size = 2010635, upload-time = "2026-08-28T09:59:51.692Z" },
187
+ { url = "https://files.pythonhosted.org/packages/3c/8b/0da06343f30b84ec549aafd309c6456223d5dc8bd36af504c573faad561d/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f194189415698233dd1114a093a9b56e61e2c57e11b469be3b0506f46f0771c", size = 2209369, upload-time = "2026-08-28T09:59:53.582Z" },
188
+ { url = "https://files.pythonhosted.org/packages/d6/5b/844c4defaa34a3df66eb9257087d121d70c201298b96abdf9f492fc2f1bf/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82a36973cf8a2ef5406f4fe2edbf8ed0c99629535d959e0b100c76a32535a111", size = 2253238, upload-time = "2026-08-28T09:59:55.484Z" },
189
+ { url = "https://files.pythonhosted.org/packages/f4/64/a4e536cb16d7f61a7fd3120b46c577fc7fa7325992f69c4f52bc786d77d8/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdbb78909f52b981d3b2d56b97328d71eb0b974c36bd77c920123a7ebb192829", size = 2065740, upload-time = "2026-08-28T09:59:58.038Z" },
190
+ { url = "https://files.pythonhosted.org/packages/5f/75/aaa38c6bc2d085f6605b34eabdc6a8a4e0b2e61fc9c8e6e52b28e97b3125/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:52e24eacdb536cade636aa90fb851835222becff8484b7001fdc78cb0290f2aa", size = 2087425, upload-time = "2026-08-28T09:59:59.898Z" },
191
+ { url = "https://files.pythonhosted.org/packages/55/ae/fcab4cfc39aba3689e1d20c8b5250ad280957022c09af2ed9cd585602a5e/pydantic_core-2.46.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:37ae34309d7bd8c0d61ab839668058f2a7962ea1fc51d105d2db228fe0618034", size = 2139306, upload-time = "2026-08-28T10:00:03.057Z" },
192
+ { url = "https://files.pythonhosted.org/packages/2d/f4/f1d03a4bc9d9acbc62f4d742b8a319af52f71885079868b2ff8e48a651ee/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:0cdbada856a1c69a7624a64d3d9aefe79300bd6ef827b43a4f265010b9b55184", size = 2144589, upload-time = "2026-08-28T10:00:05.645Z" },
193
+ { url = "https://files.pythonhosted.org/packages/83/f3/7a53bb1356de514a4cd295f25b6ac39237895620c0462d2592b76c16e114/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:545f26c504b27c3758439a5e6d9349931f0a04f855668d5fe323c89e82300a38", size = 2288882, upload-time = "2026-08-28T10:00:07.931Z" },
194
+ { url = "https://files.pythonhosted.org/packages/cd/94/5a81583660c175c59d49ffb09f4b3a44debeaf86a19fca664ae1cdd9ee32/pydantic_core-2.46.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:ff218293c9c806138dca139765e3b067621be52bcd93cdc14c7711be7ddc90a9", size = 2335210, upload-time = "2026-08-28T10:00:10.177Z" },
195
+ { url = "https://files.pythonhosted.org/packages/5a/9f/5d685c2693b972d1a59c998586e8823712b66603aeff47ee60a4bdaafd37/pydantic_core-2.46.5-cp314-cp314t-win32.whl", hash = "sha256:97cf3eb53a8cccacf9d46686a0926186c9bfb5574f2ed66d3639d5fe117cd3a9", size = 1921180, upload-time = "2026-08-28T10:00:12.35Z" },
196
+ { url = "https://files.pythonhosted.org/packages/70/12/5c94ee16d65a37a15f9e869f5e6256df111154491173801a4c5e800ab548/pydantic_core-2.46.5-cp314-cp314t-win_amd64.whl", hash = "sha256:d2f9fc07a8042a8f95925b35c4f04f469707c981fc33245b6ca187cf5d2dd290", size = 2020515, upload-time = "2026-08-28T10:00:14.774Z" },
197
+ { url = "https://files.pythonhosted.org/packages/63/19/67830dda664e6bdf9285ee2e40f355d0d7d6b92aa0c42e8d217bb8d33d36/pydantic_core-2.46.5-cp314-cp314t-win_arm64.whl", hash = "sha256:acf8a67ba51f4ca9ddbd0e6b3000a65ac51ab734661778b3e7ba64d99a710f2f", size = 1989276, upload-time = "2026-08-28T10:00:16.984Z" },
198
+ { url = "https://files.pythonhosted.org/packages/af/1e/ecca01fce348f7e8afa9572441ff6f7d1cc70d21e4859f33944d10877e1e/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:c14ad3bdc85ee7f318742c457ca3968a92126d144b15721c759033bfb06296c2", size = 2075342, upload-time = "2026-08-28T10:00:51.353Z" },
199
+ { url = "https://files.pythonhosted.org/packages/1f/4c/af80c7a8032dfc897040ad5cb772bebde529a381186499e6e29987f23f8c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0bddb4020d8f04175865ccd17eff3040874fc11fb593f424edb452653b4b947c", size = 1907219, upload-time = "2026-08-28T10:00:53.438Z" },
200
+ { url = "https://files.pythonhosted.org/packages/be/3e/54d89e2b092e778716bf6153634ef479e955f48c261090be23aa1e0fb0b5/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2471fd51c61c610e1dcf7de44d7299283661654d11264ab4802b303368d69c47", size = 1953393, upload-time = "2026-08-28T10:00:55.58Z" },
201
+ { url = "https://files.pythonhosted.org/packages/ea/89/828ee90cda28ce17bdefaa3a6eaf74fe430e113295a10e6126beca559d6c/pydantic_core-2.46.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10ec717381bdbfafef34607824db4c91de69ff085e4fca3b2af91b4fa17e68a", size = 2099024, upload-time = "2026-08-28T10:00:57.794Z" },
202
+ { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" },
203
+ { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" },
204
+ { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" },
205
+ { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" },
206
+ { url = "https://files.pythonhosted.org/packages/20/21/22102e9950b3049526d20e811b95396508377d87651edd2b80d2b3d28659/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ab4b66edffb32d9e951efb3814bd104b8367a7501b81b955cacb5726d897389f", size = 2071333, upload-time = "2026-08-28T10:01:09.636Z" },
207
+ { url = "https://files.pythonhosted.org/packages/d8/18/87aefa427d191e6d3ab1447f1efc1cdcac86af1069239b133e8a0fd7f7c9/pydantic_core-2.46.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:337639ba62a11acde6ef3aeb08c8ea755f8ef1fe5e513356c0f36a2b0d7568b0", size = 1912713, upload-time = "2026-08-28T10:01:12.285Z" },
208
+ { url = "https://files.pythonhosted.org/packages/1f/93/fd89e9ad49b1805ca94d24ce1088b7d305f05c35ffafcedb9819d03588a0/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413a717a410d0c817ef5b786a059415550b3794e1d0c2abffd9efb93a3d9f7b4", size = 2090926, upload-time = "2026-08-28T10:01:15.19Z" },
209
+ { url = "https://files.pythonhosted.org/packages/6f/45/8e59dab6acf8d35f02f0a958980074f31038968bdb2c983fcae9d1efee03/pydantic_core-2.46.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e449def1945a462c464331254e5a44fca7c3b4f9aedf59ec2f50f8066dd8e25", size = 2131303, upload-time = "2026-08-28T10:01:17.937Z" },
210
+ { url = "https://files.pythonhosted.org/packages/d5/a5/e1d4dc5180dd887a9522efc1f8716b8692b7606b1d3273d7862eaf66be44/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:a445486499897b88a7d6c310c88ed64dd37b1b59bfd7ae9107490bbb362f47d6", size = 2145128, upload-time = "2026-08-28T10:01:20.694Z" },
211
+ { url = "https://files.pythonhosted.org/packages/c2/d7/ad493864a7fb21c0c4df98f965e2db430cb25a9d7369b5778d5016c09fd9/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2d330aaba8621b1edcec8ae2c4050f63b84ccf6d98723a8f212e9684713abf0e", size = 2294560, upload-time = "2026-08-28T10:01:23.495Z" },
212
+ { url = "https://files.pythonhosted.org/packages/02/8e/b41c84c913f29973a268e6c2b5bbf13c95adb9956c126d10da11ba3b2bef/pydantic_core-2.46.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b6acfb46a814762367fb7ba0828b0a17d441b92ce249a0e007474c9072662dda", size = 2317531, upload-time = "2026-08-28T10:01:26.334Z" },
213
+ { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" },
214
+ ]
215
+
216
+ [[package]]
217
+ name = "smmap"
218
+ version = "5.0.3"
219
+ source = { registry = "https://pypi.org/simple" }
220
+ sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" }
221
+ wheels = [
222
+ { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" },
223
+ ]
224
+
225
+ [[package]]
226
+ name = "typing-extensions"
227
+ version = "4.16.0"
228
+ source = { registry = "https://pypi.org/simple" }
229
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
230
+ wheels = [
231
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
232
+ ]
233
+
234
+ [[package]]
235
+ name = "typing-inspection"
236
+ version = "0.4.4"
237
+ source = { registry = "https://pypi.org/simple" }
238
+ dependencies = [
239
+ { name = "typing-extensions" },
240
+ ]
241
+ sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
242
+ wheels = [
243
+ { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
244
+ ]
245
+
246
+ [[package]]
247
+ name = "win32-setctime"
248
+ version = "1.2.0"
249
+ source = { registry = "https://pypi.org/simple" }
250
+ sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
251
+ wheels = [
252
+ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
253
+ ]