From b531a85e60b93648ed107c80731bfe23de639557 Mon Sep 17 00:00:00 2001 From: Ankit R Gadiya Date: Tue, 30 Sep 2025 10:54:56 +0530 Subject: [PATCH 1/8] feat(rbac): add rbac models --- rapyuta_io_sdk_v2/__init__.py | 123 ++++-- rapyuta_io_sdk_v2/client.py | 442 ++++++++++++++++----- rapyuta_io_sdk_v2/config.py | 13 +- rapyuta_io_sdk_v2/models/deployment.py | 83 ++-- rapyuta_io_sdk_v2/models/disk.py | 64 ++- rapyuta_io_sdk_v2/models/managedservice.py | 23 +- rapyuta_io_sdk_v2/models/network.py | 3 +- rapyuta_io_sdk_v2/models/organization.py | 28 +- rapyuta_io_sdk_v2/models/package.py | 3 +- rapyuta_io_sdk_v2/models/project.py | 95 +++-- rapyuta_io_sdk_v2/models/role.py | 26 ++ rapyuta_io_sdk_v2/models/rolebinding.py | 40 ++ rapyuta_io_sdk_v2/models/secret.py | 31 +- rapyuta_io_sdk_v2/models/staticroute.py | 46 ++- rapyuta_io_sdk_v2/models/user.py | 77 ++-- rapyuta_io_sdk_v2/models/usergroup.py | 38 ++ rapyuta_io_sdk_v2/models/utils.py | 69 +++- 17 files changed, 826 insertions(+), 378 deletions(-) create mode 100644 rapyuta_io_sdk_v2/models/role.py create mode 100644 rapyuta_io_sdk_v2/models/rolebinding.py create mode 100644 rapyuta_io_sdk_v2/models/usergroup.py diff --git a/rapyuta_io_sdk_v2/__init__.py b/rapyuta_io_sdk_v2/__init__.py index ba9380c..23c59d7 100644 --- a/rapyuta_io_sdk_v2/__init__.py +++ b/rapyuta_io_sdk_v2/__init__.py @@ -1,36 +1,93 @@ -# ruff: noqa -from rapyuta_io_sdk_v2.async_client import AsyncClient -from rapyuta_io_sdk_v2.client import Client -from rapyuta_io_sdk_v2.config import Configuration -from rapyuta_io_sdk_v2.utils import walk_pages - -# Import all models directly into the main namespace -from .models import ( - # Core models - Secret, - StaticRoute, - Disk, - Deployment, - Package, - Project, - Network, - User, - Organization, - # List models - ProjectList, - DeploymentList, - DiskList, - NetworkList, - PackageList, - SecretList, - StaticRouteList, - # Managed service models - ManagedServiceProvider, - ManagedServiceBinding, - ManagedServiceBindingList, - ManagedServiceInstance, - ManagedServiceInstanceList, - ManagedServiceProviderList, +from rapyuta_io_sdk_v2.async_client import AsyncClient as AsyncClient +from rapyuta_io_sdk_v2.client import Client as Client +from rapyuta_io_sdk_v2.config import Configuration as Configuration +from rapyuta_io_sdk_v2.models.deployment import ( + Deployment as Deployment, +) +from rapyuta_io_sdk_v2.models.deployment import ( + DeploymentList as DeploymentList, +) +from rapyuta_io_sdk_v2.models.disk import ( + Disk as Disk, +) +from rapyuta_io_sdk_v2.models.disk import ( + DiskList as DiskList, +) +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceBinding as ManagedServiceBinding, +) +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceBindingList as ManagedServiceBindingList, +) +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceInstance as ManagedServiceInstance, +) +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceInstanceList as ManagedServiceInstanceList, +) +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceProvider as ManagedServiceProvider, +) +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceProviderList as ManagedServiceProviderList, +) +from rapyuta_io_sdk_v2.models.network import ( + Network as Network, +) +from rapyuta_io_sdk_v2.models.network import ( + NetworkList as NetworkList, +) +from rapyuta_io_sdk_v2.models.organization import ( + Organization as Organization, +) +from rapyuta_io_sdk_v2.models.package import ( + Package as Package, +) +from rapyuta_io_sdk_v2.models.package import ( + PackageList as PackageList, +) +from rapyuta_io_sdk_v2.models.project import ( + Project as Project, +) +from rapyuta_io_sdk_v2.models.project import ( + ProjectList as ProjectList, +) +from rapyuta_io_sdk_v2.models.role import ( + Role as Role, +) +from rapyuta_io_sdk_v2.models.role import ( + RoleList as RoleList, +) +from rapyuta_io_sdk_v2.models.rolebinding import ( + RoleBinding as RoleBinding, +) +from rapyuta_io_sdk_v2.models.rolebinding import ( + RoleBindingList as RoleBindingList, +) +from rapyuta_io_sdk_v2.models.secret import ( + Secret as Secret, +) +from rapyuta_io_sdk_v2.models.secret import ( + SecretList as SecretList, +) +from rapyuta_io_sdk_v2.models.staticroute import ( + StaticRoute as StaticRoute, +) +from rapyuta_io_sdk_v2.models.staticroute import ( + StaticRouteList as StaticRouteList, +) +from rapyuta_io_sdk_v2.models.user import ( + User as User, +) +from rapyuta_io_sdk_v2.models.user import ( + UserList as UserList, +) +from rapyuta_io_sdk_v2.models.usergroup import ( + UserGroup as UserGroup, +) +from rapyuta_io_sdk_v2.models.usergroup import ( + UserGroupList as UserGroupList, ) +from rapyuta_io_sdk_v2.utils import walk_pages as walk_pages __version__ = "0.3.0" diff --git a/rapyuta_io_sdk_v2/client.py b/rapyuta_io_sdk_v2/client.py index 09e8f2c..c56e3c7 100644 --- a/rapyuta_io_sdk_v2/client.py +++ b/rapyuta_io_sdk_v2/client.py @@ -1,4 +1,4 @@ -# Copyright 2024 Rapyuta Robotics +# Copyright 2025 Rapyuta Robotics # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -53,7 +53,7 @@ class Client: **kwargs: Additional keyword arguments. """ - def __init__(self, config: Configuration = None, **kwargs): + def __init__(self, config: Configuration | None = None, **kwargs): self.config = config or Configuration() timeout = kwargs.get("timeout", 10) self.c = httpx.Client( @@ -93,11 +93,7 @@ def get_auth_token(self, email: str, password: str) -> str: handle_server_errors(result) return result.json()["data"].get("token") - def login( - self, - email: str, - password: str, - ) -> None: + def login(self, email: str, password: str) -> None: """Get the authentication token for the user. Args: @@ -111,7 +107,7 @@ def login( token = self.get_auth_token(email, password) self.config.auth_token = token - def logout(self, token: str = None) -> dict[str, Any]: + def logout(self, token: str | None = None) -> None: """Expire the authentication token. Args: @@ -129,9 +125,8 @@ def logout(self, token: str = None) -> dict[str, Any]: }, ) handle_server_errors(result) - return result.json() - def refresh_token(self, token: str = None, set_token: bool = True) -> str: + def refresh_token(self, token: str | None = None, set_token: bool = True) -> str: """Refresh the authentication token. Args: @@ -172,7 +167,9 @@ def set_project(self, project_guid: str) -> None: self.config.set_project(project_guid) # -----------------Organization---------------- - def get_organization(self, organization_guid: str = None, **kwargs) -> Organization: + def get_organization( + self, organization_guid: str | None = None, **kwargs + ) -> Organization: """Get an organization by its GUID. If organization GUID is provided, the current organization GUID will be @@ -184,6 +181,7 @@ def get_organization(self, organization_guid: str = None, **kwargs) -> Organizat Returns: Organization: Organization details as an Organization object. """ + organization_guid = organization_guid or self.config.organization_guid result = self.c.get( url=f"{self.v2api_host}/v2/organizations/{organization_guid}/", @@ -221,7 +219,34 @@ def update_organization( return Organization(**result.json()) # ---------------------User-------------------- - def get_user(self, **kwargs) -> User: + def list_users( + self, + organization_guid: str | None = None, + guid: str | None = None, + cont: int = 0, + limit: int = 50, + **kwargs, + ) -> UserList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if guid: + parameters["guid"] = guid + + result = self.c.get( + url=f"{self.v2api_host}/v2/users/", + headers=self.config.get_headers( + with_project=False, organization_guid=organization_guid, **kwargs + ), + params=parameters, + ) + + handle_server_errors(result) + + return UserList(**result.json()) + + def get_myself(self, **kwargs) -> User: """Get User details. Returns: @@ -229,7 +254,9 @@ def get_user(self, **kwargs) -> User: """ result = self.c.get( url=f"{self.v2api_host}/v2/users/me/", - headers=self.config.get_headers(with_project=False, **kwargs), + headers=self.config.get_headers( + with_project=False, with_organization=False, **kwargs + ), ) handle_server_errors(result) return User(**result.json()) @@ -257,7 +284,7 @@ def update_user(self, body: User | dict, **kwargs) -> User: return User(**result.json()) # -------------------Project------------------- - def get_project(self, project_guid: str = None, **kwargs) -> Project: + def get_project(self, project_guid: str | None = None, **kwargs) -> Project: """Get a project by its GUID. If no project or organization GUID is provided, @@ -273,11 +300,7 @@ def get_project(self, project_guid: str = None, **kwargs) -> Project: Returns: Project: Project details as a Project object. """ - if project_guid is None: - project_guid = self.config.project_guid - - if not project_guid: - raise ValueError("project_guid is required") + project_guid = project_guid or self.config.project_guid result = self.c.get( url=f"{self.v2api_host}/v2/projects/{project_guid}/", @@ -290,10 +313,10 @@ def list_projects( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, - status: list[str] = None, - organizations: list[str] = None, - name: str = None, + label_selector: list[str] | None = None, + status: list[str] | None = None, + organizations: list[str] | None = None, + name: str | None = None, **kwargs, ) -> ProjectList: """List all projects in an organization. @@ -309,7 +332,7 @@ def list_projects( Dict[str, Any]: List of projects with items validated as Project objects. """ - parameters = { + parameters: dict[str, Any] = { "continue": cont, "limit": limit, } @@ -417,8 +440,8 @@ def list_packages( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, - name: str = None, + label_selector: list[str] | None = None, + name: str | None = None, **kwargs, ) -> PackageList: """List all packages in a project. @@ -504,7 +527,6 @@ def delete_package(self, name: str, version: str, **kwargs) -> None: params={"version": version}, ) handle_server_errors(result) - return None # -------------------Deployment------------------- def list_deployments( @@ -512,15 +534,15 @@ def list_deployments( cont: int = 0, limit: int = 50, dependencies: bool = False, - device_name: str = None, - guids: list[str] = None, - label_selector: list[str] = None, - name: str = None, - names: list[str] = None, - package_name: str = None, - package_version: str = None, - phases: list[str] = None, - regions: list[str] = None, + device_name: str | None = None, + guids: list[str] | None = None, + label_selector: list[str] | None = None, + name: str | None = None, + names: list[str] | None = None, + package_name: str | None = None, + package_version: str | None = None, + phases: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> DeploymentList: """List all deployments in a project. @@ -587,7 +609,7 @@ def create_deployment(self, body: Deployment | dict, **kwargs) -> Deployment: handle_server_errors(result) return Deployment(**result.json()) - def get_deployment(self, name: str, guid: str = None, **kwargs) -> Deployment: + def get_deployment(self, name: str, guid: str | None = None, **kwargs) -> Deployment: """Get a deployment by its name. Returns: @@ -615,7 +637,7 @@ def update_deployment( body = Deployment.model_validate(body) result = self.c.put( - url=f"{self.v2api_host}/v2/deployments/{name}/", + url=f"{self.v2api_host}/v2/deployments/{deployment.metadata.name}/", headers=self.config.get_headers(**kwargs), json=body.model_dump(), ) @@ -634,7 +656,6 @@ def delete_deployment(self, name: str, **kwargs) -> None: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None def get_deployment_graph(self, name: str, **kwargs) -> dict[str, Any]: """Get a deployment graph by its name. [Experimental] @@ -648,10 +669,10 @@ def get_deployment_graph(self, name: str, **kwargs) -> dict[str, Any]: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return result + return result.json() def get_deployment_history( - self, name: str, guid: str = None, **kwargs + self, name: str, guid: str | None = None, **kwargs ) -> dict[str, Any]: """Get a deployment history by its name. @@ -665,17 +686,17 @@ def get_deployment_history( params={"guid": guid}, ) handle_server_errors(result) - return result + return result.json() # -------------------Disks------------------- def list_disks( self, cont: int = 0, - label_selector: list[str] = None, + label_selector: list[str] | None = None, limit: int = 50, - names: list[str] = None, - regions: list[str] = None, - status: list[str] = None, + names: list[str] | None = None, + regions: list[str] | None = None, + status: list[str] | None = None, **kwargs, ) -> DiskList: """List all disks in a project. @@ -756,7 +777,6 @@ def delete_disk(self, name: str, **kwargs) -> None: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None # -------------------Device-------------------------- @@ -783,10 +803,10 @@ def list_staticroutes( self, cont: int = 0, limit: int = 50, - guids: list[str] = None, - label_selector: list[str] = None, - names: list[str] = None, - regions: list[str] = None, + guids: list[str] | None = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> StaticRouteList: """List all static routes in a project. @@ -892,20 +912,19 @@ def delete_staticroute(self, name: str, **kwargs) -> None: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None # -------------------Networks------------------- def list_networks( self, cont: int = 0, limit: int = 50, - device_name: str = None, - label_selector: list[str] = None, - names: list[str] = None, - network_type: str = None, - phases: list[str] = None, - regions: list[str] = None, - status: list[str] = None, + device_name: str | None = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, + network_type: str | None = None, + phases: list[str] | None = None, + regions: list[str] | None = None, + status: list[str] | None = None, **kwargs, ) -> NetworkList: """List all networks in a project. @@ -993,7 +1012,6 @@ def delete_network(self, name: str, **kwargs) -> None: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None # -------------------Secrets------------------- @@ -1001,9 +1019,9 @@ def list_secrets( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, - names: list[str] = None, - regions: list[str] = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> SecretList: """List all secrets in a project. @@ -1019,7 +1037,7 @@ def list_secrets( List of secrets as a dictionary. """ - parameters = { + parameters: dict[str, Any] = { "continue": cont, "limit": limit, } @@ -1111,16 +1129,15 @@ def delete_secret(self, name: str, **kwargs) -> None: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None # -------------------OAuth2 Clients------------------- def list_oauth2_clients( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, - names: list[str] = None, - regions: list[str] = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> dict[str, Any]: """List all OAuth2 clients in a project. @@ -1135,7 +1152,7 @@ def list_oauth2_clients( Returns: List of OAuth2 clients as a dictionary. """ - params = { + params: dict[str, Any] = { "continue": cont, "limit": limit, } @@ -1170,7 +1187,7 @@ def get_oauth2_client(self, client_id: str, **kwargs) -> dict[str, Any]: handle_server_errors(result) return result.json() - def create_oauth2_client(self, body: dict, **kwargs) -> dict[str, Any]: + def create_oauth2_client(self, body: dict[str, Any], **kwargs) -> dict[str, Any]: """Create a new OAuth2 client. Args: @@ -1180,7 +1197,7 @@ def create_oauth2_client(self, body: dict, **kwargs) -> dict[str, Any]: OAuth2 client details as a dictionary. """ result = self.c.post( - url=f"{self.v2api_host}/v2/oauth2clients/", + url=f"{self.v2api_host}/v2/oauth2/clients/", headers=self.config.get_headers(**kwargs), json=body, ) @@ -1188,7 +1205,7 @@ def create_oauth2_client(self, body: dict, **kwargs) -> dict[str, Any]: return result.json() def update_oauth2_client( - self, client_id: str, body: dict, **kwargs + self, client_id: str, body: dict[str, Any], **kwargs ) -> dict[str, Any]: """Update an OAuth2 client by its client_id. @@ -1200,7 +1217,7 @@ def update_oauth2_client( OAuth2 client details as a dictionary. """ result = self.c.put( - url=f"{self.v2api_host}/v2/oauth2clients/{client_id}/", + url=f"{self.v2api_host}/v2/oauth2/clients/{client_id}/", headers=self.config.get_headers(**kwargs), json=body, ) @@ -1208,7 +1225,7 @@ def update_oauth2_client( return result.json() def update_oauth2_client_uris( - self, client_id: str, uris: dict, **kwargs + self, client_id: str, update: OAuth2UpdateURI, **kwargs ) -> dict[str, Any]: """Update OAuth2 client URIs. @@ -1220,9 +1237,9 @@ def update_oauth2_client_uris( OAuth2 client details as a dictionary. """ result = self.c.patch( - url=f"{self.v2api_host}/v2/oauth2clients/{client_id}/uris/", + url=f"{self.v2api_host}/v2/oauth2/clients/{client_id}/uris/", headers=self.config.get_headers(**kwargs), - json=uris, + json=update.model_dump(), ) handle_server_errors(result) return result.json() @@ -1237,11 +1254,10 @@ def delete_oauth2_client(self, client_id: str, **kwargs) -> None: None if successful. """ result = self.c.delete( - url=f"{self.v2api_host}/v2/oauth2clients/{client_id}/", + url=f"{self.v2api_host}/v2/oauth2/clients/{client_id}/", headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None # -------------------Config Trees------------------- @@ -1249,7 +1265,7 @@ def list_configtrees( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, + label_selector: list[str] | None = None, with_project: bool = True, **kwargs, ) -> dict[str, Any]: @@ -1264,7 +1280,7 @@ def list_configtrees( Returns: List of config trees as a dictionary. """ - parameters = { + parameters: dict[str, Any] = { "continue": cont, "limit": limit, } @@ -1279,7 +1295,7 @@ def list_configtrees( return result.json() def create_configtree( - self, body: dict, with_project: bool = True, **kwargs + self, body: dict[str, Any], with_project: bool = True, **kwargs ) -> dict[str, Any]: """Create a new config tree. @@ -1301,10 +1317,10 @@ def create_configtree( def get_configtree( self, name: str, - content_types: list[str] = None, + content_types: list[str] | None = None, include_data: bool = False, - key_prefixes: list[str] = None, - revision: str = None, + key_prefixes: list[str] | None = None, + revision: str | None = None, with_project: bool = True, **kwargs, ) -> dict[str, Any]: @@ -1335,7 +1351,11 @@ def get_configtree( return result.json() def set_configtree_revision( - self, name: str, configtree: dict, project_guid: str = None, **kwargs + self, + name: str, + configtree: dict[str, Any], + project_guid: str | None = None, + **kwargs, ) -> dict[str, Any]: """Set a config tree revision. @@ -1356,7 +1376,7 @@ def set_configtree_revision( return result.json() def update_configtree( - self, name: str, body: dict, with_project: bool = True, **kwargs + self, name: str, body: dict[str, Any], with_project: bool = True, **kwargs ) -> dict[str, Any]: """Update a config tree by its name. @@ -1390,7 +1410,6 @@ def delete_configtree(self, name: str, **kwargs) -> None: headers=self.config.get_headers(**kwargs), ) handle_server_errors(result) - return None def list_revisions( self, @@ -1398,7 +1417,7 @@ def list_revisions( cont: int = 0, limit: int = 50, committed: bool = False, - label_selector: list[str] = None, + label_selector: list[str] | None = None, **kwargs, ) -> dict[str, Any]: """List all revisions of a config tree. @@ -1413,7 +1432,7 @@ def list_revisions( Returns: List of revisions as a dictionary. """ - parameters = { + parameters: dict[str, Any] = { "continue": cont, "limit": limit, "committed": committed, @@ -1429,7 +1448,7 @@ def list_revisions( return result.json() def create_revision( - self, name: str, body: dict, project_guid: str = None, **kwargs + self, name: str, body: dict[str, Any], project_guid: str | None = None, **kwargs ) -> dict[str, Any]: """Create a new revision. @@ -1450,7 +1469,7 @@ def create_revision( return result.json() def put_keys_in_revision( - self, name: str, revision_id: str, config_values: dict, **kwargs + self, name: str, revision_id: str, config_values: dict[str, Any], **kwargs ) -> dict[str, Any]: """Put keys in a revision. @@ -1474,9 +1493,9 @@ def commit_revision( self, tree_name: str, revision_id: str, - author: str = None, - message: str = None, - project_guid: str = None, + author: str | None = None, + message: str | None = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Commit a revision. @@ -1508,7 +1527,7 @@ def get_key_in_revision( tree_name: str, revision_id: str, key: str, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Get a key in a revision. @@ -1534,7 +1553,7 @@ def put_key_in_revision( tree_name: str, revision_id: str, key: str, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Put a key in a revision. @@ -1560,7 +1579,7 @@ def delete_key_in_revision( tree_name: str, revision_id: str, key: str, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> None: """Delete a key in a revision. @@ -1579,15 +1598,14 @@ def delete_key_in_revision( headers=self.config.get_headers(project_guid=project_guid, **kwargs), ) handle_server_errors(result) - return None def rename_key_in_revision( self, tree_name: str, revision_id: str, key: str, - config_key_rename: dict, - project_guid: str = None, + config_key_rename: dict[str, Any], + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Rename a key in a revision. @@ -1702,7 +1720,6 @@ def delete_instance(self, name: str) -> None: headers=self.config.get_headers(), ) handle_server_errors(result) - return None def list_instance_bindings( self, @@ -1791,4 +1808,225 @@ def delete_instance_binding(self, instance_name: str, name: str) -> None: headers=self.config.get_headers(), ) handle_server_errors(result) - return None + + # -------------------Usergroup------------------- + def list_user_groups( + self, + cont: int = 0, + limit: int = 50, + label_selector: list[str] | None = None, + name: str | None = None, + **kwargs, + ) -> UserGroupList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if label_selector: + parameters["labelSelector"] = label_selector + if name: + parameters["name"] = name + + result = self.c.get( + url=f"{self.v2api_host}/v2/usergroups/", + headers=self.config.get_headers(with_project=False, **kwargs), + params=parameters, + ) + + handle_server_errors(response=result) + + return UserGroupList(**result.json()) + + def get_user_group(self, group_name: str, group_guid: str, **kwargs) -> UserGroup: + result = self.c.get( + url=f"{self.v2api_host}/v2/usergroups/{group_name}/", + headers=self.config.get_headers( + with_project=False, with_group=True, group_guid=group_guid, **kwargs + ), + ) + handle_server_errors(result) + + return UserGroup(**result.json()) + + def create_user_group(self, user_group: UserGroup, **kwargs) -> UserGroup: + result = self.c.post( + url=f"{self.v2api_host}/v2/usergroups/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=user_group.model_dump(), + ) + handle_server_errors(result) + + return UserGroup(**result.json()) + + def update_user_group(self, user_group: UserGroup, **kwargs) -> UserGroup: + result = self.c.put( + url=f"{self.v2api_host}/v2/usergroups/{user_group.metadata.name}/", + headers=self.config.get_headers( + with_project=False, + with_group=True, + group_guid=user_group.metadata.guid, + **kwargs, + ), + json=user_group.model_dump(), + ) + handle_server_errors(result) + + return UserGroup(**result.json()) + + def delete_user_group(self, group_name: str, group_guid: str, **kwargs) -> None: + result = self.c.delete( + url=f"{self.v2api_host}/v2/usergroups/{group_name}/", + headers=self.config.get_headers( + with_project=False, with_group=True, group_guid=group_guid, **kwargs + ), + ) + handle_server_errors(result) + + # -------------------Roles------------------- + def list_roles( + self, + cont: int = 0, + limit: int = 50, + label_selector: list[str] | None = None, + name: str | None = None, + **kwargs, + ) -> RoleList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if label_selector: + parameters["labelSelector"] = label_selector + if name: + parameters["name"] = name + + result = self.c.get( + url=f"{self.v2api_host}/v2/roles/", + headers=self.config.get_headers(with_project=False, **kwargs), + params=parameters, + ) + + handle_server_errors(result) + + return RoleList(**result.json()) + + def get_role(self, role_name: str, **kwargs) -> Role: + result = self.c.get( + url=f"{self.v2api_host}/v2/roles/{role_name}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) + + return Role(**result.json()) + + def create_role(self, role: Role, **kwargs) -> Role: + result = self.c.post( + url=f"{self.v2api_host}/v2/roles/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=role.model_dump(), + ) + handle_server_errors(result) + + return Role(**result.json()) + + def update_role(self, role: Role, **kwargs) -> Role: + result = self.c.put( + url=f"{self.v2api_host}/v2/roles/{role.metadata.name}/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=role.model_dump(), + ) + handle_server_errors(result) + + return Role(**result.json()) + + def delete_role(self, role_name: str, **kwargs) -> None: + result = self.c.delete( + url=f"{self.v2api_host}/v2/roles/{role_name}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) + + # -------------------RoleBindings------------------- + def list_role_bindings( + self, + cont: int = 0, + limit: int = 50, + label_selector: list[str] | None = None, + role_names: list[str] | None = None, + subject_guids: list[str] | None = None, + subject_names: list[str] | None = None, + subject_kinds: list[str] | None = None, + domain_guids: list[str] | None = None, + domain_names: list[str] | None = None, + domain_kinds: list[str] | None = None, + guids: list[str] | None = None, + **kwargs, + ) -> RoleBindingList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if label_selector: + parameters["labelSelector"] = label_selector + if role_names: + parameters["roleNames"] = role_names + if subject_guids: + parameters["subjectGUIDS"] = subject_guids + if subject_names: + parameters["subjectNames"] = subject_names + if subject_kinds: + parameters["subjectKinds"] = subject_kinds + if domain_guids: + parameters["domainGUIDS"] = domain_guids + if domain_names: + parameters["domainNames"] = domain_names + if domain_kinds: + parameters["domainKinds"] = domain_kinds + if guids: + parameters["guids"] = guids + + result = self.c.get( + url=f"{self.v2api_host}/v2/role-bindings/", + headers=self.config.get_headers(with_project=False, **kwargs), + params=parameters, + ) + + handle_server_errors(result) + + return RoleBindingList(**result.json()) + + def get_role_binding(self, binding_guid: str, **kwargs) -> RoleBinding: + result = self.c.get( + url=f"{self.v2api_host}/v2/role-bindings/{binding_guid}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) + + return RoleBinding(**result.json()) + + def create_role_binding(self, binding: RoleBinding, **kwargs) -> RoleBinding: + result = self.c.post( + url=f"{self.v2api_host}/v2/role-bindings/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=binding.model_dump(), + ) + handle_server_errors(result) + + return RoleBinding(**result.json()) + + def update_role_binding(self, binding: Role, **kwargs) -> RoleBinding: + result = self.c.put( + url=f"{self.v2api_host}/v2/roles/{binding.metadata.guid}/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=binding.model_dump(), + ) + handle_server_errors(result) + + return RoleBinding(**result.json()) + + def delete_role_binding(self, binding_guid: str, **kwargs) -> None: + result = self.c.delete( + url=f"{self.v2api_host}/v2/role-bindings/{binding_guid}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) diff --git a/rapyuta_io_sdk_v2/config.py b/rapyuta_io_sdk_v2/config.py index b477cad..8cd6c8b 100644 --- a/rapyuta_io_sdk_v2/config.py +++ b/rapyuta_io_sdk_v2/config.py @@ -71,10 +71,12 @@ def from_file(cls, file_path: str = None) -> "Configuration": def get_headers( self, with_organization: bool = True, - organization_guid: str = None, + organization_guid: str | None = None, with_project: bool = True, - project_guid: str = None, - ) -> dict: + project_guid: str | None = None, + with_group: bool = False, + group_guid: str | None = None, + ) -> dict[str, str]: """Get the headers for the configuration. Args: @@ -92,9 +94,12 @@ def get_headers( headers["organizationguid"] = organization_guid project_guid = project_guid or self.project_guid - if with_project and project_guid is not None: + if with_project and project_guid: headers["project"] = project_guid + if with_group and group_guid: + headers["groupguid"] = group_guid + custom_client_request_id = os.getenv("REQUEST_ID") if custom_client_request_id: headers["X-Request-ID"] = custom_client_request_id diff --git a/rapyuta_io_sdk_v2/models/deployment.py b/rapyuta_io_sdk_v2/models/deployment.py index a2e016d..40c7194 100644 --- a/rapyuta_io_sdk_v2/models/deployment.py +++ b/rapyuta_io_sdk_v2/models/deployment.py @@ -7,33 +7,31 @@ """ from typing import Literal + from pydantic import BaseModel, Field, model_validator from rapyuta_io_sdk_v2.models.utils import ( - BaseMetadata, BaseList, - Depends, + BaseMetadata, + BaseObject, + DeploymentDepends, DeploymentPhase, DeploymentStatusType, + DeviceDepends, + DiskDepends, ExecutableStatusType, + NetworkDepends, + PackageDepends, RestartPolicy, Runtime, + StaticRouteDepends, ) -class StringMap(dict[str, str]): - pass - - -# --- Depends Models --- -class PackageDepends(Depends): - kind: Literal["package"] = Field(default="package") - - class DeploymentMetadata(BaseMetadata): """Metadata for Deployment resource.""" - depends: PackageDepends | None = None + depends: PackageDepends class EnvArgsSpec(BaseModel): @@ -44,13 +42,13 @@ class EnvArgsSpec(BaseModel): class DeploymentVolume(BaseModel): """Unified volume spec matching Go DeploymentVolume struct.""" - execName: str | None = None - mountPath: str | None = None - subPath: str | None = None + exec_name: str | None = Field(default=None, serialization_alias="execName") + mount_path: str | None = Field(default=None, serialization_alias="mountPath") + sub_path: str | None = Field(default=None, serialization_alias="subPath") uid: int | None = None gid: int | None = None perm: int | None = None - depends: Depends | None = None + depends: DiskDepends | None = None @model_validator(mode="before") @classmethod @@ -69,22 +67,7 @@ class DeploymentStaticRoute(BaseModel): name: str | None = None url: str | None = None - depends: Depends | None = None - - @model_validator(mode="before") - @classmethod - def handle_empty_depends(cls, data): - """Handle empty depends dictionaries by converting them to None.""" - if isinstance(data, dict) and "depends" in data: - depends = data["depends"] - # If depends is an empty dictionary, set it to None - if isinstance(depends, dict) and not depends: - data["depends"] = None - return data - - -class ManagedServiceSpec(BaseModel): - depends: dict[str, str] | None = None + depends: StaticRouteDepends @model_validator(mode="before") @classmethod @@ -101,8 +84,8 @@ def handle_empty_depends(cls, data): class DeploymentROSNetwork(BaseModel): """ROS Network configuration matching Go DeploymentROSNetwork struct.""" - domainID: int = Field(description="ROS Domain ID") - depends: Depends | None = None + depends: NetworkDepends + domainID: int | None = Field(default=None, description="ROS Domain ID") interface: str | None = Field(default=None, description="Network interface") @model_validator(mode="before") @@ -141,11 +124,11 @@ class DeploymentFeatures(BaseModel): class DeploymentDevice(BaseModel): """Device configuration matching Go DeploymentDevice struct.""" - depends: Depends | None = None + depends: DeviceDepends @model_validator(mode="before") - @classmethod - def handle_empty_depends(cls, data): + @staticmethod + def handle_empty_depends(data): """Handle empty depends dictionaries by converting them to None.""" if isinstance(data, dict) and "depends" in data: depends = data["depends"] @@ -157,7 +140,7 @@ def handle_empty_depends(cls, data): class DeploymentSpec(BaseModel): runtime: Runtime - depends: list[Depends] | None = None + depends: list[DeploymentDepends] | None = None device: DeploymentDevice | None = None restart: RestartPolicy = Field(default="always") envArgs: list[EnvArgsSpec] | None = None @@ -165,7 +148,6 @@ class DeploymentSpec(BaseModel): rosNetworks: list[DeploymentROSNetwork] | None = None features: DeploymentFeatures | None = None staticRoutes: list[DeploymentStaticRoute] | None = None - managedServices: list[ManagedServiceSpec] | None = None @model_validator(mode="after") def validate_runtime_and_volumes(self): @@ -196,12 +178,12 @@ def validate_runtime_and_volumes(self): class ExecutableStatus(BaseModel): - name: str | None = None + name: str status: ExecutableStatusType | None = None - errorCode: str | None = Field(default=None, alias="error_code") + error_code: str | None = None reason: str | None = None - restartCount: int | None = Field(default=None, alias="restart_count") # int32 in Go - exitCode: int | None = Field(default=None, alias="exit_code") # int32 in Go + restart_count: int | None = None + exit_code: int | None = None class DependentDeploymentStatus(BaseModel): @@ -209,7 +191,7 @@ class DependentDeploymentStatus(BaseModel): guid: str | None = None status: DeploymentStatusType | None = None phase: DeploymentPhase | None = None - errorCodes: list[str] | None = Field(default=None, alias="error_codes") + errorCodes: list[str] | None = None class DependentNetworkStatus(BaseModel): @@ -236,18 +218,19 @@ class Dependencies(BaseModel): class DeploymentStatus(BaseModel): phase: DeploymentPhase | None = None status: DeploymentStatusType | None = None - errorCodes: list[str] | None = Field(default=None, alias="error_codes") - executablesStatus: dict[str, ExecutableStatus] | None = Field( - default=None, alias="executables_status" + error_codes: list[str] | None = Field( + default=None, alias="error_codes", serialization_alias="errorCodes" + ) + executables_status: dict[str, ExecutableStatus] | None = Field( + default=None, alias="executables_status", serialization_alias="executablesStatus" ) dependencies: Dependencies | None = None -class Deployment(BaseModel): +class Deployment(BaseObject): """Deployment model.""" - apiVersion: str | None = None - kind: str | None = None + kind: Literal["Deployment"] | None = "Deployment" metadata: DeploymentMetadata spec: DeploymentSpec status: DeploymentStatus | None = None diff --git a/rapyuta_io_sdk_v2/models/disk.py b/rapyuta_io_sdk_v2/models/disk.py index fd1657b..d37d065 100644 --- a/rapyuta_io_sdk_v2/models/disk.py +++ b/rapyuta_io_sdk_v2/models/disk.py @@ -6,15 +6,11 @@ incorrect fields. """ -from typing import Literal -from pydantic import BaseModel, Field, field_validator - -from rapyuta_io_sdk_v2.models.utils import BaseMetadata, BaseList, Runtime +from typing import Any, Literal +from pydantic import BaseModel, Field, field_validator -class DiskBound(BaseModel): - deployment_guid: str | None - deployment_name: str | None +from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, BaseObject, Runtime class DiskSpec(BaseModel): @@ -23,48 +19,48 @@ class DiskSpec(BaseModel): runtime: Runtime = Field( default="cloud", description="Runtime environment for the disk" ) - capacity: int | float | None = Field(default=None, description="Disk capacity in GB") + capacity: int = Field(multiple_of=2, ge=4, le=512) - @field_validator("capacity") - @classmethod - def validate_capacity(cls, v): - """Validate disk capacity against allowed values.""" - if v is not None: - allowed_capacities = [4, 8, 16, 32, 64, 128, 256, 512] - if v not in allowed_capacities: - raise ValueError( - f"Disk capacity must be one of: {allowed_capacities}. Got: {v}" - ) - return v + +class DiskBound(BaseModel): + deployment_guid: str | None + deployment_name: str | None class DiskStatus(BaseModel): status: Literal["Available", "Bound", "Released", "Failed", "Pending"] - capacityUsed: float | None = Field( - default=None, description="Used disk capacity in GB" + capacity_used: float | None = Field( + default=None, + description="Used disk capacity in GB", + serialization_alias="capacityUsed", + ) + capacity_available: float | None = Field( + default=None, + description="Available disk capacity in GB", + serialization_alias="capacityAvailable", ) - capacityAvailable: float | None = Field( - default=None, description="Available disk capacity in GB" + error_code: str | None = Field( + default=None, description="Error code if any", serialization_alias="errorCode" ) - errorCode: str | None = Field(default=None, description="Error code if any") - diskBound: DiskBound | None = Field( - default=None, description="Disk bound information" + disk_bound: DiskBound | None = Field( + default=None, + description="Disk bound information", + serialization_alias="diskBound", ) - @field_validator("diskBound", mode="before") - @classmethod - def normalize_disk_bound(cls, v): + @field_validator("disk_bound", mode="before") + @staticmethod + def normalize_disk_bound(value: Any) -> dict[str, Any] | None: """Convert empty dict to None for diskBound field.""" - if isinstance(v, dict) and not v: + if isinstance(value, dict) and not value: return None - return v + return value -class Disk(BaseModel): +class Disk(BaseObject): """Disk model.""" - apiVersion: str | None = Field(default="apiextensions.rapyuta.io/v1") - kind: str | None = Field(default="Disk") + kind: Literal["Disk"] | None = "Disk" metadata: BaseMetadata = Field(description="Metadata for the Disk resource") spec: DiskSpec = Field(description="Specification for the Disk resource") status: DiskStatus | None = Field(default=None) diff --git a/rapyuta_io_sdk_v2/models/managedservice.py b/rapyuta_io_sdk_v2/models/managedservice.py index a287be6..95099dd 100644 --- a/rapyuta_io_sdk_v2/models/managedservice.py +++ b/rapyuta_io_sdk_v2/models/managedservice.py @@ -1,13 +1,10 @@ """Pydantic models for ManagedService resource.""" from typing import Any, Literal -from pydantic import BaseModel, Field - -from rapyuta_io_sdk_v2.models.utils import BaseMetadata, BaseList, ListMeta +from pydantic import BaseModel, Field -ManagedServiceStatus = Literal["Pending", "Error", "Success", "Deleting", "Unknown"] - +from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, ListMeta # --- ManagedServiceProvider Models --- @@ -15,7 +12,7 @@ class ManagedServiceProvider(BaseModel): """Managed service provider model.""" - name: str | None = Field(default=None, description="Name of the provider") + name: str = Field(description="Name of the provider") class ManagedServiceProviderList(BaseModel): @@ -33,9 +30,7 @@ class ManagedServiceProviderList(BaseModel): class ManagedServiceInstanceSpec(BaseModel): """Specification for ManagedServiceInstance resource.""" - provider: str | None = Field( - default=None, description="The provider for the managed service" - ) + provider: str = Field(description="The provider for the managed service") config: Any = Field( default=None, description="Configuration object for the managed service as JSON" ) @@ -44,13 +39,13 @@ class ManagedServiceInstanceSpec(BaseModel): class ManagedServiceInstanceStatus(BaseModel): """Status for ManagedServiceInstance resource.""" - status: ManagedServiceStatus | None = Field( + status: Literal["Pending", "Error", "Success", "Deleting", "Unknown"] | None = Field( default=None, description="Current status of the managed service" ) error: str | None = Field( default=None, description="Error message if any", alias="errorMessage" ) - provider: Any = Field( + provider: dict[str, Any] | None = Field( default=None, description="Provider-specific status information as JSON" ) @@ -69,7 +64,7 @@ class ManagedServiceInstance(BaseModel): ) -class ManagedServiceInstanceListOption(BaseList): +class ManagedServiceInstanceListOption(BaseList[ManagedServiceInstance]): """List options for ManagedServiceInstance.""" providers: list[str] | None = Field(default=None, description="Filter by providers") @@ -115,9 +110,7 @@ class ManagedServiceBinding(BaseModel): apiVersion: str | None = Field(default=None, description="API version") kind: str | None = Field(default=None, description="Resource kind") metadata: BaseMetadata = Field(description="Resource metadata") - spec: ManagedServiceBindingSpec | None = Field( - default=None, description="Binding specification" - ) + spec: ManagedServiceBindingSpec = Field(description="Binding specification") status: ManagedServiceBindingStatus | None = Field( default=None, description="Binding status" ) diff --git a/rapyuta_io_sdk_v2/models/network.py b/rapyuta_io_sdk_v2/models/network.py index 2c2a1a4..9bbf202 100644 --- a/rapyuta_io_sdk_v2/models/network.py +++ b/rapyuta_io_sdk_v2/models/network.py @@ -7,12 +7,13 @@ """ from typing import Literal + from pydantic import AliasChoices, BaseModel, Field, field_validator from rapyuta_io_sdk_v2.models.utils import ( Architecture, - BaseMetadata, BaseList, + BaseMetadata, RestartPolicy, Runtime, ) diff --git a/rapyuta_io_sdk_v2/models/organization.py b/rapyuta_io_sdk_v2/models/organization.py index fda9634..acdc57b 100644 --- a/rapyuta_io_sdk_v2/models/organization.py +++ b/rapyuta_io_sdk_v2/models/organization.py @@ -1,27 +1,23 @@ -from pydantic import BaseModel, Field from typing import Literal -from .utils import BaseMetadata + +from pydantic import BaseModel + +from .utils import BaseMetadata, BaseObject, Subject # Define allowed roles RoleType = Literal["admin", "viewer"] -class OrganizationUser(BaseModel): - guid: str = Field(default="", description="User GUID") - firstName: str = Field(default="", description="First name of the user") - lastName: str = Field(default="", description="Last name of the user") - emailID: str = Field(default="", description="Email ID of the user") - roleInOrganization: RoleType = Field(description="Role in the organization") +class OrganizationMember(BaseModel): + subject: Subject + roleNames: list[str] class OrganizationSpec(BaseModel): - users: list[OrganizationUser] = Field( - default_factory=list, description="List of users in the organization" - ) + members: list[OrganizationMember] -class Organization(BaseModel): - metadata: BaseMetadata = Field(description="Metadata for the Organization resource") - spec: OrganizationSpec = Field( - description="Specification for the Organization resource" - ) +class Organization(BaseObject): + kind: Literal["Organization"] | None = "Organization" + metadata: BaseMetadata + spec: OrganizationSpec diff --git a/rapyuta_io_sdk_v2/models/package.py b/rapyuta_io_sdk_v2/models/package.py index 8772a25..2d08862 100644 --- a/rapyuta_io_sdk_v2/models/package.py +++ b/rapyuta_io_sdk_v2/models/package.py @@ -7,12 +7,13 @@ """ from typing import Literal + from pydantic import BaseModel, Field, RootModel, field_validator, model_validator from rapyuta_io_sdk_v2.models.utils import ( Architecture, - BaseMetadata, BaseList, + BaseMetadata, RestartPolicy, Runtime, ) diff --git a/rapyuta_io_sdk_v2/models/project.py b/rapyuta_io_sdk_v2/models/project.py index 3685e88..fec14c1 100644 --- a/rapyuta_io_sdk_v2/models/project.py +++ b/rapyuta_io_sdk_v2/models/project.py @@ -6,86 +6,85 @@ incorrect fields. """ -from typing import Literal -from pydantic import BaseModel, Field +from typing import Literal, Self -from rapyuta_io_sdk_v2.models.utils import BaseMetadata, BaseList +from pydantic import BaseModel, Field, model_validator - -class RoleSpec(str): - pass - - -class User(BaseModel): - emailID: str - firstName: str | None = None - lastName: str | None = None - userGUID: str | None = None - role: Literal["admin", "viewer"] | None = Field(default="viewer") +from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, BaseObject, Subject -class UserGroup(BaseModel): - name: str - userGroupGUID: str | None = None - role: Literal["admin", "viewer"] | None = Field(default="viewer") +class ProjectMember(BaseModel): + subject: Subject + roleNames: list[str] class FeaturesVPN(BaseModel): - subnets: list[str] | None = None - enabled: bool | None = None - - -class FeaturesTracing(BaseModel): enabled: bool | None = None + subnets: list[str] | None = None class FeaturesDockerCache(BaseModel): enabled: bool = Field(default=False) - proxyDevice: str | None = None - proxyInterface: str | None = None - registrySecret: str | None = None - registryURL: str | None = None - dataDirectory: str | None = Field(default="/opt/rapyuta/volumes/docker-cache/") + proxy_device: str | None = Field(default=None, serialization_alias="proxyDevice") + proxy_interface: str | None = Field( + default=None, serialization_alias="proxyInterface" + ) + registry_secret: str | None = Field( + default=None, serialization_alias="registrySecret" + ) + registry_url: str | None = Field(default=None, serialization_alias="registryURL") + data_directory: str | None = Field( + default="/opt/rapyuta/volumes/docker-cache/", serialization_alias="dataDirectory" + ) + + @model_validator(mode="after") + def validate_enabled_requires_all_fields(self) -> Self: + if self.enabled: + required_fields = [ + "proxy_device", + "proxy_interface", + "registry_secret", + "registry_url", + ] + missing_fields = [ + field for field in required_fields if getattr(self, field) is None + ] + + if missing_fields: + raise ValueError( + f"Following fields should be present if docker_cache is enabled: {', '.join(missing_fields)}" + ) + + return self class Features(BaseModel): vpn: FeaturesVPN | None = None - tracing: FeaturesTracing | None = None dockerCache: FeaturesDockerCache | None = None class ProjectSpec(BaseModel): - users: list[User] | None = None - userGroups: list[UserGroup] | None = None + members: list[ProjectMember] | None = None features: Features | None = None class ProjectStatus(BaseModel): - status: str | None = None - vpn: str | None = None - tracing: str | None = None - + status: Literal["Pending", "Error", "Success", "Deleting", "Unknown"] + error: str | None = None + vpn: Literal["Success", "Error", "Disabled"] + tracing: Literal["Success", "Error", "Disabled"] -class Metadata(BaseMetadata): - """Metadata for Project resource.""" - - pass - -class Project(BaseModel): +class Project(BaseObject): """Project model.""" - apiVersion: str | None = None - kind: str | None = None - metadata: BaseMetadata | None = None - spec: ProjectSpec | None = None + kind: Literal["Project"] | None = "Project" + metadata: BaseMetadata + spec: ProjectSpec status: ProjectStatus | None = None class ProjectList(BaseList[Project]): - """List of projects using BaseList.""" - - pass """List of Project resources.""" pass diff --git a/rapyuta_io_sdk_v2/models/role.py b/rapyuta_io_sdk_v2/models/role.py new file mode 100644 index 0000000..66e24d4 --- /dev/null +++ b/rapyuta_io_sdk_v2/models/role.py @@ -0,0 +1,26 @@ +from typing import Literal + +from pydantic import BaseModel + +from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, BaseObject + + +class Rule(BaseModel): + resource: str + instances: list[str] | None = None + actions: list[str] | None = None + + +class RoleSpec(BaseModel): + description: str | None = None + rules: list[Rule] | None = None + + +class Role(BaseObject): + kind: Literal["Role"] | None = "Role" + metadata: BaseMetadata + spec: RoleSpec + + +class RoleList(BaseList[Role]): + pass diff --git a/rapyuta_io_sdk_v2/models/rolebinding.py b/rapyuta_io_sdk_v2/models/rolebinding.py new file mode 100644 index 0000000..1aedb7d --- /dev/null +++ b/rapyuta_io_sdk_v2/models/rolebinding.py @@ -0,0 +1,40 @@ +from typing import Literal, Self + +from pydantic import BaseModel, Field, model_validator + +from rapyuta_io_sdk_v2.models.utils import ( + BaseList, + BaseMetadata, + BaseObject, + Domain, + Subject, +) + + +class RoleRef(BaseModel): + kind: Literal["Role"] = "Role" + name: str | None = None + guid: str | None = None + + @model_validator(mode="after") + def ensure_name_or_guid(self) -> Self: + if self.name is None and self.guid is None: + raise ValueError("either 'name' or 'guid' should be specified") + + return self + + +class RoleBindingSpec(BaseModel): + role_ref: RoleRef = Field(serialization_alias="roleRef") + domain: Domain + subject: Subject + + +class RoleBinding(BaseObject): + kind: Literal["RoleBinding"] | None = "RoleBinding" + metadata: BaseMetadata + spec: RoleBindingSpec + + +class RoleBindingList(BaseList[RoleBinding]): + pass diff --git a/rapyuta_io_sdk_v2/models/secret.py b/rapyuta_io_sdk_v2/models/secret.py index 89d7ea4..8931543 100644 --- a/rapyuta_io_sdk_v2/models/secret.py +++ b/rapyuta_io_sdk_v2/models/secret.py @@ -6,9 +6,11 @@ incorrect fields. """ -from pydantic import BaseModel, Field, field_validator +from typing import Literal -from rapyuta_io_sdk_v2.models.utils import BaseMetadata, BaseList +from pydantic import BaseModel, Field + +from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, BaseObject class DockerSpec(BaseModel): @@ -18,36 +20,23 @@ class DockerSpec(BaseModel): default="https://index.docker.io/v1/", description="Docker registry URL" ) username: str = Field(description="Username for docker registry authentication") - password: str | None = Field( - default=None, description="Password for docker registry authentication" - ) + password: str = Field(description="Password for docker registry authentication") email: str = Field(description="Email for docker registry authentication") - @field_validator("registry", "username", "password", "email", mode="after") - @classmethod - def not_empty(cls, v, info): - # Only require password if it's not None - if info.field_name == "password" and v is None: - return v - if not v or (isinstance(v, str) and v.strip() == ""): - raise ValueError(f"{info.field_name} is required and cannot be empty") - return v - class SecretSpec(BaseModel): """Specification for Secret resource.""" - docker: DockerSpec | None = Field( - default=None, description="Docker registry configuration when type is Docker" + docker: DockerSpec = Field( + description="Docker registry configuration when type is Docker" ) -class Secret(BaseModel): +class Secret(BaseObject): """Secret model.""" - apiVersion: str | None = None - kind: str | None = None - metadata: BaseMetadata | None = None + kind: Literal["Secret"] | None = "Secret" + metadata: BaseMetadata spec: SecretSpec = Field(description="Specification for the Secret resource") diff --git a/rapyuta_io_sdk_v2/models/staticroute.py b/rapyuta_io_sdk_v2/models/staticroute.py index 7eff4fd..bfc99b6 100644 --- a/rapyuta_io_sdk_v2/models/staticroute.py +++ b/rapyuta_io_sdk_v2/models/staticroute.py @@ -6,34 +6,36 @@ incorrect fields. """ +import re from typing import Literal + from pydantic import BaseModel, Field, field_validator -from .utils import BaseList, BaseMetadata -import re +from .utils import BaseList, BaseMetadata, BaseObject class StaticRouteSpec(BaseModel): """Specification for StaticRoute resource.""" url: str | None = Field(default=None, description="URL for the static route") - sourceIPRange: list[str] | None = Field( - default=None, description="List of source IP ranges in CIDR notation" + source_ip_range: list[str] | None = Field( + default=None, + description="List of source IP ranges in CIDR notation", + serialization_alias="sourceIPRange", ) - @field_validator("sourceIPRange") - @classmethod - def validate_ip_ranges(cls, v): + @field_validator("source_ip_range") + @staticmethod + def validate_ip_ranges(v: list[str] | None) -> list[str] | None: """Validate IP range format (CIDR notation).""" + ip_pattern = ( + r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(?:/([1-9]|1\d|2\d|3[0-2]))?$" + ) if v is not None: - ip_pattern = ( - r"^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(?:/([1-9]|1\d|2\d|3[0-2]))?$" - ) for ip_range in v: if not re.match(ip_pattern, ip_range): raise ValueError( - f"Invalid IP range format: {ip_range}. " - "Must be a valid CIDR notation (e.g., 192.168.1.0/24)" + f"Invalid IP range format: {ip_range}. Must be a valid CIDR notation (e.g., 192.168.1.0/24)" ) return v @@ -44,15 +46,19 @@ class StaticRouteStatus(BaseModel): status: Literal["Available", "Unavailable"] | None = Field( default=None, description="Status of the static route" ) - packageID: str | None = Field( - default=None, description="Package ID associated with the static route" + package_guid: str | None = Field( + default=None, + description="Package ID associated with the static route", + serialization_alias="packageID", ) - deploymentID: str | None = Field( - default=None, description="Deployment ID associated with the static route" + deployment_guid: str | None = Field( + default=None, + description="Deployment ID associated with the static route", + serialization_alias="deploymentID", ) -class StaticRoute(BaseModel): +class StaticRoute(BaseObject): """ StaticRoute resource model for validation. @@ -61,12 +67,8 @@ class StaticRoute(BaseModel): A named route for the Deployment endpoint. """ - apiVersion: Literal["apiextensions.rapyuta.io/v1", "api.rapyuta.io/v2"] = Field( - default="api.rapyuta.io/v2", - description="API version for the StaticRoute resource", - ) kind: Literal["StaticRoute"] = Field( - description="Resource kind, must be 'StaticRoute'" + default="StaticRoute", description="Resource kind, must be 'StaticRoute'" ) metadata: BaseMetadata = Field(description="Metadata for the StaticRoute resource") spec: StaticRouteSpec | None = Field( diff --git a/rapyuta_io_sdk_v2/models/user.py b/rapyuta_io_sdk_v2/models/user.py index 0213a5e..1523d88 100644 --- a/rapyuta_io_sdk_v2/models/user.py +++ b/rapyuta_io_sdk_v2/models/user.py @@ -12,63 +12,94 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pydantic import BaseModel, Field -from rapyuta_io_sdk_v2.models.utils import BaseMetadata, BaseList +from typing import Literal, Self + +from pydantic import BaseModel, Field, model_validator + +from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, BaseObject class UserOrganization(BaseModel): """User organization model.""" - creator: str | None = None guid: str | None = None name: str | None = None - shortGUID: str | None = Field(None, alias="shortGUID") + creator: str | None = None + short_guid: str | None = Field(serialization_alias="shortGUID") + role_names: list[str] + + @model_validator(mode="after") + def ensure_name_or_guid(self) -> Self: + if self.name is None and self.guid is None: + raise ValueError("either 'name' or 'guid' should be specified") + + return self class UserProject(BaseModel): """User project model.""" - creator: str | None = None guid: str | None = None name: str | None = None - organizationCreatorGUID: str | None = None - organizationGUID: str | None = None + creator: str | None = None + organization_creator_guid: str | None = Field( + serialization_alias="organizationCreatorGUID" + ) + organization_guid: str | None = Field(serialization_alias="organizationGUID") + role_names: list[str] + + @model_validator(mode="after") + def ensure_name_or_guid(self) -> Self: + if self.name is None and self.guid is None: + raise ValueError("either 'name' or 'guid' should be specified") + return self -class UserGroup(BaseModel): + +class UserUserGroup(BaseModel): """User group model.""" - creator: str | None = None guid: str | None = None name: str | None = None - organizationCreatorGUID: str | None = None - organizationGUID: str | None = None + creator: str | None = None + organization_creator_guid: str | None = Field( + serialization_alias="organizationCreatorGUID" + ) + organization_guid: str | None = Field(serialization_alias="organizationGUID") + role_names: list[str] + + @model_validator(mode="after") + def ensure_name_or_guid(self) -> Self: + if self.name is None and self.guid is None: + raise ValueError("either 'name' or 'guid' should be specified") + + return self class UserSpec(BaseModel): """User specification model.""" - emailID: str | None = None - firstName: str | None = None - lastName: str | None = None - organizations: list[UserOrganization] | None = None + first_name: str | None = Field(default=None, serialization_alias="firstName") + last_name: str | None = Field(default=None, serialization_alias="lastName") + email_id: str | None = Field(default=None, serialization_alias="emailID") password: str | None = None + + organizations: list[UserOrganization] | None = None projects: list[UserProject] | None = None - userGroupAdmins: list[UserGroup] | None = None - userGroupsMembers: list[UserGroup] | None = None + user_groups: list[UserUserGroup] | None = Field( + default=None, serialization_alias="userGroups" + ) -class User(BaseModel): +class User(BaseObject): """User model.""" - apiVersion: str | None = None - kind: str | None = None - metadata: BaseMetadata | None = None - spec: UserSpec | None = None + kind: Literal["User"] | None = "User" + metadata: BaseMetadata + spec: UserSpec class UserList(BaseList[User]): """List of users using BaseList.""" pass - pass diff --git a/rapyuta_io_sdk_v2/models/usergroup.py b/rapyuta_io_sdk_v2/models/usergroup.py new file mode 100644 index 0000000..b740a61 --- /dev/null +++ b/rapyuta_io_sdk_v2/models/usergroup.py @@ -0,0 +1,38 @@ +from typing import Literal + +from pydantic import BaseModel, Field + +from rapyuta_io_sdk_v2.models.utils import ( + BaseList, + BaseMetadata, + BaseObject, + Domain, + Subject, +) + + +class UserGroupMember(BaseModel): + subject: Subject + roles: list[str] + + +class UserGroupBinding(BaseModel): + domain: Domain + role: str + + +class UserGroupSpec(BaseModel): + description: str | None = None + members_count: int | None = Field(default=None, serialization_alias="membersCount") + members: list[UserGroupMember] | None = None + roles: list[UserGroupBinding] | None = None + + +class UserGroup(BaseObject): + kind: Literal["UserGroup"] | None = "UserGroup" + metadata: BaseMetadata + spec: UserGroupSpec + + +class UserGroupList(BaseList[UserGroup]): + pass diff --git a/rapyuta_io_sdk_v2/models/utils.py b/rapyuta_io_sdk_v2/models/utils.py index 00f2c3a..fbe8248 100644 --- a/rapyuta_io_sdk_v2/models/utils.py +++ b/rapyuta_io_sdk_v2/models/utils.py @@ -1,13 +1,17 @@ -from pydantic import BaseModel, Field - - -from typing import Generic, Literal, TypeVar +from typing import Generic, Literal, Self, TypeVar +from pydantic import AliasChoices, BaseModel, Field, model_validator # Type variable for generic list items T = TypeVar("T") +class BaseObject(BaseModel): + api_version: Literal["api.rapyuta.io/v2", "apiextensions.rapyuta.io/v1"] = Field( + default="api.rapyuta.io/v2", serialization_alias="apiVersion" + ) + + class BaseMetadata(BaseModel): """Base metadata class containing common fields across all resource types. @@ -89,10 +93,33 @@ class BaseList(BaseModel, Generic[T]): class Depends(BaseModel): - kind: str - nameOrGUID: str # Keep for backward compatibility, but add GUID field - wait: bool | None = None - version: str | None = None + name_or_guid: str = Field(validation_alias=AliasChoices("nameOrGuid", "nameOrGUID")) + + +class PackageDepends(Depends): + kind: Literal["Package", "package"] = "Package" + version: str + + +class DiskDepends(Depends): + kind: Literal["Disk", "disk"] = "Disk" + + +class StaticRouteDepends(Depends): + kind: Literal["StaticRoute", "staticroute"] = "StaticRoute" + + +class NetworkDepends(Depends): + kind: Literal["Network", "network"] = "Network" + + +class DeviceDepends(Depends): + kind: Literal["Device", "device"] = "Device" + + +class DeploymentDepends(Depends): + kind: Literal["Deployment", "deployment"] = "Deployment" + wait: bool = False RestartPolicy = Literal["always", "never", "onfailure"] @@ -111,3 +138,29 @@ class Depends(BaseModel): "Stopped", ] Architecture = Literal["amd64", "arm32v7", "arm64v8"] + + +class Subject(BaseModel): + kind: Literal["User", "UserGroup", "ServiceAccount"] | None = None + name: str | None = None + guid: str | None = None + + @model_validator(mode="after") + def ensure_name_or_guid(self) -> Self: + if self.name is None and self.guid is None: + raise ValueError("either 'name' or 'guid' should be specified") + + return self + + +class Domain(BaseModel): + kind: Literal["UserGroup", "Project", "Organization"] | None = None + name: str | None = None + guid: str | None = None + + @model_validator(mode="after") + def ensure_name_or_guid(self) -> Self: + if self.name is None and self.guid is None: + raise ValueError("either 'name' or 'guid' should be specified") + + return self From cbd23b3bcd4179c9335145d249ff3661342adfc3 Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Tue, 30 Sep 2025 16:17:12 +0530 Subject: [PATCH 2/8] feat: add user and role management methods to AsyncClient and Client, and introduce OAuth2UpdateURI model --- rapyuta_io_sdk_v2/__init__.py | 33 +--- rapyuta_io_sdk_v2/async_client.py | 275 ++++++++++++++++++++++++++- rapyuta_io_sdk_v2/client.py | 16 +- rapyuta_io_sdk_v2/models/__init__.py | 18 -- rapyuta_io_sdk_v2/models/oauth2.py | 8 + 5 files changed, 292 insertions(+), 58 deletions(-) delete mode 100644 rapyuta_io_sdk_v2/models/__init__.py create mode 100644 rapyuta_io_sdk_v2/models/oauth2.py diff --git a/rapyuta_io_sdk_v2/__init__.py b/rapyuta_io_sdk_v2/__init__.py index 23c59d7..26b4a2a 100644 --- a/rapyuta_io_sdk_v2/__init__.py +++ b/rapyuta_io_sdk_v2/__init__.py @@ -3,38 +3,22 @@ from rapyuta_io_sdk_v2.config import Configuration as Configuration from rapyuta_io_sdk_v2.models.deployment import ( Deployment as Deployment, -) -from rapyuta_io_sdk_v2.models.deployment import ( DeploymentList as DeploymentList, ) from rapyuta_io_sdk_v2.models.disk import ( Disk as Disk, -) -from rapyuta_io_sdk_v2.models.disk import ( DiskList as DiskList, ) from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceBinding as ManagedServiceBinding, -) -from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceBindingList as ManagedServiceBindingList, -) -from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceInstance as ManagedServiceInstance, -) -from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceInstanceList as ManagedServiceInstanceList, -) -from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceProvider as ManagedServiceProvider, -) -from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceProviderList as ManagedServiceProviderList, ) from rapyuta_io_sdk_v2.models.network import ( Network as Network, -) -from rapyuta_io_sdk_v2.models.network import ( NetworkList as NetworkList, ) from rapyuta_io_sdk_v2.models.organization import ( @@ -42,52 +26,37 @@ ) from rapyuta_io_sdk_v2.models.package import ( Package as Package, -) -from rapyuta_io_sdk_v2.models.package import ( PackageList as PackageList, ) from rapyuta_io_sdk_v2.models.project import ( Project as Project, -) -from rapyuta_io_sdk_v2.models.project import ( ProjectList as ProjectList, ) from rapyuta_io_sdk_v2.models.role import ( Role as Role, -) -from rapyuta_io_sdk_v2.models.role import ( RoleList as RoleList, ) from rapyuta_io_sdk_v2.models.rolebinding import ( RoleBinding as RoleBinding, -) -from rapyuta_io_sdk_v2.models.rolebinding import ( RoleBindingList as RoleBindingList, ) from rapyuta_io_sdk_v2.models.secret import ( Secret as Secret, -) -from rapyuta_io_sdk_v2.models.secret import ( SecretList as SecretList, ) from rapyuta_io_sdk_v2.models.staticroute import ( StaticRoute as StaticRoute, -) -from rapyuta_io_sdk_v2.models.staticroute import ( StaticRouteList as StaticRouteList, ) from rapyuta_io_sdk_v2.models.user import ( User as User, -) -from rapyuta_io_sdk_v2.models.user import ( UserList as UserList, ) from rapyuta_io_sdk_v2.models.usergroup import ( UserGroup as UserGroup, -) -from rapyuta_io_sdk_v2.models.usergroup import ( UserGroupList as UserGroupList, ) +from rapyuta_io_sdk_v2.models.oauth2 import OAuth2UpdateURI as OAuth2UpdateURI from rapyuta_io_sdk_v2.utils import walk_pages as walk_pages __version__ = "0.3.0" diff --git a/rapyuta_io_sdk_v2/async_client.py b/rapyuta_io_sdk_v2/async_client.py index 68d13eb..3fb71c9 100644 --- a/rapyuta_io_sdk_v2/async_client.py +++ b/rapyuta_io_sdk_v2/async_client.py @@ -18,7 +18,7 @@ import httpx from rapyuta_io_sdk_v2.config import Configuration -from rapyuta_io_sdk_v2.models import ( +from rapyuta_io_sdk_v2 import ( Secret, StaticRoute, Disk, @@ -41,6 +41,13 @@ ManagedServiceProviderList, Organization, Daemon, + UserList, + UserGroupList, + UserGroup, + Role, + RoleBinding, + RoleBindingList, + RoleList, ) from rapyuta_io_sdk_v2.utils import handle_server_errors @@ -230,7 +237,34 @@ async def update_organization( return Organization(**result.json()) # ---------------------User-------------------- - async def get_user(self, **kwargs) -> User: + async def list_users( + self, + organization_guid: str | None = None, + guid: str | None = None, + cont: int = 0, + limit: int = 50, + **kwargs, + ) -> UserList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if guid: + parameters["guid"] = guid + + result = await self.c.get( + url=f"{self.v2api_host}/v2/users/", + headers=self.config.get_headers( + with_project=False, organization_guid=organization_guid, **kwargs + ), + params=parameters, + ) + + handle_server_errors(result) + + return UserList(**result.json()) + + async def get_myself(self, **kwargs) -> User: """Get User details. Returns: @@ -238,11 +272,22 @@ async def get_user(self, **kwargs) -> User: """ result = await self.c.get( url=f"{self.v2api_host}/v2/users/me/", - headers=self.config.get_headers(with_project=False, **kwargs), + headers=self.config.get_headers( + with_project=False, with_organization=False, **kwargs + ), ) handle_server_errors(result) return User(**result.json()) + # Alias for backward compatibility + async def get_user(self, **kwargs) -> User: + """Get User details. (Alias for get_myself) + + Returns: + User: User details as a User object. + """ + return await self.get_myself(**kwargs) + async def update_user(self, body: User | dict, **kwargs) -> User: """Update the user details. @@ -1865,3 +1910,227 @@ async def delete_instance_binding(self, instance_name: str, name: str) -> None: ) handle_server_errors(result) return None + + # -------------------Usergroup------------------- + async def list_user_groups( + self, + cont: int = 0, + limit: int = 50, + label_selector: list[str] | None = None, + name: str | None = None, + **kwargs, + ) -> UserGroupList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if label_selector: + parameters["labelSelector"] = label_selector + if name: + parameters["name"] = name + + result = await self.c.get( + url=f"{self.v2api_host}/v2/usergroups/", + headers=self.config.get_headers(with_project=False, **kwargs), + params=parameters, + ) + + handle_server_errors(response=result) + + return UserGroupList(**result.json()) + + async def get_user_group( + self, group_name: str, group_guid: str, **kwargs + ) -> UserGroup: + result = await self.c.get( + url=f"{self.v2api_host}/v2/usergroups/{group_name}/", + headers=self.config.get_headers( + with_project=False, with_group=True, group_guid=group_guid, **kwargs + ), + ) + handle_server_errors(result) + + return UserGroup(**result.json()) + + async def create_user_group(self, user_group: UserGroup, **kwargs) -> UserGroup: + result = await self.c.post( + url=f"{self.v2api_host}/v2/usergroups/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=user_group.model_dump(), + ) + handle_server_errors(result) + + return UserGroup(**result.json()) + + async def update_user_group(self, user_group: UserGroup, **kwargs) -> UserGroup: + result = await self.c.put( + url=f"{self.v2api_host}/v2/usergroups/{user_group.metadata.name}/", + headers=self.config.get_headers( + with_project=False, + with_group=True, + group_guid=user_group.metadata.guid, + **kwargs, + ), + json=user_group.model_dump(), + ) + handle_server_errors(result) + + return UserGroup(**result.json()) + + async def delete_user_group(self, group_name: str, group_guid: str, **kwargs) -> None: + result = await self.c.delete( + url=f"{self.v2api_host}/v2/usergroups/{group_name}/", + headers=self.config.get_headers( + with_project=False, with_group=True, group_guid=group_guid, **kwargs + ), + ) + handle_server_errors(result) + + # -------------------Roles------------------- + async def list_roles( + self, + cont: int = 0, + limit: int = 50, + label_selector: list[str] | None = None, + name: str | None = None, + **kwargs, + ) -> RoleList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if label_selector: + parameters["labelSelector"] = label_selector + if name: + parameters["name"] = name + + result = await self.c.get( + url=f"{self.v2api_host}/v2/roles/", + headers=self.config.get_headers(with_project=False, **kwargs), + params=parameters, + ) + + handle_server_errors(result) + + return RoleList(**result.json()) + + async def get_role(self, role_name: str, **kwargs) -> Role: + result = await self.c.get( + url=f"{self.v2api_host}/v2/roles/{role_name}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) + + return Role(**result.json()) + + async def create_role(self, role: Role, **kwargs) -> Role: + result = await self.c.post( + url=f"{self.v2api_host}/v2/roles/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=role.model_dump(), + ) + handle_server_errors(result) + + return Role(**result.json()) + + async def update_role(self, role: Role, **kwargs) -> Role: + result = await self.c.put( + url=f"{self.v2api_host}/v2/roles/{role.metadata.name}/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=role.model_dump(), + ) + handle_server_errors(result) + + return Role(**result.json()) + + async def delete_role(self, role_name: str, **kwargs) -> None: + result = await self.c.delete( + url=f"{self.v2api_host}/v2/roles/{role_name}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) + + # -------------------RoleBindings------------------- + async def list_role_bindings( + self, + cont: int = 0, + limit: int = 50, + label_selector: list[str] | None = None, + role_names: list[str] | None = None, + subject_guids: list[str] | None = None, + subject_names: list[str] | None = None, + subject_kinds: list[str] | None = None, + domain_guids: list[str] | None = None, + domain_names: list[str] | None = None, + domain_kinds: list[str] | None = None, + guids: list[str] | None = None, + **kwargs, + ) -> RoleBindingList: + parameters: dict[str, Any] = { + "continue": cont, + "limit": limit, + } + if label_selector: + parameters["labelSelector"] = label_selector + if role_names: + parameters["roleNames"] = role_names + if subject_guids: + parameters["subjectGUIDS"] = subject_guids + if subject_names: + parameters["subjectNames"] = subject_names + if subject_kinds: + parameters["subjectKinds"] = subject_kinds + if domain_guids: + parameters["domainGUIDS"] = domain_guids + if domain_names: + parameters["domainNames"] = domain_names + if domain_kinds: + parameters["domainKinds"] = domain_kinds + if guids: + parameters["guids"] = guids + + result = await self.c.get( + url=f"{self.v2api_host}/v2/role-bindings/", + headers=self.config.get_headers(with_project=False, **kwargs), + params=parameters, + ) + + handle_server_errors(result) + + return RoleBindingList(**result.json()) + + async def get_role_binding(self, binding_guid: str, **kwargs) -> RoleBinding: + result = await self.c.get( + url=f"{self.v2api_host}/v2/role-bindings/{binding_guid}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) + + return RoleBinding(**result.json()) + + async def create_role_binding(self, binding: RoleBinding, **kwargs) -> RoleBinding: + result = await self.c.post( + url=f"{self.v2api_host}/v2/role-bindings/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=binding.model_dump(), + ) + handle_server_errors(result) + + return RoleBinding(**result.json()) + + async def update_role_binding(self, binding: Role, **kwargs) -> RoleBinding: + result = await self.c.put( + url=f"{self.v2api_host}/v2/roles/{binding.metadata.guid}/", + headers=self.config.get_headers(with_project=False, **kwargs), + json=binding.model_dump(), + ) + handle_server_errors(result) + + return RoleBinding(**result.json()) + + async def delete_role_binding(self, binding_guid: str, **kwargs) -> None: + result = await self.c.delete( + url=f"{self.v2api_host}/v2/role-bindings/{binding_guid}/", + headers=self.config.get_headers(with_project=False, **kwargs), + ) + handle_server_errors(result) diff --git a/rapyuta_io_sdk_v2/client.py b/rapyuta_io_sdk_v2/client.py index c56e3c7..1cc61ee 100644 --- a/rapyuta_io_sdk_v2/client.py +++ b/rapyuta_io_sdk_v2/client.py @@ -18,7 +18,7 @@ import httpx from rapyuta_io_sdk_v2.config import Configuration -from rapyuta_io_sdk_v2.models import ( +from rapyuta_io_sdk_v2 import ( Secret, StaticRoute, Disk, @@ -41,6 +41,14 @@ ManagedServiceProviderList, Organization, Daemon, + UserList, + UserGroupList, + UserGroup, + Role, + RoleBinding, + RoleBindingList, + RoleList, + OAuth2UpdateURI, ) from rapyuta_io_sdk_v2.utils import handle_server_errors @@ -625,9 +633,7 @@ def get_deployment(self, name: str, guid: str | None = None, **kwargs) -> Deploy handle_server_errors(result) return Deployment(**result.json()) - def update_deployment( - self, name: str, body: Deployment | dict, **kwargs - ) -> Deployment: + def update_deployment(self, body: Deployment | dict, **kwargs) -> Deployment: """Update a deployment by its name. Returns: @@ -637,7 +643,7 @@ def update_deployment( body = Deployment.model_validate(body) result = self.c.put( - url=f"{self.v2api_host}/v2/deployments/{deployment.metadata.name}/", + url=f"{self.v2api_host}/v2/deployments/{body.metadata.name}/", headers=self.config.get_headers(**kwargs), json=body.model_dump(), ) diff --git a/rapyuta_io_sdk_v2/models/__init__.py b/rapyuta_io_sdk_v2/models/__init__.py deleted file mode 100644 index c328758..0000000 --- a/rapyuta_io_sdk_v2/models/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from .secret import Secret as Secret, SecretList as SecretList -from .staticroute import StaticRoute as StaticRoute, StaticRouteList as StaticRouteList -from .disk import Disk as Disk, DiskList as DiskList -from .package import Package as Package, PackageList as PackageList -from .deployment import Deployment as Deployment, DeploymentList as DeploymentList -from .project import Project as Project, ProjectList as ProjectList -from .network import Network as Network, NetworkList as NetworkList -from .managedservice import ( - ManagedServiceProvider as ManagedServiceProvider, - ManagedServiceProviderList as ManagedServiceProviderList, - ManagedServiceInstance as ManagedServiceInstance, - ManagedServiceInstanceList as ManagedServiceInstanceList, - ManagedServiceBinding as ManagedServiceBinding, - ManagedServiceBindingList as ManagedServiceBindingList, -) -from .user import User as User -from .organization import Organization as Organization -from .daemons import Daemon as Daemon diff --git a/rapyuta_io_sdk_v2/models/oauth2.py b/rapyuta_io_sdk_v2/models/oauth2.py new file mode 100644 index 0000000..f795e96 --- /dev/null +++ b/rapyuta_io_sdk_v2/models/oauth2.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel, Field + + +class OAuth2UpdateURI(BaseModel): + redirect_uris: list[str] | None = Field(serialization_alias="redirectURIs") + post_logout_redirect_uris: list[str] | None = Field( + serialization_alias="postLogoutRedirectURIs" + ) From e64c16b3878c16d95b2106d5315b8bf8054fd3c3 Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Tue, 30 Sep 2025 18:42:58 +0530 Subject: [PATCH 3/8] feat: reorganize model imports for improved structure and clarity --- rapyuta_io_sdk_v2/__init__.py | 28 +------ rapyuta_io_sdk_v2/async_client.py | 113 +++++++++++++++------------ rapyuta_io_sdk_v2/client.py | 49 +++++++----- rapyuta_io_sdk_v2/models/__init__.py | 91 +++++++++++++++++++++ 4 files changed, 185 insertions(+), 96 deletions(-) create mode 100644 rapyuta_io_sdk_v2/models/__init__.py diff --git a/rapyuta_io_sdk_v2/__init__.py b/rapyuta_io_sdk_v2/__init__.py index 26b4a2a..c381781 100644 --- a/rapyuta_io_sdk_v2/__init__.py +++ b/rapyuta_io_sdk_v2/__init__.py @@ -1,62 +1,38 @@ from rapyuta_io_sdk_v2.async_client import AsyncClient as AsyncClient from rapyuta_io_sdk_v2.client import Client as Client from rapyuta_io_sdk_v2.config import Configuration as Configuration -from rapyuta_io_sdk_v2.models.deployment import ( +from rapyuta_io_sdk_v2.models import ( Deployment as Deployment, DeploymentList as DeploymentList, -) -from rapyuta_io_sdk_v2.models.disk import ( Disk as Disk, DiskList as DiskList, -) -from rapyuta_io_sdk_v2.models.managedservice import ( ManagedServiceBinding as ManagedServiceBinding, ManagedServiceBindingList as ManagedServiceBindingList, ManagedServiceInstance as ManagedServiceInstance, ManagedServiceInstanceList as ManagedServiceInstanceList, ManagedServiceProvider as ManagedServiceProvider, ManagedServiceProviderList as ManagedServiceProviderList, -) -from rapyuta_io_sdk_v2.models.network import ( Network as Network, NetworkList as NetworkList, -) -from rapyuta_io_sdk_v2.models.organization import ( + OAuth2UpdateURI as OAuth2UpdateURI, Organization as Organization, -) -from rapyuta_io_sdk_v2.models.package import ( Package as Package, PackageList as PackageList, -) -from rapyuta_io_sdk_v2.models.project import ( Project as Project, ProjectList as ProjectList, -) -from rapyuta_io_sdk_v2.models.role import ( Role as Role, RoleList as RoleList, -) -from rapyuta_io_sdk_v2.models.rolebinding import ( RoleBinding as RoleBinding, RoleBindingList as RoleBindingList, -) -from rapyuta_io_sdk_v2.models.secret import ( Secret as Secret, SecretList as SecretList, -) -from rapyuta_io_sdk_v2.models.staticroute import ( StaticRoute as StaticRoute, StaticRouteList as StaticRouteList, -) -from rapyuta_io_sdk_v2.models.user import ( User as User, UserList as UserList, -) -from rapyuta_io_sdk_v2.models.usergroup import ( UserGroup as UserGroup, UserGroupList as UserGroupList, ) -from rapyuta_io_sdk_v2.models.oauth2 import OAuth2UpdateURI as OAuth2UpdateURI from rapyuta_io_sdk_v2.utils import walk_pages as walk_pages __version__ = "0.3.0" diff --git a/rapyuta_io_sdk_v2/async_client.py b/rapyuta_io_sdk_v2/async_client.py index 3fb71c9..6db89df 100644 --- a/rapyuta_io_sdk_v2/async_client.py +++ b/rapyuta_io_sdk_v2/async_client.py @@ -18,7 +18,7 @@ import httpx from rapyuta_io_sdk_v2.config import Configuration -from rapyuta_io_sdk_v2 import ( +from rapyuta_io_sdk_v2.models import ( Secret, StaticRoute, Disk, @@ -55,10 +55,10 @@ class AsyncClient: """AsyncClient class for the SDK.""" - def __init__(self, config=None, **kwargs): - self.config = config or Configuration() - timeout = kwargs.get("timeout", 10) - self.c = httpx.AsyncClient( + def __init__(self, config: Configuration | None = None, **kwargs: Any): + self.config: Configuration = config or Configuration() + timeout: float = float(kwargs.get("timeout", 10)) + self.c: httpx.AsyncClient = httpx.AsyncClient( timeout=timeout, limits=httpx.Limits( max_keepalive_connections=5, @@ -71,7 +71,7 @@ def __init__(self, config=None, **kwargs): ) }, ) - self.sync_client = httpx.Client( + self.sync_client: httpx.Client = httpx.Client( timeout=timeout, limits=httpx.Limits( max_keepalive_connections=5, @@ -126,7 +126,7 @@ def login( token = self.get_auth_token(email, password) self.config.auth_token = token - def logout(self, token: str = None) -> dict[str, Any]: + def logout(self, token: str | None = None) -> dict[str, Any]: """Expire the authentication token. Args: @@ -146,7 +146,7 @@ def logout(self, token: str = None) -> dict[str, Any]: handle_server_errors(result) return result.json() - def refresh_token(self, token: str = None, set_token: bool = True) -> str: + def refresh_token(self, token: str | None = None, set_token: bool = True) -> str: """Refresh the authentication token. Args: @@ -188,7 +188,7 @@ def set_project(self, project_guid: str) -> None: # -----------------Organization---------------- async def get_organization( - self, organization_guid: str = None, **kwargs + self, organization_guid: str | None = None, **kwargs ) -> Organization: """Get an organization by its GUID. @@ -211,7 +211,10 @@ async def get_organization( return Organization(**result.json()) async def update_organization( - self, body: Organization | dict, organization_guid: str = None, **kwargs + self, + body: Organization | dict[str, Any], + organization_guid: str | None = None, + **kwargs, ) -> Organization: """Update an organization by its GUID. @@ -288,7 +291,7 @@ async def get_user(self, **kwargs) -> User: """ return await self.get_myself(**kwargs) - async def update_user(self, body: User | dict, **kwargs) -> User: + async def update_user(self, body: User | dict[str, Any], **kwargs) -> User: """Update the user details. Args: @@ -315,10 +318,10 @@ async def list_projects( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, - status: list[str] = None, - organizations: list[str] = None, - name: str = None, + label_selector: list[str] | None = None, + status: list[str] | None = None, + organizations: list[str] | None = None, + name: str | None = None, **kwargs, ) -> ProjectList: """List all projects in an organization. @@ -357,7 +360,7 @@ async def list_projects( handle_server_errors(result) return ProjectList(**result.json()) - async def get_project(self, project_guid: str = None, **kwargs) -> Project: + async def get_project(self, project_guid: str | None = None, **kwargs) -> Project: """Get a project by its GUID. If no project or organization GUID is provided, @@ -384,7 +387,7 @@ async def get_project(self, project_guid: str = None, **kwargs) -> Project: handle_server_errors(result) return Project(**result.json()) - async def create_project(self, body: Project | dict, **kwargs) -> Project: + async def create_project(self, body: Project | dict[str, Any], **kwargs) -> Project: """Create a new project. Args: @@ -405,7 +408,7 @@ async def create_project(self, body: Project | dict, **kwargs) -> Project: return Project(**result.json()) async def update_project( - self, body: Project | dict, project_guid: str = None, **kwargs + self, body: Project | dict[str, Any], project_guid: str | None = None, **kwargs ) -> Project: """Update a project by its GUID. @@ -444,7 +447,7 @@ async def delete_project(self, project_guid: str, **kwargs) -> None: return None async def update_project_owner( - self, body: Project | dict, project_guid: str = None, **kwargs + self, body: Project | dict[str, Any], project_guid: str | None = None, **kwargs ) -> dict[str, Any]: """Update the owner of a project by its GUID. @@ -474,7 +477,7 @@ async def list_packages( cont: int = 0, limit: int = 50, label_selector: list[str] = None, - name: str = None, + name: str | None = None, **kwargs, ) -> PackageList: """List all packages in a project. @@ -502,7 +505,7 @@ async def list_packages( handle_server_errors(response=result) return PackageList(**result.json()) - async def create_package(self, body: Package | dict, **kwargs) -> Package: + async def create_package(self, body: Package | dict[str, Any], **kwargs) -> Package: """Create a new package. The Payload is the JSON format of the Package Manifest. @@ -526,7 +529,9 @@ async def create_package(self, body: Package | dict, **kwargs) -> Package: handle_server_errors(result) return Package(**result.json()) - async def get_package(self, name: str, version: str = None, **kwargs) -> Package: + async def get_package( + self, name: str, version: str | None = None, **kwargs + ) -> Package: """Get a package by its name. Args: @@ -568,13 +573,13 @@ async def list_deployments( cont: int = 0, limit: int = 50, dependencies: bool = False, - device_name: str = None, + device_name: str | None = None, guids: list[str] = None, label_selector: list[str] = None, - name: str = None, + name: str | None = None, names: list[str] = None, - package_name: str = None, - package_version: str = None, + package_name: str | None = None, + package_version: str | None = None, phases: list[str] = None, regions: list[str] = None, **kwargs, @@ -624,7 +629,9 @@ async def list_deployments( # -------------------Deployment------------------- - async def create_deployment(self, body: Deployment | dict, **kwargs) -> Deployment: + async def create_deployment( + self, body: Deployment | dict[str, Any], **kwargs + ) -> Deployment: """Create a new deployment. Args: @@ -645,7 +652,9 @@ async def create_deployment(self, body: Deployment | dict, **kwargs) -> Deployme handle_server_errors(result) return Deployment(**result.json()) - async def get_deployment(self, name: str, guid: str = None, **kwargs) -> Deployment: + async def get_deployment( + self, name: str, guid: str | None = None, **kwargs + ) -> Deployment: """Get a deployment by its name. Args: @@ -667,7 +676,7 @@ async def get_deployment(self, name: str, guid: str = None, **kwargs) -> Deploym return Deployment(**result.json()) async def update_deployment( - self, name: str, body: Deployment | dict, **kwargs + self, name: str, body: Deployment | dict[str, Any], **kwargs ) -> Deployment: """Update a deployment by its name. @@ -718,7 +727,7 @@ async def get_deployment_graph(self, name: str, **kwargs) -> dict[str, Any]: return result.json() async def get_deployment_history( - self, name: str, guid: str = None, **kwargs + self, name: str, guid: str | None = None, **kwargs ) -> dict[str, Any]: """Get a deployment history by its name. @@ -795,7 +804,7 @@ async def get_disk(self, name: str, **kwargs) -> Disk: return Disk(**result.json()) - async def create_disk(self, body: Disk | dict, **kwargs) -> Disk: + async def create_disk(self, body: Disk | dict[str, Any], **kwargs) -> Disk: """Create a new disk. Args: @@ -894,7 +903,9 @@ async def list_staticroutes( handle_server_errors(response=result) return StaticRouteList(**result.json()) - async def create_staticroute(self, body: StaticRoute | dict, **kwargs) -> StaticRoute: + async def create_staticroute( + self, body: StaticRoute | dict[str, Any], **kwargs + ) -> StaticRoute: """Create a new static route. Args: @@ -933,7 +944,7 @@ async def get_staticroute(self, name: str, **kwargs) -> StaticRoute: return StaticRoute(**result.json()) async def update_staticroute( - self, name: str, body: StaticRoute | dict, **kwargs + self, name: str, body: StaticRoute | dict[str, Any], **kwargs ) -> StaticRoute: """Update a static route by its name. @@ -978,10 +989,10 @@ async def list_networks( self, cont: int = 0, limit: int = 50, - device_name: str = None, + device_name: str | None = None, label_selector: list[str] = None, names: list[str] = None, - network_type: str = None, + network_type: str | None = None, phases: list[str] = None, regions: list[str] = None, status: list[str] = None, @@ -1023,7 +1034,7 @@ async def list_networks( handle_server_errors(response=result) return NetworkList(**result.json()) - async def create_network(self, body: Network | dict, **kwargs) -> Network: + async def create_network(self, body: Network | dict[str, Any], **kwargs) -> Network: """Create a new network. Args: @@ -1122,7 +1133,7 @@ async def list_secrets( handle_server_errors(response=result) return SecretList(**result.json()) - async def create_secret(self, body: Secret | dict, **kwargs) -> Secret: + async def create_secret(self, body: Secret | dict[str, Any], **kwargs) -> Secret: """Create a new secret. Args: @@ -1161,7 +1172,9 @@ async def get_secret(self, name: str, **kwargs) -> Secret: return Secret(**result.json()) - async def update_secret(self, name: str, body: Secret | dict, **kwargs) -> Secret: + async def update_secret( + self, name: str, body: Secret | dict[str, Any], **kwargs + ) -> Secret: """Update a secret by its name. Args: @@ -1390,7 +1403,7 @@ async def get_configtree( content_types: list[str] = None, include_data: bool = False, key_prefixes: list[str] = None, - revision: str = None, + revision: str | None = None, with_project: bool = True, **kwargs, ) -> dict[str, Any]: @@ -1422,7 +1435,7 @@ async def get_configtree( return result.json() async def set_configtree_revision( - self, name: str, configtree: object, project_guid: str = None, **kwargs + self, name: str, configtree: object, project_guid: str | None = None, **kwargs ) -> dict[str, Any]: """Set a config tree revision. @@ -1524,7 +1537,7 @@ async def list_revisions( return result.json() async def create_revision( - self, name: str, body: dict, project_guid: str = None, **kwargs + self, name: str, body: dict, project_guid: str | None = None, **kwargs ) -> dict[str, Any]: """Create a new revision. @@ -1573,9 +1586,9 @@ async def commit_revision( self, tree_name: str, revision_id: str, - author: str = None, - message: str = None, - project_guid: str = None, + author: str | None = None, + message: str | None = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Commit a revision. @@ -1609,7 +1622,7 @@ async def get_key_in_revision( tree_name: str, revision_id: str, key: str, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Get a key in a revision. @@ -1637,7 +1650,7 @@ async def put_key_in_revision( tree_name: str, revision_id: str, key: str, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Put a key in a revision. @@ -1664,7 +1677,7 @@ async def delete_key_in_revision( tree_name: str, revision_id: str, key: str, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> None: """Delete a key in a revision. @@ -1692,7 +1705,7 @@ async def rename_key_in_revision( revision_id: str, key: str, config_key_rename: dict, - project_guid: str = None, + project_guid: str | None = None, **kwargs, ) -> dict[str, Any]: """Rename a key in a revision. @@ -1785,7 +1798,7 @@ async def get_instance(self, name: str) -> dict[str, Any]: return ManagedServiceInstance(**result.json()) async def create_instance( - self, body: ManagedServiceInstance | dict + self, body: ManagedServiceInstance | dict[str, Any] ) -> ManagedServiceInstance: """Create a new instance. @@ -1850,7 +1863,7 @@ async def list_instance_bindings( return ManagedServiceBindingList(**result.json()) async def create_instance_binding( - self, instance_name: str, body: ManagedServiceBinding | dict + self, instance_name: str, body: ManagedServiceBinding | dict[str, Any] ) -> dict[str, Any]: """Create a new instance binding. diff --git a/rapyuta_io_sdk_v2/client.py b/rapyuta_io_sdk_v2/client.py index 1cc61ee..40e4e0b 100644 --- a/rapyuta_io_sdk_v2/client.py +++ b/rapyuta_io_sdk_v2/client.py @@ -18,7 +18,7 @@ import httpx from rapyuta_io_sdk_v2.config import Configuration -from rapyuta_io_sdk_v2 import ( +from rapyuta_io_sdk_v2.models import ( Secret, StaticRoute, Disk, @@ -61,7 +61,7 @@ class Client: **kwargs: Additional keyword arguments. """ - def __init__(self, config: Configuration | None = None, **kwargs): + def __init__(self, config: Configuration | None = None, **kwargs) -> None: self.config = config or Configuration() timeout = kwargs.get("timeout", 10) self.c = httpx.Client( @@ -201,7 +201,10 @@ def get_organization( return Organization(**result.json()) def update_organization( - self, body: Organization | dict, organization_guid: str = None, **kwargs + self, + body: Organization | dict[str, Any], + organization_guid: str | None = None, + **kwargs, ) -> Organization: """Update an organization by its GUID. @@ -269,7 +272,7 @@ def get_myself(self, **kwargs) -> User: handle_server_errors(result) return User(**result.json()) - def update_user(self, body: User | dict, **kwargs) -> User: + def update_user(self, body: User | dict[str, Any], **kwargs) -> User: """Update the user details. Args: @@ -362,7 +365,7 @@ def list_projects( handle_server_errors(response=result) return ProjectList(**result.json()) - def create_project(self, body: Project | dict, **kwargs) -> Project: + def create_project(self, body: Project | dict[str, Any], **kwargs) -> Project: """Create a new project. Args: @@ -383,7 +386,7 @@ def create_project(self, body: Project | dict, **kwargs) -> Project: return Project(**result.json()) def update_project( - self, body: Project | dict, project_guid: str = None, **kwargs + self, body: Project | dict[str, Any], project_guid: str | None = None, **kwargs ) -> Project: """Update a project by its GUID. @@ -423,7 +426,7 @@ def delete_project(self, project_guid: str, **kwargs) -> None: return None def update_project_owner( - self, body: Project | dict, project_guid: str = None, **kwargs + self, body: Project | dict[str, Any], project_guid: str | None = None, **kwargs ) -> dict[str, Any]: """Update the owner of a project by its GUID. @@ -441,7 +444,7 @@ def update_project_owner( json=body.model_dump(), ) handle_server_errors(result) - return result + return result.json() # -------------------Package------------------- def list_packages( @@ -478,7 +481,7 @@ def list_packages( handle_server_errors(response=result) return PackageList(**result.json()) - def create_package(self, body: Package | dict, **kwargs) -> Package: + def create_package(self, body: Package | dict[str, Any], **kwargs) -> Package: """Create a new package. The Payload is the JSON format of the Package Manifest. @@ -499,7 +502,7 @@ def create_package(self, body: Package | dict, **kwargs) -> Package: handle_server_errors(result) return Package(**result.json()) - def get_package(self, name: str, version: str = None, **kwargs) -> Package: + def get_package(self, name: str, version: str | None = None, **kwargs) -> Package: """Get a package by its name. Args: @@ -596,7 +599,9 @@ def list_deployments( return DeploymentList(**result.json()) - def create_deployment(self, body: Deployment | dict, **kwargs) -> Deployment: + def create_deployment( + self, body: Deployment | dict[str, Any], **kwargs + ) -> Deployment: """Create a new deployment. Args: @@ -633,7 +638,9 @@ def get_deployment(self, name: str, guid: str | None = None, **kwargs) -> Deploy handle_server_errors(result) return Deployment(**result.json()) - def update_deployment(self, body: Deployment | dict, **kwargs) -> Deployment: + def update_deployment( + self, body: Deployment | dict[str, Any], **kwargs + ) -> Deployment: """Update a deployment by its name. Returns: @@ -751,7 +758,7 @@ def get_disk(self, name: str, **kwargs) -> Disk: handle_server_errors(result) return Disk(**result.json()) - def create_disk(self, body: Disk | dict, **kwargs) -> Disk: + def create_disk(self, body: Disk | dict[str, Any], **kwargs) -> Disk: """Create a new disk. Returns: @@ -844,7 +851,9 @@ def list_staticroutes( handle_server_errors(result) return StaticRouteList(**result.json()) - def create_staticroute(self, body: StaticRoute | dict, **kwargs) -> StaticRoute: + def create_staticroute( + self, body: StaticRoute | dict[str, Any], **kwargs + ) -> StaticRoute: """Create a new static route. Returns: @@ -880,7 +889,7 @@ def get_staticroute(self, name: str, **kwargs) -> StaticRoute: return StaticRoute(**result.json()) def update_staticroute( - self, name: str, body: StaticRoute | dict, **kwargs + self, name: str, body: StaticRoute | dict[str, Any], **kwargs ) -> StaticRoute: """Update a static route by its name. @@ -969,7 +978,7 @@ def list_networks( handle_server_errors(result) return NetworkList(**result.json()) - def create_network(self, body: Network | dict, **kwargs) -> Network: + def create_network(self, body: Network | dict[str, Any], **kwargs) -> Network: """Create a new network. Returns: @@ -1063,7 +1072,7 @@ def list_secrets( handle_server_errors(result) return SecretList(**result.json()) - def create_secret(self, body: Secret | dict, **kwargs) -> Secret: + def create_secret(self, body: Secret | dict[str, Any], **kwargs) -> Secret: """Create a new secret. Returns: @@ -1098,7 +1107,7 @@ def get_secret(self, name: str, **kwargs) -> Secret: handle_server_errors(response=result) return Secret(**result.json()) - def update_secret(self, name: str, body: Secret | dict, **kwargs) -> Secret: + def update_secret(self, name: str, body: Secret | dict[str, Any], **kwargs) -> Secret: """Update a secret by its name. Args: @@ -1697,7 +1706,7 @@ def get_instance(self, name: str) -> ManagedServiceInstance: return ManagedServiceInstance(**result.json()) def create_instance( - self, body: ManagedServiceInstance | dict + self, body: ManagedServiceInstance | dict[str, Any] ) -> ManagedServiceInstance: """Create a new instance. @@ -1758,7 +1767,7 @@ def list_instance_bindings( return ManagedServiceBindingList(**result.json()) def create_instance_binding( - self, instance_name: str, body: ManagedServiceBinding | dict + self, instance_name: str, body: ManagedServiceBinding | dict[str, Any] ) -> ManagedServiceBinding: """Create a new instance binding. diff --git a/rapyuta_io_sdk_v2/models/__init__.py b/rapyuta_io_sdk_v2/models/__init__.py new file mode 100644 index 0000000..a0d3cbc --- /dev/null +++ b/rapyuta_io_sdk_v2/models/__init__.py @@ -0,0 +1,91 @@ +""" +Models package for Rapyuta IO SDK v2. + +This module provides flattened imports for all model classes. +""" + +# Deployment models +from rapyuta_io_sdk_v2.models.deployment import ( + Deployment as Deployment, + DeploymentList as DeploymentList, +) + +# Disk models +from rapyuta_io_sdk_v2.models.disk import ( + Disk as Disk, + DiskList as DiskList, +) + +# Managed Service models +from rapyuta_io_sdk_v2.models.managedservice import ( + ManagedServiceBinding as ManagedServiceBinding, + ManagedServiceBindingList as ManagedServiceBindingList, + ManagedServiceInstance as ManagedServiceInstance, + ManagedServiceInstanceList as ManagedServiceInstanceList, + ManagedServiceProvider as ManagedServiceProvider, + ManagedServiceProviderList as ManagedServiceProviderList, +) + +# Network models +from rapyuta_io_sdk_v2.models.network import ( + Network as Network, + NetworkList as NetworkList, +) + +# OAuth2 models +from rapyuta_io_sdk_v2.models.oauth2 import ( + OAuth2UpdateURI as OAuth2UpdateURI, +) + +# Organization models +from rapyuta_io_sdk_v2.models.organization import ( + Organization as Organization, +) + +# Package models +from rapyuta_io_sdk_v2.models.package import ( + Package as Package, + PackageList as PackageList, +) + +# Project models +from rapyuta_io_sdk_v2.models.project import ( + Project as Project, + ProjectList as ProjectList, +) + +# Role models +from rapyuta_io_sdk_v2.models.role import ( + Role as Role, + RoleList as RoleList, +) + +# Role Binding models +from rapyuta_io_sdk_v2.models.rolebinding import ( + RoleBinding as RoleBinding, + RoleBindingList as RoleBindingList, +) + +# Secret models +from rapyuta_io_sdk_v2.models.secret import ( + Secret as Secret, + SecretList as SecretList, +) + +# Static Route models +from rapyuta_io_sdk_v2.models.staticroute import ( + StaticRoute as StaticRoute, + StaticRouteList as StaticRouteList, +) + +# User models +from rapyuta_io_sdk_v2.models.user import ( + User as User, + UserList as UserList, +) + +# User Group models +from rapyuta_io_sdk_v2.models.usergroup import ( + UserGroup as UserGroup, + UserGroupList as UserGroupList, +) From 0b18df843e5f1e32f8aeb1e19061c2799c4295b9 Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Tue, 7 Oct 2025 09:55:23 +0530 Subject: [PATCH 4/8] refactor: update type hints for parameters and return values in AsyncClient and user models --- rapyuta_io_sdk_v2/async_client.py | 50 +++++++++---------- rapyuta_io_sdk_v2/models/user.py | 30 +++++------- tests/data/mock_data.py | 79 +++++++++++++++++-------------- 3 files changed, 82 insertions(+), 77 deletions(-) diff --git a/rapyuta_io_sdk_v2/async_client.py b/rapyuta_io_sdk_v2/async_client.py index 6db89df..9557b95 100644 --- a/rapyuta_io_sdk_v2/async_client.py +++ b/rapyuta_io_sdk_v2/async_client.py @@ -338,7 +338,7 @@ async def list_projects( List of projects as a dictionary. """ - parameters = { + parameters: dict[str, Any] = { "continue": cont, "limit": limit, } @@ -448,7 +448,7 @@ async def delete_project(self, project_guid: str, **kwargs) -> None: async def update_project_owner( self, body: Project | dict[str, Any], project_guid: str | None = None, **kwargs - ) -> dict[str, Any]: + ) -> Project: """Update the owner of a project by its GUID. Args: @@ -476,7 +476,7 @@ async def list_packages( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, + label_selector: list[str] | None = None, name: str | None = None, **kwargs, ) -> PackageList: @@ -574,14 +574,14 @@ async def list_deployments( limit: int = 50, dependencies: bool = False, device_name: str | None = None, - guids: list[str] = None, - label_selector: list[str] = None, + guids: list[str] | None = None, + label_selector: list[str] | None = None, name: str | None = None, - names: list[str] = None, + names: list[str] | None = None, package_name: str | None = None, package_version: str | None = None, - phases: list[str] = None, - regions: list[str] = None, + phases: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> DeploymentList: """List all deployments in a project. @@ -748,11 +748,11 @@ async def get_deployment_history( async def list_disks( self, cont: int = 0, - label_selector: list[str] = None, + label_selector: list[str] | None = None, limit: int = 50, - names: list[str] = None, - regions: list[str] = None, - status: list[str] = None, + names: list[str] | None = None, + regions: list[str] | None = None, + status: list[str] | None = None, **kwargs, ) -> DiskList: """List all disks in a project. @@ -867,10 +867,10 @@ async def list_staticroutes( self, cont: int = 0, limit: int = 50, - guids: list[str] = None, - label_selector: list[str] = None, - names: list[str] = None, - regions: list[str] = None, + guids: list[str] | None = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> StaticRouteList: """List all static routes in a project. @@ -990,12 +990,12 @@ async def list_networks( cont: int = 0, limit: int = 50, device_name: str | None = None, - label_selector: list[str] = None, - names: list[str] = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, network_type: str | None = None, - phases: list[str] = None, - regions: list[str] = None, - status: list[str] = None, + phases: list[str] | None = None, + regions: list[str] | None = None, + status: list[str] | None = None, **kwargs, ) -> NetworkList: """List all networks in a project. @@ -1095,9 +1095,9 @@ async def list_secrets( self, cont: int = 0, limit: int = 50, - label_selector: list[str] = None, - names: list[str] = None, - regions: list[str] = None, + label_selector: list[str] | None = None, + names: list[str] | None = None, + regions: list[str] | None = None, **kwargs, ) -> SecretList: """List all secrets in a project. @@ -1113,7 +1113,7 @@ async def list_secrets( List of secrets as a dictionary. """ - parameters = { + parameters: dict[str, Any] = { "continue": cont, "limit": limit, } diff --git a/rapyuta_io_sdk_v2/models/user.py b/rapyuta_io_sdk_v2/models/user.py index 1523d88..b035845 100644 --- a/rapyuta_io_sdk_v2/models/user.py +++ b/rapyuta_io_sdk_v2/models/user.py @@ -14,7 +14,7 @@ from typing import Literal, Self -from pydantic import BaseModel, Field, model_validator +from pydantic import AliasChoices, BaseModel, Field, model_validator from rapyuta_io_sdk_v2.models.utils import BaseList, BaseMetadata, BaseObject @@ -25,8 +25,8 @@ class UserOrganization(BaseModel): guid: str | None = None name: str | None = None creator: str | None = None - short_guid: str | None = Field(serialization_alias="shortGUID") - role_names: list[str] + short_guid: str | None = Field(alias="shortGUID") + role_names: list[str] | None = Field(alias="roleNames") @model_validator(mode="after") def ensure_name_or_guid(self) -> Self: @@ -43,10 +43,10 @@ class UserProject(BaseModel): name: str | None = None creator: str | None = None organization_creator_guid: str | None = Field( - serialization_alias="organizationCreatorGUID" + validation_alias=AliasChoices("organizationCreator") ) - organization_guid: str | None = Field(serialization_alias="organizationGUID") - role_names: list[str] + organization_guid: str | None = Field(alias="organizationGUID") + role_names: list[str] | None = Field(alias="roleNames") @model_validator(mode="after") def ensure_name_or_guid(self) -> Self: @@ -62,11 +62,9 @@ class UserUserGroup(BaseModel): guid: str | None = None name: str | None = None creator: str | None = None - organization_creator_guid: str | None = Field( - serialization_alias="organizationCreatorGUID" - ) - organization_guid: str | None = Field(serialization_alias="organizationGUID") - role_names: list[str] + organization_creator_guid: str | None = Field(alias="organizationCreatorGUID") + organization_guid: str | None = Field(alias="organizationGUID") + role_names: list[str] = Field(alias="roleNames") @model_validator(mode="after") def ensure_name_or_guid(self) -> Self: @@ -79,16 +77,14 @@ def ensure_name_or_guid(self) -> Self: class UserSpec(BaseModel): """User specification model.""" - first_name: str | None = Field(default=None, serialization_alias="firstName") - last_name: str | None = Field(default=None, serialization_alias="lastName") - email_id: str | None = Field(default=None, serialization_alias="emailID") + first_name: str | None = Field(default=None, alias="firstName") + last_name: str | None = Field(default=None, alias="lastName") + email_id: str | None = Field(default=None, alias="emailID") password: str | None = None organizations: list[UserOrganization] | None = None projects: list[UserProject] | None = None - user_groups: list[UserUserGroup] | None = Field( - default=None, serialization_alias="userGroups" - ) + user_groups: list[UserUserGroup] | None = Field(default=None, alias="userGroups") class User(BaseObject): diff --git a/tests/data/mock_data.py b/tests/data/mock_data.py index bd644ab..dcfc570 100644 --- a/tests/data/mock_data.py +++ b/tests/data/mock_data.py @@ -1,4 +1,7 @@ # Deployment and DeploymentList mocks using pydantic models +from typing import Any + + import pytest @@ -8,7 +11,7 @@ @pytest.fixture -def mock_response_project(): +def mock_response_project() -> dict[str, Any]: return { "kind": "Project", "metadata": {"name": "test-project", "guid": "mock_project_guid"}, @@ -19,7 +22,7 @@ def mock_response_project(): @pytest.fixture -def project_body(): +def project_body() -> dict[str, Any]: return { "apiVersion": "api.rapyuta.io/v2", "kind": "Project", @@ -35,7 +38,7 @@ def project_body(): @pytest.fixture -def project_model_mock(): +def project_model_mock() -> dict[str, Any]: return { "apiVersion": "api.rapyuta.io/v2", "kind": "Project", @@ -52,7 +55,7 @@ def project_model_mock(): @pytest.fixture -def projectlist_model_mock(project_model_mock): +def projectlist_model_mock(project_model_mock) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -65,7 +68,7 @@ def projectlist_model_mock(project_model_mock): @pytest.fixture -def package_body(): +def package_body() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Package", @@ -81,7 +84,7 @@ def package_body(): @pytest.fixture -def cloud_package_model_mock(): +def cloud_package_model_mock() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Package", @@ -131,7 +134,7 @@ def cloud_package_model_mock(): @pytest.fixture -def device_package_model_mock(): +def device_package_model_mock() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Package", @@ -176,7 +179,9 @@ def device_package_model_mock(): @pytest.fixture -def packagelist_model_mock(cloud_package_model_mock, device_package_model_mock): +def packagelist_model_mock( + cloud_package_model_mock, device_package_model_mock +) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -189,7 +194,7 @@ def packagelist_model_mock(cloud_package_model_mock, device_package_model_mock): @pytest.fixture -def deployment_body(): +def deployment_body() -> dict[str, Any]: # Updated to match device_deployment_model_mock keys and values, but only using keys present in deployment_body return { "apiVersion": "apiextensions.rapyuta.io/v1", @@ -217,7 +222,7 @@ def deployment_body(): @pytest.fixture -def cloud_deployment_model_mock(): +def cloud_deployment_model_mock() -> dict[str, Any]: return { "kind": "Deployment", "apiVersion": "api.rapyuta.io/v2", @@ -278,7 +283,7 @@ def cloud_deployment_model_mock(): @pytest.fixture -def device_deployment_model_mock(): +def device_deployment_model_mock() -> dict[str, Any]: return { "kind": "Deployment", "apiVersion": "api.rapyuta.io/v2", @@ -335,7 +340,9 @@ def device_deployment_model_mock(): @pytest.fixture -def deploymentlist_model_mock(cloud_deployment_model_mock, device_deployment_model_mock): +def deploymentlist_model_mock( + cloud_deployment_model_mock, device_deployment_model_mock +) -> dict[str, Any]: return { "metadata": { "continue": 123, @@ -348,7 +355,7 @@ def deploymentlist_model_mock(cloud_deployment_model_mock, device_deployment_mod @pytest.fixture -def disk_body(): +def disk_body() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Disk", @@ -364,7 +371,7 @@ def disk_body(): @pytest.fixture -def disk_model_mock(): +def disk_model_mock() -> dict[str, Any]: return { "kind": "Disk", "apiVersion": "api.rapyuta.io/v2", @@ -392,7 +399,7 @@ def disk_model_mock(): @pytest.fixture -def disklist_model_mock(disk_model_mock): +def disklist_model_mock(disk_model_mock) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -405,7 +412,7 @@ def disklist_model_mock(disk_model_mock): @pytest.fixture -def secret_body(): +def secret_body() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Secret", @@ -426,7 +433,7 @@ def secret_body(): @pytest.fixture -def secret_model_mock(): +def secret_model_mock() -> dict[str, Any]: return { "apiVersion": "api.rapyuta.io/v2", "kind": "Secret", @@ -458,7 +465,7 @@ def secret_model_mock(): @pytest.fixture -def secretlist_model_mock(secret_model_mock): +def secretlist_model_mock(secret_model_mock) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -471,7 +478,7 @@ def secretlist_model_mock(secret_model_mock): @pytest.fixture -def staticroute_body(): +def staticroute_body() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "StaticRoute", @@ -484,7 +491,7 @@ def staticroute_body(): @pytest.fixture -def staticroute_model_mock(): +def staticroute_model_mock() -> dict[str, Any]: return { "kind": "StaticRoute", "apiVersion": "api.rapyuta.io/v2", @@ -514,7 +521,7 @@ def staticroute_model_mock(): @pytest.fixture -def staticroutelist_model_mock(staticroute_model_mock): +def staticroutelist_model_mock(staticroute_model_mock) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -527,7 +534,7 @@ def staticroutelist_model_mock(staticroute_model_mock): @pytest.fixture -def network_body(): +def network_body() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Network", @@ -545,7 +552,7 @@ def network_body(): @pytest.fixture -def network_model_mock(): +def network_model_mock() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "Network", @@ -582,7 +589,7 @@ def network_model_mock(): @pytest.fixture -def networklist_model_mock(network_model_mock): +def networklist_model_mock(network_model_mock) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -595,7 +602,7 @@ def networklist_model_mock(network_model_mock): @pytest.fixture -def configtree_body(): +def configtree_body() -> dict[str, Any]: return { "apiVersion": "apiextensions.rapyuta.io/v1", "kind": "ConfigTree", @@ -610,7 +617,7 @@ def configtree_body(): @pytest.fixture -def mock_response_user(): +def mock_response_user() -> dict[str, Any]: return { "kind": "User", "metadata": {"name": "test user", "guid": "mock_user_guid"}, @@ -657,7 +664,7 @@ def mock_response_user(): @pytest.fixture -def user_body(): +def user_body() -> dict[str, Any]: return { "emailID": "test.user@example.com", "firstName": "Test", @@ -671,7 +678,7 @@ def user_body(): @pytest.fixture -def mock_response_organization(): +def mock_response_organization() -> dict[str, Any]: return { "metadata": { "name": "test-org", @@ -707,7 +714,7 @@ def mock_response_organization(): @pytest.fixture -def organization_body(): +def organization_body() -> dict[str, Any]: return { "metadata": { "name": "test-org", @@ -738,7 +745,7 @@ def organization_body(): @pytest.fixture -def managedservice_model_mock(): +def managedservice_model_mock() -> dict[str, Any]: return { "apiVersion": "api.rapyuta.io/v2", "kind": "ManagedServiceInstance", @@ -757,7 +764,7 @@ def managedservice_model_mock(): @pytest.fixture -def managedservicelist_model_mock(managedservice_model_mock): +def managedservicelist_model_mock(managedservice_model_mock) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -767,7 +774,9 @@ def managedservicelist_model_mock(managedservice_model_mock): @pytest.fixture -def managedservicebindinglist_model_mock(managedservice_binding_model_mock): +def managedservicebindinglist_model_mock( + managedservice_binding_model_mock, +) -> dict[str, Any]: return { "metadata": { "continue": 1, @@ -777,7 +786,7 @@ def managedservicebindinglist_model_mock(managedservice_binding_model_mock): @pytest.fixture -def managedservice_binding_model_mock(): +def managedservice_binding_model_mock() -> dict[str, Any]: return { "apiVersion": "api.rapyuta.io/v2", "kind": "ManagedServiceBinding", @@ -799,7 +808,7 @@ def managedservice_binding_model_mock(): @pytest.fixture -def mock_config(): +def mock_config() -> dict[str, Any]: return { "project_id": "mock_project_guid", "organization_id": "mock_org_guid", @@ -808,7 +817,7 @@ def mock_config(): @pytest.fixture -def config_obj(): +def config_obj() -> Configuration: return Configuration( project_guid="mock_project_guid", organization_guid="mock_org_guid", From 907e5fb9a07c4c51cb3c88ea8c1c1be003374fdf Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Wed, 8 Oct 2025 12:14:52 +0530 Subject: [PATCH 5/8] refactor: update serialization aliases to use 'alias' in model fields for consistency - And also updated get_project method to include project_guid header --- rapyuta_io_sdk_v2/client.py | 4 +++- rapyuta_io_sdk_v2/models/deployment.py | 12 +++++------- rapyuta_io_sdk_v2/models/disk.py | 8 ++++---- rapyuta_io_sdk_v2/models/oauth2.py | 6 ++---- rapyuta_io_sdk_v2/models/project.py | 18 +++++++----------- rapyuta_io_sdk_v2/models/rolebinding.py | 8 ++++++-- rapyuta_io_sdk_v2/models/staticroute.py | 6 +++--- rapyuta_io_sdk_v2/models/usergroup.py | 2 +- rapyuta_io_sdk_v2/models/utils.py | 2 +- rapyuta_io_sdk_v2/utils.py | 3 +++ 10 files changed, 35 insertions(+), 34 deletions(-) diff --git a/rapyuta_io_sdk_v2/client.py b/rapyuta_io_sdk_v2/client.py index 40e4e0b..4302fb7 100644 --- a/rapyuta_io_sdk_v2/client.py +++ b/rapyuta_io_sdk_v2/client.py @@ -315,7 +315,9 @@ def get_project(self, project_guid: str | None = None, **kwargs) -> Project: result = self.c.get( url=f"{self.v2api_host}/v2/projects/{project_guid}/", - headers=self.config.get_headers(with_project=False, **kwargs), + headers=self.config.get_headers( + with_project=True, project_guid=project_guid, **kwargs + ), ) handle_server_errors(result) return Project(**result.json()) diff --git a/rapyuta_io_sdk_v2/models/deployment.py b/rapyuta_io_sdk_v2/models/deployment.py index 40c7194..45e9cea 100644 --- a/rapyuta_io_sdk_v2/models/deployment.py +++ b/rapyuta_io_sdk_v2/models/deployment.py @@ -42,9 +42,9 @@ class EnvArgsSpec(BaseModel): class DeploymentVolume(BaseModel): """Unified volume spec matching Go DeploymentVolume struct.""" - exec_name: str | None = Field(default=None, serialization_alias="execName") - mount_path: str | None = Field(default=None, serialization_alias="mountPath") - sub_path: str | None = Field(default=None, serialization_alias="subPath") + exec_name: str | None = Field(default=None, alias="execName") + mount_path: str | None = Field(default=None, alias="mountPath") + sub_path: str | None = Field(default=None, alias="subPath") uid: int | None = None gid: int | None = None perm: int | None = None @@ -218,11 +218,9 @@ class Dependencies(BaseModel): class DeploymentStatus(BaseModel): phase: DeploymentPhase | None = None status: DeploymentStatusType | None = None - error_codes: list[str] | None = Field( - default=None, alias="error_codes", serialization_alias="errorCodes" - ) + error_codes: list[str] | None = Field(default=None, alias="errorCodes") executables_status: dict[str, ExecutableStatus] | None = Field( - default=None, alias="executables_status", serialization_alias="executablesStatus" + default=None, alias="executablesStatus" ) dependencies: Dependencies | None = None diff --git a/rapyuta_io_sdk_v2/models/disk.py b/rapyuta_io_sdk_v2/models/disk.py index d37d065..e9efdcc 100644 --- a/rapyuta_io_sdk_v2/models/disk.py +++ b/rapyuta_io_sdk_v2/models/disk.py @@ -32,20 +32,20 @@ class DiskStatus(BaseModel): capacity_used: float | None = Field( default=None, description="Used disk capacity in GB", - serialization_alias="capacityUsed", + alias="capacityUsed", ) capacity_available: float | None = Field( default=None, description="Available disk capacity in GB", - serialization_alias="capacityAvailable", + alias="capacityAvailable", ) error_code: str | None = Field( - default=None, description="Error code if any", serialization_alias="errorCode" + default=None, description="Error code if any", alias="errorCode" ) disk_bound: DiskBound | None = Field( default=None, description="Disk bound information", - serialization_alias="diskBound", + alias="diskBound", ) @field_validator("disk_bound", mode="before") diff --git a/rapyuta_io_sdk_v2/models/oauth2.py b/rapyuta_io_sdk_v2/models/oauth2.py index f795e96..5aa9a30 100644 --- a/rapyuta_io_sdk_v2/models/oauth2.py +++ b/rapyuta_io_sdk_v2/models/oauth2.py @@ -2,7 +2,5 @@ class OAuth2UpdateURI(BaseModel): - redirect_uris: list[str] | None = Field(serialization_alias="redirectURIs") - post_logout_redirect_uris: list[str] | None = Field( - serialization_alias="postLogoutRedirectURIs" - ) + redirect_uris: list[str] | None = Field(alias="redirectURIs") + post_logout_redirect_uris: list[str] | None = Field(alias="postLogoutRedirectURIs") diff --git a/rapyuta_io_sdk_v2/models/project.py b/rapyuta_io_sdk_v2/models/project.py index fec14c1..19ba70e 100644 --- a/rapyuta_io_sdk_v2/models/project.py +++ b/rapyuta_io_sdk_v2/models/project.py @@ -25,16 +25,12 @@ class FeaturesVPN(BaseModel): class FeaturesDockerCache(BaseModel): enabled: bool = Field(default=False) - proxy_device: str | None = Field(default=None, serialization_alias="proxyDevice") - proxy_interface: str | None = Field( - default=None, serialization_alias="proxyInterface" - ) - registry_secret: str | None = Field( - default=None, serialization_alias="registrySecret" - ) - registry_url: str | None = Field(default=None, serialization_alias="registryURL") + proxy_device: str | None = Field(default=None, alias="proxyDevice") + proxy_interface: str | None = Field(default=None, alias="proxyInterface") + registry_secret: str | None = Field(default=None, alias="registrySecret") + registry_url: str | None = Field(default=None, alias="registryURL") data_directory: str | None = Field( - default="/opt/rapyuta/volumes/docker-cache/", serialization_alias="dataDirectory" + default="/opt/rapyuta/volumes/docker-cache/", alias="dataDirectory" ) @model_validator(mode="after") @@ -71,8 +67,8 @@ class ProjectSpec(BaseModel): class ProjectStatus(BaseModel): status: Literal["Pending", "Error", "Success", "Deleting", "Unknown"] error: str | None = None - vpn: Literal["Success", "Error", "Disabled"] - tracing: Literal["Success", "Error", "Disabled"] + vpn: Literal["Success", "Error", "Disabled", "Pending"] + tracing: Literal["Success", "Error", "Disabled", "Pending"] class Project(BaseObject): diff --git a/rapyuta_io_sdk_v2/models/rolebinding.py b/rapyuta_io_sdk_v2/models/rolebinding.py index 1aedb7d..6976764 100644 --- a/rapyuta_io_sdk_v2/models/rolebinding.py +++ b/rapyuta_io_sdk_v2/models/rolebinding.py @@ -11,6 +11,10 @@ ) +class RoleBindingMetadata(BaseMetadata): + name: None = Field(default=None, exclude=True) + + class RoleRef(BaseModel): kind: Literal["Role"] = "Role" name: str | None = None @@ -25,14 +29,14 @@ def ensure_name_or_guid(self) -> Self: class RoleBindingSpec(BaseModel): - role_ref: RoleRef = Field(serialization_alias="roleRef") + role_ref: RoleRef = Field(alias="roleRef") domain: Domain subject: Subject class RoleBinding(BaseObject): kind: Literal["RoleBinding"] | None = "RoleBinding" - metadata: BaseMetadata + metadata: RoleBindingMetadata spec: RoleBindingSpec diff --git a/rapyuta_io_sdk_v2/models/staticroute.py b/rapyuta_io_sdk_v2/models/staticroute.py index bfc99b6..eb82def 100644 --- a/rapyuta_io_sdk_v2/models/staticroute.py +++ b/rapyuta_io_sdk_v2/models/staticroute.py @@ -21,7 +21,7 @@ class StaticRouteSpec(BaseModel): source_ip_range: list[str] | None = Field( default=None, description="List of source IP ranges in CIDR notation", - serialization_alias="sourceIPRange", + alias="sourceIPRange", ) @field_validator("source_ip_range") @@ -49,12 +49,12 @@ class StaticRouteStatus(BaseModel): package_guid: str | None = Field( default=None, description="Package ID associated with the static route", - serialization_alias="packageID", + alias="packageID", ) deployment_guid: str | None = Field( default=None, description="Deployment ID associated with the static route", - serialization_alias="deploymentID", + alias="deploymentID", ) diff --git a/rapyuta_io_sdk_v2/models/usergroup.py b/rapyuta_io_sdk_v2/models/usergroup.py index b740a61..3d6b59e 100644 --- a/rapyuta_io_sdk_v2/models/usergroup.py +++ b/rapyuta_io_sdk_v2/models/usergroup.py @@ -23,7 +23,7 @@ class UserGroupBinding(BaseModel): class UserGroupSpec(BaseModel): description: str | None = None - members_count: int | None = Field(default=None, serialization_alias="membersCount") + members_count: int | None = Field(default=None, alias="membersCount") members: list[UserGroupMember] | None = None roles: list[UserGroupBinding] | None = None diff --git a/rapyuta_io_sdk_v2/models/utils.py b/rapyuta_io_sdk_v2/models/utils.py index fbe8248..f6814e6 100644 --- a/rapyuta_io_sdk_v2/models/utils.py +++ b/rapyuta_io_sdk_v2/models/utils.py @@ -8,7 +8,7 @@ class BaseObject(BaseModel): api_version: Literal["api.rapyuta.io/v2", "apiextensions.rapyuta.io/v1"] = Field( - default="api.rapyuta.io/v2", serialization_alias="apiVersion" + default="api.rapyuta.io/v2", alias="apiVersion" ) diff --git a/rapyuta_io_sdk_v2/utils.py b/rapyuta_io_sdk_v2/utils.py index d7ca275..44b9265 100644 --- a/rapyuta_io_sdk_v2/utils.py +++ b/rapyuta_io_sdk_v2/utils.py @@ -34,6 +34,9 @@ def handle_server_errors(response: httpx.Response): except json.JSONDecodeError: err = response.text + # 400 error + if status_code == httpx.codes.BAD_REQUEST: + raise exceptions.MethodNotAllowedError(err) # 404 Not Found if status_code == httpx.codes.NOT_FOUND: raise exceptions.HttpNotFoundError(err) From e6245916168fcee0554e49e3a6be443b06a5d65a Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Fri, 10 Oct 2025 12:21:20 +0530 Subject: [PATCH 6/8] feat: add BulkRoleBindingCreate and BulkRoleBindingUpdate models, and update role and role binding methods to support dict input --- rapyuta_io_sdk_v2/__init__.py | 3 +++ rapyuta_io_sdk_v2/async_client.py | 28 +++++++++++++++++++----- rapyuta_io_sdk_v2/client.py | 29 ++++++++++++++++++++----- rapyuta_io_sdk_v2/models/__init__.py | 4 ++++ rapyuta_io_sdk_v2/models/project.py | 4 ++-- rapyuta_io_sdk_v2/models/rolebinding.py | 9 ++++++++ 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/rapyuta_io_sdk_v2/__init__.py b/rapyuta_io_sdk_v2/__init__.py index c381781..56e2016 100644 --- a/rapyuta_io_sdk_v2/__init__.py +++ b/rapyuta_io_sdk_v2/__init__.py @@ -24,6 +24,8 @@ RoleList as RoleList, RoleBinding as RoleBinding, RoleBindingList as RoleBindingList, + BulkRoleBindingUpdate as BulkRoleBindingUpdate, + BulkRoleBindingCreate as BulkRoleBindingCreate, Secret as Secret, SecretList as SecretList, StaticRoute as StaticRoute, @@ -32,6 +34,7 @@ UserList as UserList, UserGroup as UserGroup, UserGroupList as UserGroupList, + Daemon as Daemon, ) from rapyuta_io_sdk_v2.utils import walk_pages as walk_pages diff --git a/rapyuta_io_sdk_v2/async_client.py b/rapyuta_io_sdk_v2/async_client.py index 9557b95..fe7835a 100644 --- a/rapyuta_io_sdk_v2/async_client.py +++ b/rapyuta_io_sdk_v2/async_client.py @@ -47,6 +47,8 @@ Role, RoleBinding, RoleBindingList, + BulkRoleBindingUpdate, + BulkRoleBindingCreate, RoleList, ) from rapyuta_io_sdk_v2.utils import handle_server_errors @@ -2036,7 +2038,9 @@ async def get_role(self, role_name: str, **kwargs) -> Role: return Role(**result.json()) - async def create_role(self, role: Role, **kwargs) -> Role: + async def create_role(self, role: Role | dict, **kwargs) -> Role: + if isinstance(role, dict): + role = Role.model_validate(role) result = await self.c.post( url=f"{self.v2api_host}/v2/roles/", headers=self.config.get_headers(with_project=False, **kwargs), @@ -2121,7 +2125,11 @@ async def get_role_binding(self, binding_guid: str, **kwargs) -> RoleBinding: return RoleBinding(**result.json()) - async def create_role_binding(self, binding: RoleBinding, **kwargs) -> RoleBinding: + async def create_role_binding( + self, binding: BulkRoleBindingCreate | dict, **kwargs + ) -> RoleBinding: + if isinstance(binding, dict): + binding = BulkRoleBindingCreate.model_validate(binding) result = await self.c.post( url=f"{self.v2api_host}/v2/role-bindings/", headers=self.config.get_headers(with_project=False, **kwargs), @@ -2129,9 +2137,16 @@ async def create_role_binding(self, binding: RoleBinding, **kwargs) -> RoleBindi ) handle_server_errors(result) - return RoleBinding(**result.json()) + try: + return RoleBinding(**result.json()) + except Exception: + return result.json() - async def update_role_binding(self, binding: Role, **kwargs) -> RoleBinding: + async def update_role_binding( + self, binding: BulkRoleBindingUpdate | dict, **kwargs + ) -> RoleBinding: + if isinstance(binding, dict): + binding = BulkRoleBindingUpdate.model_validate(binding) result = await self.c.put( url=f"{self.v2api_host}/v2/roles/{binding.metadata.guid}/", headers=self.config.get_headers(with_project=False, **kwargs), @@ -2139,7 +2154,10 @@ async def update_role_binding(self, binding: Role, **kwargs) -> RoleBinding: ) handle_server_errors(result) - return RoleBinding(**result.json()) + try: + return RoleBinding(**result.json()) + except Exception: + return result.json() async def delete_role_binding(self, binding_guid: str, **kwargs) -> None: result = await self.c.delete( diff --git a/rapyuta_io_sdk_v2/client.py b/rapyuta_io_sdk_v2/client.py index 4302fb7..3bdb73e 100644 --- a/rapyuta_io_sdk_v2/client.py +++ b/rapyuta_io_sdk_v2/client.py @@ -47,6 +47,8 @@ Role, RoleBinding, RoleBindingList, + BulkRoleBindingCreate, + BulkRoleBindingUpdate, RoleList, OAuth2UpdateURI, ) @@ -1936,7 +1938,9 @@ def get_role(self, role_name: str, **kwargs) -> Role: return Role(**result.json()) - def create_role(self, role: Role, **kwargs) -> Role: + def create_role(self, role: Role | dict, **kwargs) -> Role: + if isinstance(role, dict): + role = Role.model_validate(role) result = self.c.post( url=f"{self.v2api_host}/v2/roles/", headers=self.config.get_headers(with_project=False, **kwargs), @@ -2021,17 +2025,27 @@ def get_role_binding(self, binding_guid: str, **kwargs) -> RoleBinding: return RoleBinding(**result.json()) - def create_role_binding(self, binding: RoleBinding, **kwargs) -> RoleBinding: + def create_role_binding( + self, binding: BulkRoleBindingCreate | dict, **kwargs + ) -> RoleBinding: + if isinstance(binding, dict): + binding = BulkRoleBindingCreate.model_validate(binding) result = self.c.post( url=f"{self.v2api_host}/v2/role-bindings/", headers=self.config.get_headers(with_project=False, **kwargs), json=binding.model_dump(), ) handle_server_errors(result) + try: + return RoleBinding(**result.json()) + except Exception: + return result.json() - return RoleBinding(**result.json()) - - def update_role_binding(self, binding: Role, **kwargs) -> RoleBinding: + def update_role_binding( + self, binding: BulkRoleBindingUpdate | dict, **kwargs + ) -> RoleBinding: + if isinstance(binding, dict): + binding = BulkRoleBindingUpdate.model_validate(binding) result = self.c.put( url=f"{self.v2api_host}/v2/roles/{binding.metadata.guid}/", headers=self.config.get_headers(with_project=False, **kwargs), @@ -2039,7 +2053,10 @@ def update_role_binding(self, binding: Role, **kwargs) -> RoleBinding: ) handle_server_errors(result) - return RoleBinding(**result.json()) + try: + return RoleBinding(**result.json()) + except Exception: + return result.json() def delete_role_binding(self, binding_guid: str, **kwargs) -> None: result = self.c.delete( diff --git a/rapyuta_io_sdk_v2/models/__init__.py b/rapyuta_io_sdk_v2/models/__init__.py index a0d3cbc..4f37023 100644 --- a/rapyuta_io_sdk_v2/models/__init__.py +++ b/rapyuta_io_sdk_v2/models/__init__.py @@ -64,6 +64,8 @@ from rapyuta_io_sdk_v2.models.rolebinding import ( RoleBinding as RoleBinding, RoleBindingList as RoleBindingList, + BulkRoleBindingCreate as BulkRoleBindingCreate, + BulkRoleBindingUpdate as BulkRoleBindingUpdate, ) # Secret models @@ -89,3 +91,5 @@ UserGroup as UserGroup, UserGroupList as UserGroupList, ) + +from rapyuta_io_sdk_v2.models.daemons import Daemon as Daemon diff --git a/rapyuta_io_sdk_v2/models/project.py b/rapyuta_io_sdk_v2/models/project.py index 19ba70e..38879e6 100644 --- a/rapyuta_io_sdk_v2/models/project.py +++ b/rapyuta_io_sdk_v2/models/project.py @@ -67,8 +67,8 @@ class ProjectSpec(BaseModel): class ProjectStatus(BaseModel): status: Literal["Pending", "Error", "Success", "Deleting", "Unknown"] error: str | None = None - vpn: Literal["Success", "Error", "Disabled", "Pending"] - tracing: Literal["Success", "Error", "Disabled", "Pending"] + vpn: Literal["Success", "Error", "Disabled", "Pending"] | None = None + tracing: Literal["Success", "Error", "Disabled", "Pending"] | None = None class Project(BaseObject): diff --git a/rapyuta_io_sdk_v2/models/rolebinding.py b/rapyuta_io_sdk_v2/models/rolebinding.py index 6976764..403e0ee 100644 --- a/rapyuta_io_sdk_v2/models/rolebinding.py +++ b/rapyuta_io_sdk_v2/models/rolebinding.py @@ -40,5 +40,14 @@ class RoleBinding(BaseObject): spec: RoleBindingSpec +class BulkRoleBindingUpdate(BaseModel): + new_bindings: list[RoleBinding] = Field(alias="newBindings") + old_bindings: list[RoleBinding] = Field(alias="oldBindings") + + +class BulkRoleBindingCreate(BaseModel): + bindings: list[RoleBinding] + + class RoleBindingList(BaseList[RoleBinding]): pass From d45d447f2d62d0d30a4329b5748ad641cd76faae Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Mon, 13 Oct 2025 14:57:40 +0530 Subject: [PATCH 7/8] feat: add SecretCreate model and update create_secret methods to use it, enhancing secret management --- rapyuta_io_sdk_v2/__init__.py | 1 + rapyuta_io_sdk_v2/async_client.py | 5 ++- rapyuta_io_sdk_v2/client.py | 55 +++++++++++++++------------- rapyuta_io_sdk_v2/models/__init__.py | 1 + rapyuta_io_sdk_v2/models/project.py | 11 +++++- rapyuta_io_sdk_v2/models/secret.py | 14 +++++-- rapyuta_io_sdk_v2/utils.py | 2 + 7 files changed, 57 insertions(+), 32 deletions(-) diff --git a/rapyuta_io_sdk_v2/__init__.py b/rapyuta_io_sdk_v2/__init__.py index 56e2016..0a3fdeb 100644 --- a/rapyuta_io_sdk_v2/__init__.py +++ b/rapyuta_io_sdk_v2/__init__.py @@ -28,6 +28,7 @@ BulkRoleBindingCreate as BulkRoleBindingCreate, Secret as Secret, SecretList as SecretList, + SecretCreate as SecretCreate, StaticRoute as StaticRoute, StaticRouteList as StaticRouteList, User as User, diff --git a/rapyuta_io_sdk_v2/async_client.py b/rapyuta_io_sdk_v2/async_client.py index fe7835a..e3ebefe 100644 --- a/rapyuta_io_sdk_v2/async_client.py +++ b/rapyuta_io_sdk_v2/async_client.py @@ -20,6 +20,7 @@ from rapyuta_io_sdk_v2.config import Configuration from rapyuta_io_sdk_v2.models import ( Secret, + SecretCreate, StaticRoute, Disk, Deployment, @@ -1135,7 +1136,7 @@ async def list_secrets( handle_server_errors(response=result) return SecretList(**result.json()) - async def create_secret(self, body: Secret | dict[str, Any], **kwargs) -> Secret: + async def create_secret(self, body: SecretCreate | dict[str, Any], **kwargs) -> Secret: """Create a new secret. Args: @@ -1145,7 +1146,7 @@ async def create_secret(self, body: Secret | dict[str, Any], **kwargs) -> Secret Secret: Secret details as a Secret object. """ if isinstance(body, dict): - body = Secret.model_validate(body) + body = SecretCreate.model_validate(body) result = await self.c.post( url=f"{self.v2api_host}/v2/secrets/", diff --git a/rapyuta_io_sdk_v2/client.py b/rapyuta_io_sdk_v2/client.py index 3bdb73e..998d451 100644 --- a/rapyuta_io_sdk_v2/client.py +++ b/rapyuta_io_sdk_v2/client.py @@ -20,6 +20,7 @@ from rapyuta_io_sdk_v2.config import Configuration from rapyuta_io_sdk_v2.models import ( Secret, + SecretCreate, StaticRoute, Disk, Deployment, @@ -226,7 +227,7 @@ def update_organization( headers=self.config.get_headers( with_project=False, organization_guid=organization_guid, **kwargs ), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return Organization(**result.json()) @@ -291,7 +292,7 @@ def update_user(self, body: User | dict[str, Any], **kwargs) -> User: headers=self.config.get_headers( with_project=False, with_organization=False, **kwargs ), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return User(**result.json()) @@ -384,7 +385,7 @@ def create_project(self, body: Project | dict[str, Any], **kwargs) -> Project: result = self.c.post( url=f"{self.v2api_host}/v2/projects/", headers=self.config.get_headers(with_project=False, **kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return Project(**result.json()) @@ -407,7 +408,7 @@ def update_project( result = self.c.put( url=f"{self.v2api_host}/v2/projects/{project_guid}/", headers=self.config.get_headers(with_project=False, **kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return Project(**result.json()) @@ -424,7 +425,9 @@ def delete_project(self, project_guid: str, **kwargs) -> None: result = self.c.delete( url=f"{self.v2api_host}/v2/projects/{project_guid}/", - headers=self.config.get_headers(with_project=False, **kwargs), + headers=self.config.get_headers( + with_project=True, project_guid=project_guid, **kwargs + ), ) handle_server_errors(result) return None @@ -445,7 +448,7 @@ def update_project_owner( result = self.c.put( url=f"{self.v2api_host}/v2/projects/{project_guid}/owner/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return result.json() @@ -500,7 +503,7 @@ def create_package(self, body: Package | dict[str, Any], **kwargs) -> Package: result = self.c.post( url=f"{self.v2api_host}/v2/packages/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) @@ -620,7 +623,7 @@ def create_deployment( result = self.c.post( url=f"{self.v2api_host}/v2/deployments/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) @@ -656,7 +659,7 @@ def update_deployment( result = self.c.put( url=f"{self.v2api_host}/v2/deployments/{body.metadata.name}/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return Deployment(**result.json()) @@ -774,7 +777,7 @@ def create_disk(self, body: Disk | dict[str, Any], **kwargs) -> Disk: result = self.c.post( url=f"{self.v2api_host}/v2/disks/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return Disk(**result.json()) @@ -869,7 +872,7 @@ def create_staticroute( result = self.c.post( url=f"{self.v2api_host}/v2/staticroutes/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) @@ -910,7 +913,7 @@ def update_staticroute( result = self.c.put( url=f"{self.v2api_host}/v2/staticroutes/{name}/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) @@ -994,7 +997,7 @@ def create_network(self, body: Network | dict[str, Any], **kwargs) -> Network: result = self.c.post( url=f"{self.v2api_host}/v2/networks/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return Network(**result.json()) @@ -1076,19 +1079,19 @@ def list_secrets( handle_server_errors(result) return SecretList(**result.json()) - def create_secret(self, body: Secret | dict[str, Any], **kwargs) -> Secret: + def create_secret(self, body: SecretCreate | dict[str, Any], **kwargs) -> Secret: """Create a new secret. Returns: Secret: Secret details. """ if isinstance(body, dict): - body = Secret.model_validate(body) + body = SecretCreate.model_validate(body) result = self.c.post( url=f"{self.v2api_host}/v2/secrets/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) @@ -1127,7 +1130,7 @@ def update_secret(self, name: str, body: Secret | dict[str, Any], **kwargs) -> S result = self.c.put( url=f"{self.v2api_host}/v2/secrets/{name}/", headers=self.config.get_headers(**kwargs), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(response=result) @@ -1258,7 +1261,7 @@ def update_oauth2_client_uris( result = self.c.patch( url=f"{self.v2api_host}/v2/oauth2/clients/{client_id}/uris/", headers=self.config.get_headers(**kwargs), - json=update.model_dump(), + json=update.model_dump(by_alias=True), ) handle_server_errors(result) return result.json() @@ -1723,7 +1726,7 @@ def create_instance( result = self.c.post( url=f"{self.v2api_host}/v2/managedservices/", headers=self.config.get_headers(), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return ManagedServiceInstance(**result.json()) @@ -1788,7 +1791,7 @@ def create_instance_binding( result = self.c.post( url=f"{self.v2api_host}/v2/managedservices/{instance_name}/bindings/", headers=self.config.get_headers(), - json=body.model_dump(), + json=body.model_dump(by_alias=True), ) handle_server_errors(result) return ManagedServiceBinding(**result.json()) @@ -1871,7 +1874,7 @@ def create_user_group(self, user_group: UserGroup, **kwargs) -> UserGroup: result = self.c.post( url=f"{self.v2api_host}/v2/usergroups/", headers=self.config.get_headers(with_project=False, **kwargs), - json=user_group.model_dump(), + json=user_group.model_dump(by_alias=True), ) handle_server_errors(result) @@ -1886,7 +1889,7 @@ def update_user_group(self, user_group: UserGroup, **kwargs) -> UserGroup: group_guid=user_group.metadata.guid, **kwargs, ), - json=user_group.model_dump(), + json=user_group.model_dump(by_alias=True), ) handle_server_errors(result) @@ -1944,7 +1947,7 @@ def create_role(self, role: Role | dict, **kwargs) -> Role: result = self.c.post( url=f"{self.v2api_host}/v2/roles/", headers=self.config.get_headers(with_project=False, **kwargs), - json=role.model_dump(), + json=role.model_dump(by_alias=True), ) handle_server_errors(result) @@ -1954,7 +1957,7 @@ def update_role(self, role: Role, **kwargs) -> Role: result = self.c.put( url=f"{self.v2api_host}/v2/roles/{role.metadata.name}/", headers=self.config.get_headers(with_project=False, **kwargs), - json=role.model_dump(), + json=role.model_dump(by_alias=True), ) handle_server_errors(result) @@ -2033,7 +2036,7 @@ def create_role_binding( result = self.c.post( url=f"{self.v2api_host}/v2/role-bindings/", headers=self.config.get_headers(with_project=False, **kwargs), - json=binding.model_dump(), + json=binding.model_dump(by_alias=True), ) handle_server_errors(result) try: @@ -2049,7 +2052,7 @@ def update_role_binding( result = self.c.put( url=f"{self.v2api_host}/v2/roles/{binding.metadata.guid}/", headers=self.config.get_headers(with_project=False, **kwargs), - json=binding.model_dump(), + json=binding.model_dump(by_alias=True), ) handle_server_errors(result) diff --git a/rapyuta_io_sdk_v2/models/__init__.py b/rapyuta_io_sdk_v2/models/__init__.py index 4f37023..b69625b 100644 --- a/rapyuta_io_sdk_v2/models/__init__.py +++ b/rapyuta_io_sdk_v2/models/__init__.py @@ -72,6 +72,7 @@ from rapyuta_io_sdk_v2.models.secret import ( Secret as Secret, SecretList as SecretList, + SecretCreate as SecretCreate ) # Static Route models diff --git a/rapyuta_io_sdk_v2/models/project.py b/rapyuta_io_sdk_v2/models/project.py index 38879e6..27672c2 100644 --- a/rapyuta_io_sdk_v2/models/project.py +++ b/rapyuta_io_sdk_v2/models/project.py @@ -57,6 +57,15 @@ def validate_enabled_requires_all_fields(self) -> Self: class Features(BaseModel): vpn: FeaturesVPN | None = None dockerCache: FeaturesDockerCache | None = None + +class ProjectMetadata(BaseMetadata): + @model_validator(mode="after") + def validate_project_name(self): + if not (3 <= len(self.name) <= 63): + raise ValueError("Project name length must be between 3 and 63 characters.") + if self.name.startswith("project-"): + raise ValueError('Project name should not start with the prefix "project-".') + return self class ProjectSpec(BaseModel): @@ -75,7 +84,7 @@ class Project(BaseObject): """Project model.""" kind: Literal["Project"] | None = "Project" - metadata: BaseMetadata + metadata: ProjectMetadata spec: ProjectSpec status: ProjectStatus | None = None diff --git a/rapyuta_io_sdk_v2/models/secret.py b/rapyuta_io_sdk_v2/models/secret.py index 8931543..505dbf8 100644 --- a/rapyuta_io_sdk_v2/models/secret.py +++ b/rapyuta_io_sdk_v2/models/secret.py @@ -14,14 +14,16 @@ class DockerSpec(BaseModel): - """Docker registry configuration for secrets.""" - registry: str = Field( default="https://index.docker.io/v1/", description="Docker registry URL" ) username: str = Field(description="Username for docker registry authentication") - password: str = Field(description="Password for docker registry authentication") email: str = Field(description="Email for docker registry authentication") + +class DockerSpecCreate(DockerSpec): + password: str = Field(description="Password for docker registry authentication") + + class SecretSpec(BaseModel): @@ -31,6 +33,9 @@ class SecretSpec(BaseModel): description="Docker registry configuration when type is Docker" ) +class SecretSpecCreate(BaseModel): + docker: DockerSpecCreate + class Secret(BaseObject): """Secret model.""" @@ -39,6 +44,9 @@ class Secret(BaseObject): metadata: BaseMetadata spec: SecretSpec = Field(description="Specification for the Secret resource") +class SecretCreate(Secret): + spec: SecretSpecCreate + class SecretList(BaseList[Secret]): """List of secrets using BaseList.""" diff --git a/rapyuta_io_sdk_v2/utils.py b/rapyuta_io_sdk_v2/utils.py index 44b9265..7f20149 100644 --- a/rapyuta_io_sdk_v2/utils.py +++ b/rapyuta_io_sdk_v2/utils.py @@ -37,6 +37,8 @@ def handle_server_errors(response: httpx.Response): # 400 error if status_code == httpx.codes.BAD_REQUEST: raise exceptions.MethodNotAllowedError(err) + if status_code == httpx.codes.FORBIDDEN: + raise exceptions.MethodNotAllowedError(err) # 404 Not Found if status_code == httpx.codes.NOT_FOUND: raise exceptions.HttpNotFoundError(err) From 24589513458d7cb6a85dd90f734ced03493bb166 Mon Sep 17 00:00:00 2001 From: guptadev21 Date: Mon, 13 Oct 2025 14:58:35 +0530 Subject: [PATCH 8/8] chore: uv sync upgrade --- uv.lock | 305 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 176 insertions(+), 129 deletions(-) diff --git a/uv.lock b/uv.lock index 60c527c..e3eb293 100644 --- a/uv.lock +++ b/uv.lock @@ -49,11 +49,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.10.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, ] [[package]] @@ -284,11 +284,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.11" 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, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -329,7 +329,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.11.9" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -337,96 +337,119 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/da/b8a7ee04378a53f6fefefc0c5e05570a3ebfdfa0523a878bcd3b475683ee/pydantic-2.12.0.tar.gz", hash = "sha256:c1a077e6270dbfb37bfd8b498b3981e2bb18f68103720e51fa6c306a5a9af563", size = 814760, upload-time = "2025-10-07T15:58:03.467Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9d/d5c855424e2e5b6b626fbc6ec514d8e655a600377ce283008b115abb7445/pydantic-2.12.0-py3-none-any.whl", hash = "sha256:f6a1da352d42790537e95e83a8bdfb91c7efbae63ffd0b86fa823899e807116f", size = 459730, upload-time = "2025-10-07T15:58:01.576Z" }, ] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.1" 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, upload-time = "2025-04-23T18:33:52.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" }, - { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" }, - { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" }, - { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" }, - { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" }, - { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" }, - { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" }, - { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" }, - { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" }, - { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" }, - { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" }, - { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" }, - { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" }, - { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" }, - { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" }, - { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" }, - { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" }, - { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" }, - { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" }, - { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" }, - { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" }, - { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" }, - { 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, upload-time = "2025-04-23T18:31:53.175Z" }, - { 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, upload-time = "2025-04-23T18:31:54.79Z" }, - { 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, upload-time = "2025-04-23T18:31:57.393Z" }, - { 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, upload-time = "2025-04-23T18:31:59.065Z" }, - { 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, upload-time = "2025-04-23T18:32:00.78Z" }, - { 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, upload-time = "2025-04-23T18:32:02.418Z" }, - { 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, upload-time = "2025-04-23T18:32:04.152Z" }, - { 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, upload-time = "2025-04-23T18:32:06.129Z" }, - { 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, upload-time = "2025-04-23T18:32:08.178Z" }, - { 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, upload-time = "2025-04-23T18:32:10.242Z" }, - { 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, upload-time = "2025-04-23T18:32:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, - { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, - { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, - { 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, upload-time = "2025-04-23T18:32:20.188Z" }, - { 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, upload-time = "2025-04-23T18:32:22.354Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, - { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" }, - { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" }, - { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" }, - { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" }, - { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" }, - { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" }, - { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" }, - { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7d/14/12b4a0d2b0b10d8e1d9a24ad94e7bbb43335eaf29c0c4e57860e8a30734a/pydantic_core-2.41.1.tar.gz", hash = "sha256:1ad375859a6d8c356b7704ec0f547a58e82ee80bb41baa811ad710e124bc8f2f", size = 454870, upload-time = "2025-10-07T10:50:45.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2c/a5c4640dc7132540109f67fe83b566fbc7512ccf2a068cfa22a243df70c7/pydantic_core-2.41.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e63036298322e9aea1c8b7c0a6c1204d615dbf6ec0668ce5b83ff27f07404a61", size = 2113814, upload-time = "2025-10-06T21:09:50.892Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e7/a8694c3454a57842095d69c7a4ab3cf81c3c7b590f052738eabfdfc2e234/pydantic_core-2.41.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:241299ca91fc77ef64f11ed909d2d9220a01834e8e6f8de61275c4dd16b7c936", size = 1916660, upload-time = "2025-10-06T21:09:52.783Z" }, + { url = "https://files.pythonhosted.org/packages/9c/58/29f12e65b19c1877a0269eb4f23c5d2267eded6120a7d6762501ab843dc9/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab7e594a2a5c24ab8013a7dc8cfe5f2260e80e490685814122081705c2cf2b0", size = 1975071, upload-time = "2025-10-06T21:09:54.009Z" }, + { url = "https://files.pythonhosted.org/packages/98/26/4e677f2b7ec3fbdd10be6b586a82a814c8ebe3e474024c8df2d4260e564e/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b054ef1a78519cb934b58e9c90c09e93b837c935dcd907b891f2b265b129eb6e", size = 2067271, upload-time = "2025-10-06T21:09:55.175Z" }, + { url = "https://files.pythonhosted.org/packages/29/50/50614bd906089904d7ca1be3b9ecf08c00a327143d48f1decfdc21b3c302/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2ab7d10d0ab2ed6da54c757233eb0f48ebfb4f86e9b88ccecb3f92bbd61a538", size = 2253207, upload-time = "2025-10-06T21:09:56.709Z" }, + { url = "https://files.pythonhosted.org/packages/ea/58/b1e640b4ca559273cca7c28e0fe8891d5d8e9a600f5ab4882670ec107549/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2757606b7948bb853a27e4040820306eaa0ccb9e8f9f8a0fa40cb674e170f350", size = 2375052, upload-time = "2025-10-06T21:09:57.97Z" }, + { url = "https://files.pythonhosted.org/packages/53/25/cd47df3bfb24350e03835f0950288d1054f1cc9a8023401dabe6d4ff2834/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec0e75eb61f606bad0a32f2be87507087514e26e8c73db6cbdb8371ccd27917", size = 2076834, upload-time = "2025-10-06T21:09:59.58Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b4/71b2c77e5df527fbbc1a03e72c3fd96c44cd10d4241a81befef8c12b9fc4/pydantic_core-2.41.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0234236514f44a5bf552105cfe2543a12f48203397d9d0f866affa569345a5b5", size = 2195374, upload-time = "2025-10-06T21:10:01.18Z" }, + { url = "https://files.pythonhosted.org/packages/aa/08/4b8a50733005865efde284fec45da75fe16a258f706e16323c5ace4004eb/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1b974e41adfbb4ebb0f65fc4ca951347b17463d60893ba7d5f7b9bb087c83897", size = 2156060, upload-time = "2025-10-06T21:10:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/83/c3/1037cb603ef2130c210150a51b1710d86825b5c28df54a55750099f91196/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:248dafb3204136113c383e91a4d815269f51562b6659b756cf3df14eefc7d0bb", size = 2331640, upload-time = "2025-10-06T21:10:04.39Z" }, + { url = "https://files.pythonhosted.org/packages/56/4c/52d111869610e6b1a46e1f1035abcdc94d0655587e39104433a290e9f377/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:678f9d76a91d6bcedd7568bbf6beb77ae8447f85d1aeebaab7e2f0829cfc3a13", size = 2329844, upload-time = "2025-10-06T21:10:05.68Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/4b435f0b52ab543967761aca66b84ad3f0026e491e57de47693d15d0a8db/pydantic_core-2.41.1-cp310-cp310-win32.whl", hash = "sha256:dff5bee1d21ee58277900692a641925d2dddfde65182c972569b1a276d2ac8fb", size = 1991289, upload-time = "2025-10-06T21:10:07.199Z" }, + { url = "https://files.pythonhosted.org/packages/88/52/31b4deafc1d3cb96d0e7c0af70f0dc05454982d135d07f5117e6336153e8/pydantic_core-2.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:5042da12e5d97d215f91567110fdfa2e2595a25f17c19b9ff024f31c34f9b53e", size = 2027747, upload-time = "2025-10-06T21:10:08.503Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a9/ec440f02e57beabdfd804725ef1e38ac1ba00c49854d298447562e119513/pydantic_core-2.41.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4f276a6134fe1fc1daa692642a3eaa2b7b858599c49a7610816388f5e37566a1", size = 2111456, upload-time = "2025-10-06T21:10:09.824Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f9/6bc15bacfd8dcfc073a1820a564516d9c12a435a9a332d4cbbfd48828ddd/pydantic_core-2.41.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07588570a805296ece009c59d9a679dc08fab72fb337365afb4f3a14cfbfc176", size = 1915012, upload-time = "2025-10-06T21:10:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/38/8a/d9edcdcdfe80bade17bed424284427c08bea892aaec11438fa52eaeaf79c/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28527e4b53400cd60ffbd9812ccb2b5135d042129716d71afd7e45bf42b855c0", size = 1973762, upload-time = "2025-10-06T21:10:13.154Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b3/ff225c6d49fba4279de04677c1c876fc3dc6562fd0c53e9bfd66f58c51a8/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46a1c935c9228bad738c8a41de06478770927baedf581d172494ab36a6b96575", size = 2065386, upload-time = "2025-10-06T21:10:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/47/ba/183e8c0be4321314af3fd1ae6bfc7eafdd7a49bdea5da81c56044a207316/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:447ddf56e2b7d28d200d3e9eafa936fe40485744b5a824b67039937580b3cb20", size = 2252317, upload-time = "2025-10-06T21:10:15.719Z" }, + { url = "https://files.pythonhosted.org/packages/57/c5/aab61e94fd02f45c65f1f8c9ec38bb3b33fbf001a1837c74870e97462572/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63892ead40c1160ac860b5debcc95c95c5a0035e543a8b5a4eac70dd22e995f4", size = 2373405, upload-time = "2025-10-06T21:10:17.017Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4f/3aaa3bd1ea420a15acc42d7d3ccb3b0bbc5444ae2f9dbc1959f8173e16b8/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4a9543ca355e6df8fbe9c83e9faab707701e9103ae857ecb40f1c0cf8b0e94d", size = 2073794, upload-time = "2025-10-06T21:10:18.383Z" }, + { url = "https://files.pythonhosted.org/packages/58/bd/e3975cdebe03ec080ef881648de316c73f2a6be95c14fc4efb2f7bdd0d41/pydantic_core-2.41.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2611bdb694116c31e551ed82e20e39a90bea9b7ad9e54aaf2d045ad621aa7a1", size = 2194430, upload-time = "2025-10-06T21:10:19.638Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/6b7e7217f147d3b3105b57fb1caec3c4f667581affdfaab6d1d277e1f749/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fecc130893a9b5f7bfe230be1bb8c61fe66a19db8ab704f808cb25a82aad0bc9", size = 2154611, upload-time = "2025-10-06T21:10:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/fe/7b/239c2fe76bd8b7eef9ae2140d737368a3c6fea4fd27f8f6b4cde6baa3ce9/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:1e2df5f8344c99b6ea5219f00fdc8950b8e6f2c422fbc1cc122ec8641fac85a1", size = 2329809, upload-time = "2025-10-06T21:10:22.678Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/77a821a67ff0786f2f14856d6bd1348992f695ee90136a145d7a445c1ff6/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:35291331e9d8ed94c257bab6be1cb3a380b5eee570a2784bffc055e18040a2ea", size = 2327907, upload-time = "2025-10-06T21:10:24.447Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9a/b54512bb9df7f64c586b369328c30481229b70ca6a5fcbb90b715e15facf/pydantic_core-2.41.1-cp311-cp311-win32.whl", hash = "sha256:2876a095292668d753f1a868c4a57c4ac9f6acbd8edda8debe4218d5848cf42f", size = 1989964, upload-time = "2025-10-06T21:10:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/9d/72/63c9a4f1a5c950e65dd522d7dd67f167681f9d4f6ece3b80085a0329f08f/pydantic_core-2.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:b92d6c628e9a338846a28dfe3fcdc1a3279388624597898b105e078cdfc59298", size = 2025158, upload-time = "2025-10-06T21:10:27.522Z" }, + { url = "https://files.pythonhosted.org/packages/d8/16/4e2706184209f61b50c231529257c12eb6bd9eb36e99ea1272e4815d2200/pydantic_core-2.41.1-cp311-cp311-win_arm64.whl", hash = "sha256:7d82ae99409eb69d507a89835488fb657faa03ff9968a9379567b0d2e2e56bc5", size = 1972297, upload-time = "2025-10-06T21:10:28.814Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bc/5f520319ee1c9e25010412fac4154a72e0a40d0a19eb00281b1f200c0947/pydantic_core-2.41.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:db2f82c0ccbce8f021ad304ce35cbe02aa2f95f215cac388eed542b03b4d5eb4", size = 2099300, upload-time = "2025-10-06T21:10:30.463Z" }, + { url = "https://files.pythonhosted.org/packages/31/14/010cd64c5c3814fb6064786837ec12604be0dd46df3327cf8474e38abbbd/pydantic_core-2.41.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47694a31c710ced9205d5f1e7e8af3ca57cbb8a503d98cb9e33e27c97a501601", size = 1910179, upload-time = "2025-10-06T21:10:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/8e/2e/23fc2a8a93efad52df302fdade0a60f471ecc0c7aac889801ac24b4c07d6/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e9decce94daf47baf9e9d392f5f2557e783085f7c5e522011545d9d6858e00", size = 1957225, upload-time = "2025-10-06T21:10:33.11Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/6db08b2725b2432b9390844852e11d320281e5cea8a859c52c68001975fa/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab0adafdf2b89c8b84f847780a119437a0931eca469f7b44d356f2b426dd9741", size = 2053315, upload-time = "2025-10-06T21:10:34.87Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/4de44600f2d4514b44f3f3aeeda2e14931214b6b5bf52479339e801ce748/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5da98cc81873f39fd56882e1569c4677940fbc12bce6213fad1ead784192d7c8", size = 2224298, upload-time = "2025-10-06T21:10:36.233Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ae/dbe51187a7f35fc21b283c5250571a94e36373eb557c1cba9f29a9806dcf/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:209910e88afb01fd0fd403947b809ba8dba0e08a095e1f703294fda0a8fdca51", size = 2351797, upload-time = "2025-10-06T21:10:37.601Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a7/975585147457c2e9fb951c7c8dab56deeb6aa313f3aa72c2fc0df3f74a49/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365109d1165d78d98e33c5bfd815a9b5d7d070f578caefaabcc5771825b4ecb5", size = 2074921, upload-time = "2025-10-06T21:10:38.927Z" }, + { url = "https://files.pythonhosted.org/packages/62/37/ea94d1d0c01dec1b7d236c7cec9103baab0021f42500975de3d42522104b/pydantic_core-2.41.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:706abf21e60a2857acdb09502bc853ee5bce732955e7b723b10311114f033115", size = 2187767, upload-time = "2025-10-06T21:10:40.651Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fe/694cf9fdd3a777a618c3afd210dba7b414cb8a72b1bd29b199c2e5765fee/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bf0bd5417acf7f6a7ec3b53f2109f587be176cb35f9cf016da87e6017437a72d", size = 2136062, upload-time = "2025-10-06T21:10:42.09Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/174aeabd89916fbd2988cc37b81a59e1186e952afd2a7ed92018c22f31ca/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2e71b1c6ceb9c78424ae9f63a07292fb769fb890a4e7efca5554c47f33a60ea5", size = 2317819, upload-time = "2025-10-06T21:10:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/65/e8/e9aecafaebf53fc456314f72886068725d6fba66f11b013532dc21259343/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80745b9770b4a38c25015b517451c817799bfb9d6499b0d13d8227ec941cb513", size = 2312267, upload-time = "2025-10-06T21:10:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/35/2f/1c2e71d2a052f9bb2f2df5a6a05464a0eb800f9e8d9dd800202fe31219e1/pydantic_core-2.41.1-cp312-cp312-win32.whl", hash = "sha256:83b64d70520e7890453f1aa21d66fda44e7b35f1cfea95adf7b4289a51e2b479", size = 1990927, upload-time = "2025-10-06T21:10:46.738Z" }, + { url = "https://files.pythonhosted.org/packages/b1/78/562998301ff2588b9c6dcc5cb21f52fa919d6e1decc75a35055feb973594/pydantic_core-2.41.1-cp312-cp312-win_amd64.whl", hash = "sha256:377defd66ee2003748ee93c52bcef2d14fde48fe28a0b156f88c3dbf9bc49a50", size = 2034703, upload-time = "2025-10-06T21:10:48.524Z" }, + { url = "https://files.pythonhosted.org/packages/b2/53/d95699ce5a5cdb44bb470bd818b848b9beadf51459fd4ea06667e8ede862/pydantic_core-2.41.1-cp312-cp312-win_arm64.whl", hash = "sha256:c95caff279d49c1d6cdfe2996e6c2ad712571d3b9caaa209a404426c326c4bde", size = 1972719, upload-time = "2025-10-06T21:10:50.256Z" }, + { url = "https://files.pythonhosted.org/packages/27/8a/6d54198536a90a37807d31a156642aae7a8e1263ed9fe6fc6245defe9332/pydantic_core-2.41.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70e790fce5f05204ef4403159857bfcd587779da78627b0babb3654f75361ebf", size = 2105825, upload-time = "2025-10-06T21:10:51.719Z" }, + { url = "https://files.pythonhosted.org/packages/4f/2e/4784fd7b22ac9c8439db25bf98ffed6853d01e7e560a346e8af821776ccc/pydantic_core-2.41.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cebf1ca35f10930612d60bd0f78adfacee824c30a880e3534ba02c207cceceb", size = 1910126, upload-time = "2025-10-06T21:10:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/f3/92/31eb0748059ba5bd0aa708fb4bab9fcb211461ddcf9e90702a6542f22d0d/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:170406a37a5bc82c22c3274616bf6f17cc7df9c4a0a0a50449e559cb755db669", size = 1961472, upload-time = "2025-10-06T21:10:55.754Z" }, + { url = "https://files.pythonhosted.org/packages/ab/91/946527792275b5c4c7dde4cfa3e81241bf6900e9fee74fb1ba43e0c0f1ab/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12d4257fc9187a0ccd41b8b327d6a4e57281ab75e11dda66a9148ef2e1fb712f", size = 2063230, upload-time = "2025-10-06T21:10:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/31/5d/a35c5d7b414e5c0749f1d9f0d159ee2ef4bab313f499692896b918014ee3/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a75a33b4db105dd1c8d57839e17ee12db8d5ad18209e792fa325dbb4baeb00f4", size = 2229469, upload-time = "2025-10-06T21:10:59.409Z" }, + { url = "https://files.pythonhosted.org/packages/21/4d/8713737c689afa57ecfefe38db78259d4484c97aa494979e6a9d19662584/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08a589f850803a74e0fcb16a72081cafb0d72a3cdda500106942b07e76b7bf62", size = 2347986, upload-time = "2025-10-06T21:11:00.847Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/929f9a3a5ed5cda767081494bacd32f783e707a690ce6eeb5e0730ec4986/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a97939d6ea44763c456bd8a617ceada2c9b96bb5b8ab3dfa0d0827df7619014", size = 2072216, upload-time = "2025-10-06T21:11:02.43Z" }, + { url = "https://files.pythonhosted.org/packages/26/55/a33f459d4f9cc8786d9db42795dbecc84fa724b290d7d71ddc3d7155d46a/pydantic_core-2.41.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae423c65c556f09569524b80ffd11babff61f33055ef9773d7c9fabc11ed8d", size = 2193047, upload-time = "2025-10-06T21:11:03.787Z" }, + { url = "https://files.pythonhosted.org/packages/77/af/d5c6959f8b089f2185760a2779079e3c2c411bfc70ea6111f58367851629/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:4dc703015fbf8764d6a8001c327a87f1823b7328d40b47ce6000c65918ad2b4f", size = 2140613, upload-time = "2025-10-06T21:11:05.607Z" }, + { url = "https://files.pythonhosted.org/packages/58/e5/2c19bd2a14bffe7fabcf00efbfbd3ac430aaec5271b504a938ff019ac7be/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:968e4ffdfd35698a5fe659e5e44c508b53664870a8e61c8f9d24d3d145d30257", size = 2327641, upload-time = "2025-10-06T21:11:07.143Z" }, + { url = "https://files.pythonhosted.org/packages/93/ef/e0870ccda798c54e6b100aff3c4d49df5458fd64217e860cb9c3b0a403f4/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fff2b76c8e172d34771cd4d4f0ade08072385310f214f823b5a6ad4006890d32", size = 2318229, upload-time = "2025-10-06T21:11:08.73Z" }, + { url = "https://files.pythonhosted.org/packages/b1/4b/c3b991d95f5deb24d0bd52e47bcf716098fa1afe0ce2d4bd3125b38566ba/pydantic_core-2.41.1-cp313-cp313-win32.whl", hash = "sha256:a38a5263185407ceb599f2f035faf4589d57e73c7146d64f10577f6449e8171d", size = 1997911, upload-time = "2025-10-06T21:11:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/5c316fd62e01f8d6be1b7ee6b54273214e871772997dc2c95e204997a055/pydantic_core-2.41.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42ae7fd6760782c975897e1fdc810f483b021b32245b0105d40f6e7a3803e4b", size = 2034301, upload-time = "2025-10-06T21:11:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/29/41/902640cfd6a6523194123e2c3373c60f19006447f2fb06f76de4e8466c5b/pydantic_core-2.41.1-cp313-cp313-win_arm64.whl", hash = "sha256:ad4111acc63b7384e205c27a2f15e23ac0ee21a9d77ad6f2e9cb516ec90965fb", size = 1977238, upload-time = "2025-10-06T21:11:14.1Z" }, + { url = "https://files.pythonhosted.org/packages/04/04/28b040e88c1b89d851278478842f0bdf39c7a05da9e850333c6c8cbe7dfa/pydantic_core-2.41.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:440d0df7415b50084a4ba9d870480c16c5f67c0d1d4d5119e3f70925533a0edc", size = 1875626, upload-time = "2025-10-06T21:11:15.69Z" }, + { url = "https://files.pythonhosted.org/packages/d6/58/b41dd3087505220bb58bc81be8c3e8cbc037f5710cd3c838f44f90bdd704/pydantic_core-2.41.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71eaa38d342099405dae6484216dcf1e8e4b0bebd9b44a4e08c9b43db6a2ab67", size = 2045708, upload-time = "2025-10-06T21:11:17.258Z" }, + { url = "https://files.pythonhosted.org/packages/d7/b8/760f23754e40bf6c65b94a69b22c394c24058a0ef7e2aa471d2e39219c1a/pydantic_core-2.41.1-cp313-cp313t-win_amd64.whl", hash = "sha256:555ecf7e50f1161d3f693bc49f23c82cf6cdeafc71fa37a06120772a09a38795", size = 1997171, upload-time = "2025-10-06T21:11:18.822Z" }, + { url = "https://files.pythonhosted.org/packages/41/12/cec246429ddfa2778d2d6301eca5362194dc8749ecb19e621f2f65b5090f/pydantic_core-2.41.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:05226894a26f6f27e1deb735d7308f74ef5fa3a6de3e0135bb66cdcaee88f64b", size = 2107836, upload-time = "2025-10-06T21:11:20.432Z" }, + { url = "https://files.pythonhosted.org/packages/20/39/baba47f8d8b87081302498e610aefc37142ce6a1cc98b2ab6b931a162562/pydantic_core-2.41.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85ff7911c6c3e2fd8d3779c50925f6406d770ea58ea6dde9c230d35b52b16b4a", size = 1904449, upload-time = "2025-10-06T21:11:22.185Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/9a3d87cae2c75a5178334b10358d631bd094b916a00a5993382222dbfd92/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47f1f642a205687d59b52dc1a9a607f45e588f5a2e9eeae05edd80c7a8c47674", size = 1961750, upload-time = "2025-10-06T21:11:24.348Z" }, + { url = "https://files.pythonhosted.org/packages/27/42/a96c9d793a04cf2a9773bff98003bb154087b94f5530a2ce6063ecfec583/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df11c24e138876ace5ec6043e5cae925e34cf38af1a1b3d63589e8f7b5f5cdc4", size = 2063305, upload-time = "2025-10-06T21:11:26.556Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8d/028c4b7d157a005b1f52c086e2d4b0067886b213c86220c1153398dbdf8f/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f0bf7f5c8f7bf345c527e8a0d72d6b26eda99c1227b0c34e7e59e181260de31", size = 2228959, upload-time = "2025-10-06T21:11:28.426Z" }, + { url = "https://files.pythonhosted.org/packages/08/f7/ee64cda8fcc9ca3f4716e6357144f9ee71166775df582a1b6b738bf6da57/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82b887a711d341c2c47352375d73b029418f55b20bd7815446d175a70effa706", size = 2345421, upload-time = "2025-10-06T21:11:30.226Z" }, + { url = "https://files.pythonhosted.org/packages/13/c0/e8ec05f0f5ee7a3656973ad9cd3bc73204af99f6512c1a4562f6fb4b3f7d/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5f1d5d6bbba484bdf220c72d8ecd0be460f4bd4c5e534a541bb2cd57589fb8b", size = 2065288, upload-time = "2025-10-06T21:11:32.019Z" }, + { url = "https://files.pythonhosted.org/packages/0a/25/d77a73ff24e2e4fcea64472f5e39b0402d836da9b08b5361a734d0153023/pydantic_core-2.41.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bf1917385ebe0f968dc5c6ab1375886d56992b93ddfe6bf52bff575d03662be", size = 2189759, upload-time = "2025-10-06T21:11:33.753Z" }, + { url = "https://files.pythonhosted.org/packages/66/45/4a4ebaaae12a740552278d06fe71418c0f2869537a369a89c0e6723b341d/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4f94f3ab188f44b9a73f7295663f3ecb8f2e2dd03a69c8f2ead50d37785ecb04", size = 2140747, upload-time = "2025-10-06T21:11:35.781Z" }, + { url = "https://files.pythonhosted.org/packages/da/6d/b727ce1022f143194a36593243ff244ed5a1eb3c9122296bf7e716aa37ba/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:3925446673641d37c30bd84a9d597e49f72eacee8b43322c8999fa17d5ae5bc4", size = 2327416, upload-time = "2025-10-06T21:11:37.75Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8c/02df9d8506c427787059f87c6c7253435c6895e12472a652d9616ee0fc95/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:49bd51cc27adb980c7b97357ae036ce9b3c4d0bb406e84fbe16fb2d368b602a8", size = 2318138, upload-time = "2025-10-06T21:11:39.463Z" }, + { url = "https://files.pythonhosted.org/packages/98/67/0cf429a7d6802536941f430e6e3243f6d4b68f41eeea4b242372f1901794/pydantic_core-2.41.1-cp314-cp314-win32.whl", hash = "sha256:a31ca0cd0e4d12ea0df0077df2d487fc3eb9d7f96bbb13c3c5b88dcc21d05159", size = 1998429, upload-time = "2025-10-06T21:11:41.989Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/742fef93de5d085022d2302a6317a2b34dbfe15258e9396a535c8a100ae7/pydantic_core-2.41.1-cp314-cp314-win_amd64.whl", hash = "sha256:1b5c4374a152e10a22175d7790e644fbd8ff58418890e07e2073ff9d4414efae", size = 2028870, upload-time = "2025-10-06T21:11:43.66Z" }, + { url = "https://files.pythonhosted.org/packages/31/38/cdd8ccb8555ef7720bd7715899bd6cfbe3c29198332710e1b61b8f5dd8b8/pydantic_core-2.41.1-cp314-cp314-win_arm64.whl", hash = "sha256:4fee76d757639b493eb600fba668f1e17475af34c17dd61db7a47e824d464ca9", size = 1974275, upload-time = "2025-10-06T21:11:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7e/8ac10ccb047dc0221aa2530ec3c7c05ab4656d4d4bd984ee85da7f3d5525/pydantic_core-2.41.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f9b9c968cfe5cd576fdd7361f47f27adeb120517e637d1b189eea1c3ece573f4", size = 1875124, upload-time = "2025-10-06T21:11:47.591Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e4/7d9791efeb9c7d97e7268f8d20e0da24d03438a7fa7163ab58f1073ba968/pydantic_core-2.41.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ebc7ab67b856384aba09ed74e3e977dded40e693de18a4f197c67d0d4e6d8e", size = 2043075, upload-time = "2025-10-06T21:11:49.542Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c3/3f6e6b2342ac11ac8cd5cb56e24c7b14afa27c010e82a765ffa5f771884a/pydantic_core-2.41.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8ae0dc57b62a762985bc7fbf636be3412394acc0ddb4ade07fe104230f1b9762", size = 1995341, upload-time = "2025-10-06T21:11:51.497Z" }, + { url = "https://files.pythonhosted.org/packages/16/89/d0afad37ba25f5801735af1472e650b86baad9fe807a42076508e4824a2a/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:68f2251559b8efa99041bb63571ec7cdd2d715ba74cc82b3bc9eff824ebc8bf0", size = 2124001, upload-time = "2025-10-07T10:49:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c4/08609134b34520568ddebb084d9ed0a2a3f5f52b45739e6e22cb3a7112eb/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:c7bc140c596097cb53b30546ca257dbe3f19282283190b1b5142928e5d5d3a20", size = 1941841, upload-time = "2025-10-07T10:49:56.248Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/94a4877094e5fe19a3f37e7e817772263e2c573c94f1e3fa2b1eee56ef3b/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2896510fce8f4725ec518f8b9d7f015a00db249d2fd40788f442af303480063d", size = 1961129, upload-time = "2025-10-07T10:49:58.298Z" }, + { url = "https://files.pythonhosted.org/packages/a2/30/23a224d7e25260eb5f69783a63667453037e07eb91ff0e62dabaadd47128/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ced20e62cfa0f496ba68fa5d6c7ee71114ea67e2a5da3114d6450d7f4683572a", size = 2148770, upload-time = "2025-10-07T10:49:59.959Z" }, + { url = "https://files.pythonhosted.org/packages/2b/3e/a51c5f5d37b9288ba30683d6e96f10fa8f1defad1623ff09f1020973b577/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b04fa9ed049461a7398138c604b00550bc89e3e1151d84b81ad6dc93e39c4c06", size = 2115344, upload-time = "2025-10-07T10:50:02.466Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/389504c9e0600ef4502cd5238396b527afe6ef8981a6a15cd1814fc7b434/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b3b7d9cfbfdc43c80a16638c6dc2768e3956e73031fca64e8e1a3ae744d1faeb", size = 1927994, upload-time = "2025-10-07T10:50:04.379Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9c/5111c6b128861cb792a4c082677e90dac4f2e090bb2e2fe06aa5b2d39027/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eec83fc6abef04c7f9bec616e2d76ee9a6a4ae2a359b10c21d0f680e24a247ca", size = 1959394, upload-time = "2025-10-07T10:50:06.335Z" }, + { url = "https://files.pythonhosted.org/packages/14/3f/cfec8b9a0c48ce5d64409ec5e1903cb0b7363da38f14b41de2fcb3712700/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6771a2d9f83c4038dfad5970a3eef215940682b2175e32bcc817bdc639019b28", size = 2147365, upload-time = "2025-10-07T10:50:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/f403d7ca8352e3e4df352ccacd200f5f7f7fe81cef8e458515f015091625/pydantic_core-2.41.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fabcbdb12de6eada8d6e9a759097adb3c15440fafc675b3e94ae5c9cb8d678a0", size = 2114268, upload-time = "2025-10-07T10:50:10.257Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b5/334473b6d2810df84db67f03d4f666acacfc538512c2d2a254074fee0889/pydantic_core-2.41.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e97ccfaf0aaf67d55de5085b0ed0d994f57747d9d03f2de5cc9847ca737b08", size = 1935786, upload-time = "2025-10-07T10:50:12.333Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5e/45513e4dc621f47397cfa5fef12ba8fa5e8b1c4c07f2ff2a5fef8ff81b25/pydantic_core-2.41.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34df1fe8fea5d332484a763702e8b6a54048a9d4fe6ccf41e34a128238e01f52", size = 1971995, upload-time = "2025-10-07T10:50:14.071Z" }, + { url = "https://files.pythonhosted.org/packages/22/e3/f1797c168e5f52b973bed1c585e99827a22d5e579d1ed57d51bc15b14633/pydantic_core-2.41.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:421b5595f845842fc093f7250e24ee395f54ca62d494fdde96f43ecf9228ae01", size = 2191264, upload-time = "2025-10-07T10:50:15.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e1/24ef4c3b4ab91c21c3a09a966c7d2cffe101058a7bfe5cc8b2c7c7d574e2/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dce8b22663c134583aaad24827863306a933f576c79da450be3984924e2031d1", size = 2152430, upload-time = "2025-10-07T10:50:18.018Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/70c1e225d67f7ef3fdba02c506d9011efaf734020914920b2aa3d1a45e61/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:300a9c162fea9906cc5c103893ca2602afd84f0ec90d3be36f4cc360125d22e1", size = 2324691, upload-time = "2025-10-07T10:50:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/dd4d21037c8bef0d8cce90a86a3f2dcb011c30086db2a10113c3eea23eba/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e019167628f6e6161ae7ab9fb70f6d076a0bf0d55aa9b20833f86a320c70dd65", size = 2324493, upload-time = "2025-10-07T10:50:21.568Z" }, + { url = "https://files.pythonhosted.org/packages/7e/78/3093b334e9c9796c8236a4701cd2ddef1c56fb0928fe282a10c797644380/pydantic_core-2.41.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:13ab9cc2de6f9d4ab645a050ae5aee61a2424ac4d3a16ba23d4c2027705e0301", size = 2146156, upload-time = "2025-10-07T10:50:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6c/fa3e45c2b054a1e627a89a364917f12cbe3abc3e91b9004edaae16e7b3c5/pydantic_core-2.41.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:af2385d3f98243fb733862f806c5bb9122e5fba05b373e3af40e3c82d711cef1", size = 2112094, upload-time = "2025-10-07T10:50:25.513Z" }, + { url = "https://files.pythonhosted.org/packages/e5/17/7eebc38b4658cc8e6902d0befc26388e4c2a5f2e179c561eeb43e1922c7b/pydantic_core-2.41.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6550617a0c2115be56f90c31a5370261d8ce9dbf051c3ed53b51172dd34da696", size = 1935300, upload-time = "2025-10-07T10:50:27.715Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/9fe640194a1717a464ab861d43595c268830f98cb1e2705aa134b3544b70/pydantic_core-2.41.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc17b6ecf4983d298686014c92ebc955a9f9baf9f57dad4065e7906e7bee6222", size = 1970417, upload-time = "2025-10-07T10:50:29.573Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ad/f4cdfaf483b78ee65362363e73b6b40c48e067078d7b146e8816d5945ad6/pydantic_core-2.41.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42ae9352cf211f08b04ea110563d6b1e415878eea5b4c70f6bdb17dca3b932d2", size = 2190745, upload-time = "2025-10-07T10:50:31.48Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c1/18f416d40a10f44e9387497ba449f40fdb1478c61ba05c4b6bdb82300362/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e82947de92068b0a21681a13dd2102387197092fbe7defcfb8453e0913866506", size = 2150888, upload-time = "2025-10-07T10:50:33.477Z" }, + { url = "https://files.pythonhosted.org/packages/42/30/134c8a921630d8a88d6f905a562495a6421e959a23c19b0f49b660801d67/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e244c37d5471c9acdcd282890c6c4c83747b77238bfa19429b8473586c907656", size = 2324489, upload-time = "2025-10-07T10:50:36.48Z" }, + { url = "https://files.pythonhosted.org/packages/9c/48/a9263aeaebdec81e941198525b43edb3b44f27cfa4cb8005b8d3eb8dec72/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1e798b4b304a995110d41ec93653e57975620ccb2842ba9420037985e7d7284e", size = 2322763, upload-time = "2025-10-07T10:50:38.751Z" }, + { url = "https://files.pythonhosted.org/packages/1d/62/755d2bd2593f701c5839fc084e9c2c5e2418f460383ad04e3b5d0befc3ca/pydantic_core-2.41.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f1fc716c0eb1663c59699b024428ad5ec2bcc6b928527b8fe28de6cb89f47efb", size = 2144046, upload-time = "2025-10-07T10:50:40.686Z" }, ] [[package]] @@ -512,16 +535,18 @@ wheels = [ [[package]] name = "python-benedict" -version = "0.34.1" +version = "0.35.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-fsutil" }, { name = "python-slugify" }, { name = "requests" }, + { name = "typing-extensions" }, + { name = "useful-types" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/a8/744dce47aeba978a516f13c0d728fa1236217fa9e75e2619891df8100e85/python_benedict-0.34.1.tar.gz", hash = "sha256:0a81784c826c8983c485bb647ed5e6c8fad139c3921ea1844b21e41da0ff0dc0", size = 53340, upload-time = "2025-01-18T14:52:04.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/7f/670cea45a5de7ba79b820b9e58c19ec28070e65f8fe4584edc14b3b86ff2/python_benedict-0.35.0.tar.gz", hash = "sha256:ca825742cb60641939857417b799c6ca6680ba1ed7d3a92cf722103bc8dcb3ea", size = 58486, upload-time = "2025-09-30T22:57:42.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/65/c59fb6eec8859047394ccd73eaf1623ce9fd82c48d880d9a072045669bc5/python_benedict-0.34.1-py3-none-any.whl", hash = "sha256:f09711ca2d8e716ca40440f1e26dfa3ccff15e08f1baa88de594fbba799cf87a", size = 51308, upload-time = "2025-01-18T14:52:01.886Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ae/76f758e10b04c00f26c1613d8c77a078a3001d8ec439ec9e992d7c5a9268/python_benedict-0.35.0-py3-none-any.whl", hash = "sha256:4be33761812a2b8986b19f9dcb9df687feefd81c4cbb11becd044a5e28c340f6", size = 59905, upload-time = "2025-09-30T22:57:40.637Z" }, ] [[package]] @@ -695,41 +720,51 @@ wheels = [ [[package]] name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] [[package]] @@ -743,14 +778,14 @@ wheels = [ [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" 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, upload-time = "2025-05-21T18:55:23.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] @@ -761,3 +796,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599 wheels = [ { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, ] + +[[package]] +name = "useful-types" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/d6/0d6db1f8766e9b5e7ec259666c40ceae6bbb9326caf50e08717639e167b7/useful_types-0.2.1.tar.gz", hash = "sha256:870a0bcc8fcb7d0b2f14055438c1cab7e248fded942b0943a4d7019e7fbbdacd", size = 4748, upload-time = "2024-04-20T08:58:15.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/23/c194fd6c6258727f694e22b106c262c7d2678049dbc2d2045743e235f43a/useful_types-0.2.1-py3-none-any.whl", hash = "sha256:0dca32763d7271b5c8c7c395c44c10d09dba47a41aec97dcb085041ad096e0e9", size = 5382, upload-time = "2024-04-20T08:58:13.759Z" }, +]