diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2ceadd1..5eff626 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ ci: - skip: [pytest] + skip: [] default_language_version: python: python3.13 @@ -11,6 +11,7 @@ repos: hooks: - id: check-yaml args: [--allow-multiple-documents] + exclude: config\.yaml$ - id: end-of-file-fixer - id: trailing-whitespace @@ -29,6 +30,14 @@ repos: hooks: - id: mypy name: mypy + additional_dependencies: + [ + types-PyYAML, + types-requests, + pydantic, + python-dotenv, + pyyaml-env-tag, + ] # docformatter - formats docstrings to follow PEP 257 - repo: https://github.com/pycqa/docformatter @@ -62,15 +71,6 @@ repos: - -r - src - - repo: local - hooks: - - id: pytest - name: pytest - entry: uv run pytest tests --cov=src - language: system - types: [python] - pass_filenames: false - # prettier - formatting JS, CSS, JSON, Markdown, ... - repo: https://github.com/pre-commit/mirrors-prettier rev: v3.1.0 diff --git a/pyproject.toml b/pyproject.toml index e778811..30e3926 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,10 @@ authors = [ ] requires-python = ">=3.13" readme = "README.md" -dependencies = [] +dependencies = [ + "python-dotenv>=1.1.0", + "pyyaml-env-tag>=1.1", +] [tool.uv.sources] utils = { workspace = true } @@ -27,6 +30,8 @@ dev-dependencies = [ "docformatter>=1.7.5", "ruff>=0.9.10", "pre-commit>=3.8.0", + "types-PyYAML>=6.0.12", + "types-requests>=2.32", "utils", "core", ] diff --git a/src/core/.env.example b/src/core/.env.example new file mode 100644 index 0000000..8918f1a --- /dev/null +++ b/src/core/.env.example @@ -0,0 +1 @@ +API_BASE_URL="https://jsonplaceholder.typicode.com" diff --git a/src/core/README.md b/src/core/README.md index e69de29..03cd839 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -0,0 +1,48 @@ +# Core Module + +Core application module providing CLI interface and business logic functionality. + +## 📁 Structure + +``` +src/ +├── clients/ # HTTP clients and external integrations +├── commands/ # CLI command implementations +├── config/ # Configuration management +│ ├── config.yaml # Application configuration +│ └── logger_config.py +├── exceptions/ # Custom exception classes +├── models/ # Data models and schemas +│ └── config.py # Settings and configuration models +├── services/ # Business logic services +├── utils/ # Core utilities +└── main.py # Application entry point +``` + +## 🚀 Usage + +### Run the application + +```bash +uv run core +``` + +### Show help and available commands + +```bash +uv run core --help +``` + +### Get help for specific command + +```bash +uv run core [command] --help +``` + +## 📋 Available Commands + +To see all available commands, run: + +```bash +uv run core --help +``` diff --git a/src/core/pyproject.toml b/src/core/pyproject.toml index 551df57..f23a408 100644 --- a/src/core/pyproject.toml +++ b/src/core/pyproject.toml @@ -3,21 +3,29 @@ name = "core" version = "0.0.0" description = "Core functionality" authors = [ - {name="Aleksander Kowalski"} + { name = "Aleksander Kowalski" } ] requires-python = ">=3.13" readme = "README.md" dependencies = [ - "utils", - "typer>=0.12.5" + "utils", + "typer>=0.12.5", + "pydantic>=2.0.0", + "pyyaml>=6.0.0", + "wireup>=0.8.0", + "requests>=2.31.0", + "tabulate>=0.9.0" ] [project.scripts] -core = "core.cli:app" +core = "src.main:entrypoint" [build-system] requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.build.targets.wheel] +packages = ["src"] + [tool.uv.sources] utils = { workspace = true } diff --git a/src/core/src/__init__.py b/src/core/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/clients/__init__.py b/src/core/src/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/clients/http_client.py b/src/core/src/clients/http_client.py new file mode 100644 index 0000000..4a7c1df --- /dev/null +++ b/src/core/src/clients/http_client.py @@ -0,0 +1,32 @@ +from typing import Any, Dict, Optional + +import requests # type: ignore +from ..models.config import Settings +from wireup import service + + +@service +class HttpClient: + def __init__(self, settings: Settings): + self.base_url = settings.api.base_url.rstrip("/") + self.session = requests.Session() + + def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + url = f"{self.base_url}{endpoint}" + try: + response = self.session.get(url, params=params) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + raise Exception(f"HTTP request failed: {e}") + except ValueError as e: + raise Exception(f"Invalid JSON response: {e}") + + def close(self): + self.session.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() diff --git a/src/core/src/commands/__init__.py b/src/core/src/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/commands/user_commands.py b/src/core/src/commands/user_commands.py new file mode 100644 index 0000000..bdb21cd --- /dev/null +++ b/src/core/src/commands/user_commands.py @@ -0,0 +1,52 @@ +import logging + +import typer +from ..config.registry import command_group +from ..services.user_service import UserService +from wireup import service + + +@command_group("user") +@service() +class UserCommands: + def __init__(self, user_service: UserService, logger: logging.Logger): + self.user_service = user_service + self.logger = logger + self.app = typer.Typer(help="User management commands") + self._register_commands() + + def _register_commands(self): + self.app.command("get", help="Fetch a specific user by ID")(self.get_user) + self.app.command("list", help="List all users")(self.list_users) + self.app.command("posts", help="Get posts for a specific user")(self.get_user_posts) + + def get_user(self, user_id: int = typer.Option(1, "--id", help="User ID to fetch")) -> None: + try: + user = self.user_service.get_user_by_id(user_id) + self.logger.info(f"User: {user.name} ({user.email})") + self.logger.info(f"Company: {user.company.name}") + self.logger.info(f"Address: {user.address.street}, {user.address.city}") + except Exception as e: + self.logger.error(f"Error fetching user: {e}") + raise typer.Exit(1) + + def list_users(self) -> None: + try: + users = self.user_service.get_all_users() + self.logger.info(f"Found {len(users)} users:") + for user in users: + self.logger.info(f" {user.id}: {user.name} ({user.email})") + except Exception as e: + self.logger.error(f"Error fetching users: {e}") + raise typer.Exit(1) + + def get_user_posts(self, user_id: int = typer.Option(1, "--id", help="User ID")) -> None: + try: + posts = self.user_service.get_user_posts(user_id) + user = self.user_service.get_user_by_id(user_id) + self.logger.info(f"Posts by {user.name}:") + for post in posts: + self.logger.info(f" - {post['title']}") + except Exception as e: + self.logger.error(f"Error fetching posts: {e}") + raise typer.Exit(1) diff --git a/src/core/src/config/__init__.py b/src/core/src/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/config/config.yaml b/src/core/src/config/config.yaml new file mode 100644 index 0000000..b79a4e1 --- /dev/null +++ b/src/core/src/config/config.yaml @@ -0,0 +1,30 @@ +#https://docs.python.org/3/library/logging.config.html#logging-config-dictschema +logging: + version: 1 + formatters: + simple: + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + datefmt: "%Y-%m-%d %H:%M:%S" + handlers: + console: + class: logging.StreamHandler + level: DEBUG + formatter: simple + stream: ext://sys.stdout + loggers: + reports: + level: DEBUG + handlers: [console] + propagate: no + root: + level: INFO + handlers: [console] + +api: + base_url: !ENV API_BASE_URL + retry_attempts: 3 + +app: + name: "Hello World CLI" + version: "1.0.0" + default_greeting: "Hello" diff --git a/src/core/src/config/logger_config.py b/src/core/src/config/logger_config.py new file mode 100644 index 0000000..875ad14 --- /dev/null +++ b/src/core/src/config/logger_config.py @@ -0,0 +1,13 @@ +import logging +from logging.config import dictConfig + +from ..models.config import Settings +from wireup import service + + +@service +def create_logger(settings: Settings) -> logging.Logger: + dictConfig(settings.logging) + logger = logging.getLogger() + logger.info("✅ Skonfigurowano logger") + return logger diff --git a/src/core/src/config/registry.py b/src/core/src/config/registry.py new file mode 100644 index 0000000..4536c92 --- /dev/null +++ b/src/core/src/config/registry.py @@ -0,0 +1,14 @@ +_command_registry = [] + + +def command_group(name: str): + def decorator(cls): + cls._command_name = name + _command_registry.append(cls) + return cls + + return decorator + + +def get_registered_commands(): + return _command_registry diff --git a/src/core/src/core/__init__.py b/src/core/src/core/__init__.py deleted file mode 100644 index 162dd1a..0000000 --- a/src/core/src/core/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from utils import return_one - - -def hello(): - print("Hello from core core!") - - -def return_two() -> int: - return 1 + return_one() diff --git a/src/core/src/core/cli.py b/src/core/src/core/cli.py deleted file mode 100644 index 334ec25..0000000 --- a/src/core/src/core/cli.py +++ /dev/null @@ -1,14 +0,0 @@ -import typer - -from core import return_two - -app = typer.Typer() - - -@app.command() -def run(): - print(f"Here is {return_two()} for you") - - -def entrypoint(): - app() diff --git a/src/core/src/exceptions/__init__.py b/src/core/src/exceptions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/main.py b/src/core/src/main.py new file mode 100644 index 0000000..f33a160 --- /dev/null +++ b/src/core/src/main.py @@ -0,0 +1,32 @@ +import typer +import wireup +from .clients.http_client import HttpClient +from .config.registry import get_registered_commands +from .commands.user_commands import UserCommands +from .config.logger_config import create_logger +from .models.config import Settings +from .services.user_service import UserService +from utils import hello as hello_world_from_utils + +container = wireup.create_sync_container( + services=[Settings, create_logger, HttpClient, UserService, UserCommands] +) + +app = typer.Typer() + +for command_class in get_registered_commands(): + command_instance = container.get(command_class) + app.add_typer(command_instance.app, name=command_class._command_name) + + +@app.command(help="Greet someone with a friendly message") +def hello(): + hello_world_from_utils() + + +def entrypoint(): + app() + + +if __name__ == "__main__": + entrypoint() diff --git a/src/core/src/models/__init__.py b/src/core/src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/models/config.py b/src/core/src/models/config.py new file mode 100644 index 0000000..bf307b3 --- /dev/null +++ b/src/core/src/models/config.py @@ -0,0 +1,45 @@ +from pathlib import Path +import yaml +from pydantic import BaseModel +from wireup import service +from yaml.loader import SafeLoader +from yaml_env_tag import construct_env_tag +from dotenv import load_dotenv + +SafeLoader.add_constructor("!ENV", construct_env_tag) + + +class ApiConfig(BaseModel): + base_url: str + retry_attempts: int + + +class AppConfig(BaseModel): + name: str + version: str + default_greeting: str + + +@service +class Settings(BaseModel): + api: ApiConfig + app: AppConfig + logging: dict + + def __init__(self): + path = Path(__file__).parent.parent + config_path = path / "config" / "config.yaml" + + load_dotenv(dotenv_path=path.parent / ".env") + + if not config_path.exists(): + raise FileNotFoundError(f"Config file not found: {config_path}") + + with open(config_path, "r") as f: + config_data = yaml.load(f, Loader=SafeLoader) + + super().__init__(**config_data) + + @classmethod + def load_from_yaml(cls) -> "Settings": + return cls() diff --git a/src/core/src/models/user.py b/src/core/src/models/user.py new file mode 100644 index 0000000..284d2f1 --- /dev/null +++ b/src/core/src/models/user.py @@ -0,0 +1,32 @@ +from typing import Any, Dict + +from pydantic import BaseModel + + +class Address(BaseModel): + street: str + suite: str + city: str + zipcode: str + geo: Dict[str, str] + + +class Company(BaseModel): + name: str + catchPhrase: str + bs: str + + +class User(BaseModel): + id: int + name: str + username: str + email: str + address: Address + phone: str + website: str + company: Company + + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> "User": + return cls(**data) diff --git a/src/core/src/services/__init__.py b/src/core/src/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/src/services/user_service.py b/src/core/src/services/user_service.py new file mode 100644 index 0000000..f972d87 --- /dev/null +++ b/src/core/src/services/user_service.py @@ -0,0 +1,22 @@ +from typing import Any, Dict, List, cast + +from ..clients.http_client import HttpClient +from ..models.user import User +from wireup import service + + +@service() +class UserService: + def __init__(self, http_client: HttpClient): + self.http_client = http_client + + def get_user_by_id(self, user_id: int) -> User: + data = self.http_client.get(f"/users/{user_id}") + return User.from_dict(cast(Dict[str, Any], data)) + + def get_all_users(self) -> List[User]: + data = self.http_client.get("/users") + return [User.from_dict(user_data) for user_data in cast(List[Dict[str, Any]], data)] + + def get_user_posts(self, user_id: int) -> List[Dict[str, Any]]: + return cast(List[Dict[str, Any]], self.http_client.get(f"/users/{user_id}/posts")) diff --git a/src/core/src/utils/__init__.py b/src/core/src/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/src/core/test_return_two.py b/tests/src/core/test_return_two.py deleted file mode 100644 index 08f0943..0000000 --- a/tests/src/core/test_return_two.py +++ /dev/null @@ -1,5 +0,0 @@ -from core import return_two - - -def test_return_two(): - assert return_two() == 2 diff --git a/uv.lock b/uv.lock index 4bc0507..5aaab45 100644 --- a/uv.lock +++ b/uv.lock @@ -8,6 +8,15 @@ members = [ "utils", ] +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + [[package]] name = "bandit" version = "1.8.3" @@ -23,6 +32,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/85/db74b9233e0aa27ec96891045c5e920a64dd5cbccd50f8e64e9460f48d35/bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8", size = 129078 }, ] +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, +] + [[package]] name = "cfgv" version = "3.4.0" @@ -80,14 +98,24 @@ name = "core" version = "0.0.0" source = { editable = "src/core" } dependencies = [ + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tabulate" }, { name = "typer" }, { name = "utils" }, + { name = "wireup" }, ] [package.metadata] requires-dist = [ + { name = "pydantic", specifier = ">=2.0.0" }, + { name = "pyyaml", specifier = ">=6.0.0" }, + { name = "requests", specifier = ">=2.31.0" }, + { name = "tabulate", specifier = ">=0.9.0" }, { name = "typer", specifier = ">=0.12.5" }, { name = "utils", editable = "shared/utils" }, + { name = "wireup", specifier = ">=0.8.0" }, ] [[package]] @@ -159,6 +187,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101 }, ] +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + [[package]] name = "iniconfig" version = "2.0.0" @@ -193,6 +230,10 @@ wheels = [ name = "monorepo" version = "0.0.0" source = { virtual = "." } +dependencies = [ + { name = "python-dotenv" }, + { name = "pyyaml-env-tag" }, +] [package.dev-dependencies] dev = [ @@ -204,10 +245,16 @@ dev = [ { name = "pytest" }, { name = "pytest-cov" }, { name = "ruff" }, + { name = "types-pyyaml" }, + { name = "types-requests" }, { name = "utils" }, ] [package.metadata] +requires-dist = [ + { name = "python-dotenv", specifier = ">=1.1.0" }, + { name = "pyyaml-env-tag", specifier = ">=1.1" }, +] [package.metadata.requires-dev] dev = [ @@ -219,6 +266,8 @@ dev = [ { name = "pytest", specifier = ">=8.3.5" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "ruff", specifier = ">=0.9.10" }, + { name = "types-pyyaml", specifier = ">=6.0.12" }, + { name = "types-requests", specifier = ">=2.32" }, { name = "utils", editable = "shared/utils" }, ] @@ -314,6 +363,49 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707 }, ] +[[package]] +name = "pydantic" +version = "2.11.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, +] + [[package]] name = "pygments" version = "2.18.0" @@ -351,6 +443,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841 }, ] +[[package]] +name = "python-dotenv" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -368,6 +469,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + [[package]] name = "rich" version = "13.8.1" @@ -436,6 +564,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/45/8c4ebc0c460e6ec38e62ab245ad3c7fc10b210116cea7c16d61602aa9558/stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe", size = 49533 }, ] +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252 }, +] + [[package]] name = "typer" version = "0.12.5" @@ -451,6 +588,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/2b/886d13e742e514f704c33c4caa7df0f3b89e5a25ef8db02aa9ca3d9535d5/typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", size = 47288 }, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312 }, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20250602" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/b0/5321e6eeba5d59e4347fcf9bf06a5052f085c3aa0f4876230566d6a4dc97/types_requests-2.32.0.20250602.tar.gz", hash = "sha256:ee603aeefec42051195ae62ca7667cd909a2f8128fdf8aad9e8a5219ecfab3bf", size = 23042 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/18/9b782980e575c6581d5c0c1c99f4c6f89a1d7173dad072ee96b2756c02e6/types_requests-2.32.0.20250602-py3-none-any.whl", hash = "sha256:f4f335f87779b47ce10b8b8597b409130299f6971ead27fead4fe7ba6ea3e726", size = 20638 }, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -460,12 +618,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "typing-inspection" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, +] + [[package]] name = "untokenize" version = "0.1.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f7/46/e7cea8159199096e1df52da20a57a6665da80c37fb8aeb848a3e47442c32/untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2", size = 3099 } +[[package]] +name = "urllib3" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 }, +] + [[package]] name = "utils" version = "0.0.0" @@ -484,3 +663,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c wheels = [ { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982 }, ] + +[[package]] +name = "wireup" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/00/1633d3dae0bea05e57436eb6da439d3245800a45b4124e6590f5420e4b5c/wireup-1.0.2.tar.gz", hash = "sha256:8c7a7fdc2db02265b9a47b94c45a15229dcf9466f3fd043214b7691fbacf11a8", size = 24763 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/4e/8ac22d28c4b3eb2b34eb8b9436cde0090c5544c22c1e6f6934a393ba4794/wireup-1.0.2-py3-none-any.whl", hash = "sha256:cae3c2a8eaf98f7a224d4c5b9ec1dfca424b13d585290f5b3f0137a12f4695cd", size = 30131 }, +]