diff --git a/docs/api/openapi-reference.md b/docs/api/openapi-reference.md new file mode 100644 index 0000000..a4005a6 --- /dev/null +++ b/docs/api/openapi-reference.md @@ -0,0 +1 @@ +[OAD(./openapi.json)] diff --git a/docs/api/openapi.json b/docs/api/openapi.json new file mode 100644 index 0000000..dc85f63 --- /dev/null +++ b/docs/api/openapi.json @@ -0,0 +1,4781 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Prusa Connect API (Inferred)", + "version": "20260225-5dfbf71e", + "description": "This is NOT official Prusa Connect API documentation. This is the result of manually reverse engineering the platform when developing the `prusa-connect-sdk-client` Python library. See for full documentation.\n\n## Authentication\n\nMost endpoints require a Bearer JWT access token obtained via OAuth2 PKCE flow.\n\n**OAuth2 Endpoints (on `account.prusa3d.com`):**\n\n- Authorization: `https://account.prusa3d.com/o/authorize/`\n- Token: `https://account.prusa3d.com/o/token/`\n- Client ID: `MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4`\n- Redirect URI: `https://connect.prusa3d.com/login/auth-callback`\n- PKCE Method: S256\n\nOnce obtained, pass the access token as `Authorization: Bearer ` on all authenticated endpoints.\n\n## Notes on Field Names\n\nMany response fields use API-side names that differ from SDK model attributes. Aliases are noted in schema descriptions where observed." + }, + "servers": [ + { + "url": "https://connect.prusa3d.com", + "description": "Prusa Connect production server" + } + ], + "security": [ + { + "BearerAuth": [] + } + ], + "tags": [ + { + "name": "Auth", + "description": "Endpoints for application authentication." + }, + { + "name": "Platform", + "description": "Public endpoints for API version and application configuration. No authentication required." + }, + { + "name": "Printers", + "description": "List and inspect printers, retrieve supported commands, and access printer-local files and storage." + }, + { + "name": "Jobs", + "description": "Job history, job details, failure reasons, and the planned print queue." + }, + { + "name": "Teams", + "description": "Team (workspace) management and membership." + }, + { + "name": "Files", + "description": "Team cloud storage: list, download, and upload G-code / firmware files." + }, + { + "name": "Cameras", + "description": "List cameras and retrieve or trigger snapshots." + }, + { + "name": "Notifications", + "description": "User notification feed." + }, + { + "name": "Statistics", + "description": "Per-printer usage and job success statistics over a date range." + } + ], + "components": { + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Prusa Connect JWT access token passed as `Authorization: Bearer `. Obtain via the `PrusaOAuth2` authorization code + PKCE flow. Token claims include: `jti` (token ID), `sub` (user ID), `exp` (expiry), `sid` (session ID), `app` (app slug), `type` (token type), `connect_id`." + }, + "PrusaOAuth2": { + "type": "oauth2", + "description": "OAuth2 authorization code flow with PKCE (S256) against the Prusa Account identity provider. The `client_id` is `MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4` and the registered redirect URI is `https://connect.prusa3d.com/login/auth-callback`. Include `code_challenge` (SHA-256 of the verifier, base64url-encoded) and `code_challenge_method=S256` in the authorization request. Exchange the returned code for tokens at the token URL, supplying the original `code_verifier`. The resulting access token is then passed as a Bearer JWT to all Prusa Connect API endpoints.", + "flows": { + "authorizationCode": { + "authorizationUrl": "https://account.prusa3d.com/o/authorize/", + "tokenUrl": "https://account.prusa3d.com/o/token/", + "refreshUrl": "https://account.prusa3d.com/o/token/", + "scopes": { + "openid": "OpenID Connect identity token.", + "basic_info": "Read access to basic account profile information.", + "email_lists": "Access to email list subscription preferences.", + "connect": "Access to Prusa Connect resources (printers, jobs, files, etc.).", + "user_operations": "Perform user-level operations such as team management." + }, + "x-usePkce": { + "disableManualConfiguration": true, + "hideClientSecretInput": false + } + } + } + } + }, + "parameters": { + "LimitParam": { + "name": "limit", + "in": "query", + "description": "Maximum number of items to return.", + "required": false, + "schema": { + "type": "integer", + "default": 100 + } + }, + "OffsetParam": { + "name": "offset", + "in": "query", + "description": "Number of items to skip.", + "required": false, + "schema": { + "type": "integer", + "default": 0 + } + }, + "PrinterUuidParam": { + "name": "uuid", + "in": "path", + "required": true, + "description": "The UUID of the printer.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + "TeamIdParam": { + "name": "team_id", + "in": "path", + "required": true, + "description": "The numeric ID of the team.", + "schema": { + "type": "integer" + } + } + }, + "schemas": { + "Pager": { + "type": "object", + "description": "Pagination metadata returned alongside list responses.", + "properties": { + "limit": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "from": { + "type": "integer", + "description": "Index of first item in this page." + }, + "to": { + "type": "integer", + "description": "Index of last item in this page." + } + } + }, + "NetworkInfo": { + "type": "object", + "description": "Network configuration details for a printer.", + "properties": { + "hostname": { + "type": [ + "string", + "null" + ] + }, + "ipv4": { + "type": [ + "string", + "null" + ] + }, + "ipv6": { + "type": [ + "string", + "null" + ] + }, + "mac": { + "type": [ + "string", + "null" + ] + }, + "wifi_ssid": { + "type": [ + "string", + "null" + ] + }, + "lan_ipv4": { + "type": [ + "string", + "null" + ] + }, + "lan_mac": { + "type": [ + "string", + "null" + ] + } + } + }, + "SourceInfo": { + "type": "object", + "description": "Information about the source of an action or resource (e.g., a user).", + "properties": { + "id": { + "type": [ + "integer", + "null" + ] + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "public_name": { + "type": [ + "string", + "null" + ] + }, + "avatar": { + "type": [ + "string", + "null" + ], + "description": "Avatar URL. Relative paths are automatically prefixed with `https://media.printables.com/media/`." + } + } + }, + "Owner": { + "allOf": [ + { + "$ref": "#/components/schemas/SourceInfo" + } + ], + "description": "Represents the owner of a resource. Same fields as SourceInfo." + }, + "SyncInfo": { + "type": "object", + "description": "Synchronization details for a file or resource.", + "properties": { + "synced_by": { + "type": [ + "object", + "null" + ] + }, + "synced": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "source": { + "type": [ + "string", + "null" + ] + } + } + }, + "Temperatures": { + "type": "object", + "description": "Current and target temperatures for a printer. Returned nested under the `temp` key in printer responses.", + "properties": { + "temp_nozzle": { + "type": [ + "number", + "null" + ], + "description": "Current nozzle temperature in °C." + }, + "temp_bed": { + "type": [ + "number", + "null" + ], + "description": "Current bed temperature in °C." + }, + "target_nozzle": { + "type": [ + "number", + "null" + ], + "description": "Target nozzle temperature in °C." + }, + "target_bed": { + "type": [ + "number", + "null" + ], + "description": "Target bed temperature in °C." + } + } + }, + "FirmwareSupport": { + "type": "object", + "description": "Firmware version and support information for a printer.", + "properties": { + "latest": { + "type": [ + "string", + "null" + ] + }, + "current": { + "type": [ + "string", + "null" + ] + }, + "release_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "stable": { + "type": [ + "string", + "null" + ] + }, + "prerelease": { + "type": [ + "string", + "null" + ] + }, + "release": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": [ + "string", + "null" + ] + } + } + }, + "Tool": { + "type": "object", + "description": "Represents a print head / extruder tool.", + "properties": { + "material": { + "type": [ + "string", + "null" + ] + }, + "temp": { + "type": [ + "number", + "null" + ] + }, + "nozzle_diameter": { + "type": [ + "number", + "null" + ] + }, + "fan_hotend": { + "type": [ + "number", + "null" + ] + }, + "fan_print": { + "type": [ + "number", + "null" + ] + }, + "mmu": { + "type": [ + "object", + "null" + ] + }, + "hardened": { + "type": [ + "boolean", + "null" + ] + }, + "high_flow": { + "type": [ + "boolean", + "null" + ] + }, + "active": { + "type": [ + "boolean", + "null" + ] + } + } + }, + "SlotInfo": { + "type": "object", + "description": "MMU (Multi-Material Unit) slot information.", + "properties": { + "active": { + "type": [ + "integer", + "null" + ], + "description": "Index of the currently active slot." + }, + "slots": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/components/schemas/Tool" + } + }, + "state": { + "type": [ + "string", + "null" + ] + }, + "command": { + "type": [ + "string", + "null" + ] + } + } + }, + "JobInfo": { + "type": "object", + "description": "Snapshot of the job currently running on a printer. Returned nested under `job_info` in printer responses.", + "properties": { + "id": { + "type": [ + "integer", + "null" + ] + }, + "origin_id": { + "type": [ + "integer", + "null" + ] + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": [ + "string", + "null" + ] + }, + "progress": { + "type": [ + "number", + "null" + ], + "description": "Print progress 0.0–100.0." + }, + "time_printing": { + "type": [ + "integer", + "null" + ], + "description": "Elapsed print time in seconds." + }, + "time_remaining": { + "type": [ + "integer", + "null" + ], + "description": "Estimated remaining time in seconds." + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "start": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "end": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "hash": { + "type": [ + "string", + "null" + ] + }, + "preview_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "model_weight": { + "type": [ + "number", + "null" + ] + }, + "weight_remaining": { + "type": [ + "number", + "null" + ] + }, + "print_height": { + "type": [ + "number", + "null" + ] + }, + "total_height": { + "type": [ + "number", + "null" + ] + }, + "lifetime_id": { + "type": [ + "string", + "null" + ] + } + } + }, + "CameraResolution": { + "type": "object", + "required": [ + "width", + "height" + ], + "properties": { + "width": { + "type": "integer" + }, + "height": { + "type": "integer" + } + } + }, + "CameraNetworkInfo": { + "type": "object", + "properties": { + "wifi_mac": { + "type": [ + "string", + "null" + ] + }, + "wifi_ipv4": { + "type": [ + "string", + "null" + ] + }, + "wifi_ssid": { + "type": [ + "string", + "null" + ] + } + } + }, + "CameraConfig": { + "type": "object", + "description": "Internal configuration snapshot for a camera.", + "properties": { + "name": { + "type": [ + "string", + "null" + ] + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "model": { + "type": [ + "string", + "null" + ] + }, + "driver": { + "type": [ + "string", + "null" + ] + }, + "firmware": { + "type": [ + "string", + "null" + ] + }, + "rotation": { + "type": [ + "integer", + "null" + ] + }, + "camera_id": { + "type": [ + "string", + "null" + ] + }, + "resolution": { + "$ref": "#/components/schemas/CameraResolution" + }, + "manufacturer": { + "type": [ + "string", + "null" + ] + }, + "network_info": { + "$ref": "#/components/schemas/CameraNetworkInfo" + }, + "trigger_scheme": { + "type": [ + "string", + "null" + ] + } + } + }, + "CameraOptions": { + "type": "object", + "properties": { + "available_resolutions": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/CameraResolution" + } + } + } + }, + "Camera": { + "type": "object", + "description": "Camera device associated with a printer or team.", + "properties": { + "id": { + "type": [ + "integer", + "null" + ], + "description": "Numeric ID used for snapshot retrieval." + }, + "token": { + "type": [ + "string", + "null" + ], + "description": "Alphanumeric token used for snapshot triggers." + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "origin": { + "type": [ + "string", + "null" + ] + }, + "resolution": { + "type": [ + "string", + "null" + ] + }, + "snapshot_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "config": { + "$ref": "#/components/schemas/CameraConfig" + }, + "options": { + "$ref": "#/components/schemas/CameraOptions" + }, + "capabilities": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "features": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "sort_order": { + "type": [ + "integer", + "null" + ] + }, + "registered": { + "type": [ + "boolean", + "null" + ] + }, + "team_id": { + "type": [ + "integer", + "null" + ] + }, + "printer_uuid": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "snapshots": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + } + }, + "Printer": { + "type": "object", + "description": "Full printer object returned by `/app/printers` and `/app/printers/{uuid}`.", + "properties": { + "uuid": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "printer_state": { + "type": [ + "string", + "null" + ], + "description": "Also returned as `state` in some responses. Falls back to `UNKNOWN` for unrecognized values.", + "enum": [ + "READY", + "IDLE", + "BUSY", + "MANIPULATING", + "PRINTING", + "PAUSED", + "FINISHED", + "STOPPED", + "ATTENTION", + "ERROR", + "OFFLINE", + "UNKNOWN" + ] + }, + "state_reason": { + "type": [ + "string", + "null" + ] + }, + "connect_state": { + "type": [ + "string", + "null" + ] + }, + "disabled": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "boolean" + } + }, + "printer_model": { + "type": [ + "string", + "null" + ] + }, + "printer_type": { + "type": [ + "string", + "null" + ] + }, + "printer_type_name": { + "type": [ + "string", + "null" + ] + }, + "firmware": { + "type": [ + "string", + "null" + ], + "description": "Current firmware version string. SDK alias: `firmware_version`." + }, + "support": { + "$ref": "#/components/schemas/FirmwareSupport" + }, + "network_info": { + "$ref": "#/components/schemas/NetworkInfo" + }, + "tools": { + "type": [ + "object", + "null" + ], + "additionalProperties": { + "$ref": "#/components/schemas/Tool" + } + }, + "slot": { + "$ref": "#/components/schemas/SlotInfo" + }, + "temp": { + "$ref": "#/components/schemas/Temperatures", + "description": "Current temperatures. SDK alias: `telemetry`." + }, + "job_info": { + "$ref": "#/components/schemas/JobInfo", + "description": "Current job snapshot. SDK alias: `job`." + }, + "cameras": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/Camera" + } + }, + "nozzle_diameter": { + "type": [ + "number", + "null" + ] + }, + "speed": { + "type": [ + "integer", + "null" + ], + "description": "Current print speed percentage." + }, + "flow": { + "type": [ + "integer", + "null" + ], + "description": "Current flow rate percentage." + }, + "axis_x": { + "type": [ + "number", + "null" + ] + }, + "axis_y": { + "type": [ + "number", + "null" + ] + }, + "axis_z": { + "type": [ + "number", + "null" + ] + }, + "team_id": { + "type": [ + "integer", + "null" + ] + }, + "team_name": { + "type": [ + "string", + "null" + ] + }, + "organization_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "api_key": { + "type": [ + "string", + "null" + ] + }, + "prusalink_api_key": { + "type": [ + "string", + "null" + ] + }, + "prusaconnect_api_key": { + "type": [ + "string", + "null" + ] + }, + "last_online": { + "type": [ + "number", + "null" + ], + "description": "Unix timestamp of last online time." + }, + "created": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "sn": { + "type": [ + "string", + "null" + ], + "description": "Printer serial number." + }, + "location": { + "type": [ + "string", + "null" + ] + }, + "is_beta": { + "type": [ + "boolean", + "null" + ] + }, + "supported_printer_models": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "printable_extension": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "allowed_functionalities": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "max_filename": { + "type": [ + "integer", + "null" + ] + }, + "slots": { + "type": [ + "integer", + "null" + ], + "description": "Number of MMU slots." + }, + "rights_r": { + "type": [ + "boolean", + "null" + ] + }, + "rights_w": { + "type": [ + "boolean", + "null" + ] + }, + "rights_u": { + "type": [ + "boolean", + "null" + ] + }, + "owner": { + "$ref": "#/components/schemas/Owner" + }, + "flags": { + "type": [ + "object", + "null" + ] + }, + "filament": { + "type": [ + "object", + "null" + ] + }, + "enclosure": {}, + "appendix": { + "type": [ + "boolean", + "null" + ] + }, + "time_delta": { + "type": [ + "integer", + "null" + ] + }, + "inaccurate_estimates": { + "type": [ + "boolean", + "null" + ] + }, + "groups": { + "type": [ + "array", + "null" + ], + "items": {} + } + } + }, + "CommandArgument": { + "type": "object", + "description": "Describes a single argument for a printer command.", + "required": [ + "name", + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "string", + "integer", + "boolean", + "object", + "number" + ] + }, + "required": { + "type": "boolean", + "default": false + }, + "default": {}, + "description": { + "type": [ + "string", + "null" + ] + }, + "reference": { + "type": [ + "string", + "null" + ] + }, + "input": { + "type": "boolean", + "default": true + }, + "output": { + "type": "boolean", + "default": false + }, + "unit": { + "type": [ + "string", + "null" + ] + } + } + }, + "CommandDefinition": { + "type": "object", + "description": "Defines a command that can be sent to a printer.", + "required": [ + "command" + ], + "properties": { + "command": { + "type": "string", + "description": "Command identifier. Known values include: `SET_PRINTER_READY`, `CANCEL_PRINTER_READY`, `PAUSE_PRINT`, `RESUME_PRINT`, `STOP_PRINT`, `RESET_PRINTER`, `UNLOAD_FILAMENT`, `SEND_INFO`, `STOP_TRANSFER`, `SEND_STATE_INFO`, `RESET`, `DISABLE_STEPPERS`, `BEEP`." + }, + "args": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CommandArgument" + }, + "default": [] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "template": { + "type": [ + "string", + "null" + ], + "description": "G-code template, if applicable." + }, + "executable_from_state": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of printer states from which this command can be executed." + }, + "duplicates_allowed": { + "type": "boolean", + "default": false + } + } + }, + "JobStatus": { + "type": "string", + "description": "Status of a job. `FIN_UNKNOWN` is returned for unrecognized values.", + "enum": [ + "PRINTING", + "FINISHED", + "FIN_OK", + "FIN_STOPPED", + "FIN_ERROR", + "FIN_UNKNOWN" + ] + }, + "JobFailureTag": { + "type": "string", + "description": "Reason tag for a job failure or cancellation.", + "enum": [ + "IGNORED", + "CLOGGED_NOZZLE", + "NON_ADHERENT_BED", + "UNDER_EXTRUSION", + "OVER_EXTRUSION", + "STRINGING_OR_OOZING", + "GAPS_IN_THIN_WALLS", + "OVERHEATING", + "LAYER_SHIFTING", + "SPAGHETTI_MONSTER", + "LAYER_SEPARATION", + "WARPING", + "POOR_BRIDGING", + "OTHER" + ] + }, + "JobFailureReason": { + "type": "object", + "properties": { + "tag": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JobFailureTag" + } + }, + "other": { + "type": [ + "string", + "null" + ], + "description": "Free-text note when tag is OTHER." + } + } + }, + "CancelableObject": { + "type": "object", + "required": [ + "id", + "name" + ], + "description": "An object within a print that can be individually cancelled.", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "polygon": { + "type": [ + "array", + "null" + ], + "items": { + "type": "array", + "items": { + "type": "number" + } + } + }, + "canceled": { + "type": "boolean", + "default": false + } + } + }, + "Job": { + "type": "object", + "description": "A print job in the history or queue for a printer.", + "required": [ + "id", + "state" + ], + "properties": { + "id": { + "type": "integer" + }, + "lifetime_id": { + "type": [ + "string", + "null" + ] + }, + "printer_uuid": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "team_id": { + "type": [ + "integer", + "null" + ] + }, + "origin_id": { + "type": [ + "integer", + "null" + ] + }, + "source": { + "type": [ + "string", + "null" + ] + }, + "source_info": { + "$ref": "#/components/schemas/SourceInfo" + }, + "state": { + "$ref": "#/components/schemas/JobStatus" + }, + "cameras": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/Camera" + } + }, + "hash": { + "type": [ + "string", + "null" + ] + }, + "time_printing": { + "type": [ + "integer", + "null" + ], + "description": "Total print time in seconds." + }, + "start": { + "type": [ + "integer", + "null" + ], + "description": "Job start time as Unix timestamp." + }, + "end": { + "type": [ + "integer", + "null" + ], + "description": "Job end time as Unix timestamp." + }, + "progress": { + "type": [ + "number", + "null" + ] + }, + "planned": { + "type": [ + "object", + "null" + ] + }, + "print_height": { + "type": [ + "number", + "null" + ] + }, + "file": { + "$ref": "#/components/schemas/File" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "reason": { + "$ref": "#/components/schemas/JobFailureReason" + }, + "cancelable_objects": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/CancelableObject" + }, + "description": "Also accessible as `cancelable.objects` in some response formats." + }, + "cancelable_time": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + } + }, + "Storage": { + "type": "object", + "description": "A storage device available on the printer (e.g., USB, internal flash).", + "required": [ + "type", + "path", + "name" + ], + "properties": { + "type": { + "type": "string" + }, + "path": { + "type": "string" + }, + "mountpoint": { + "type": [ + "string", + "null" + ] + }, + "name": { + "type": "string" + }, + "read_only": { + "type": "boolean", + "default": false + }, + "is_sfn": { + "type": [ + "boolean", + "null" + ] + }, + "file_count": { + "type": [ + "integer", + "null" + ] + }, + "free_space": { + "type": [ + "integer", + "null" + ], + "description": "Free space in bytes." + }, + "total_space": { + "type": [ + "integer", + "null" + ], + "description": "Total space in bytes." + } + } + }, + "PrintFileMeta": { + "type": "object", + "description": "Metadata extracted from G-code / BG-code slicer comments.", + "properties": { + "extruder_colour": { + "type": [ + "string", + "null" + ] + }, + "filament_type": { + "type": [ + "string", + "null" + ] + }, + "filament_cost": { + "type": [ + "number", + "null" + ] + }, + "filament_abrasive": { + "type": [ + "boolean", + "null" + ] + }, + "temperature": { + "type": [ + "integer", + "null" + ], + "description": "Nozzle temperature in °C." + }, + "bed_temperature": { + "type": [ + "integer", + "null" + ], + "description": "Bed temperature in °C." + }, + "brim_width": { + "type": [ + "integer", + "null" + ] + }, + "ironing": { + "type": [ + "boolean", + "null" + ] + }, + "nozzle_high_flow": { + "type": [ + "boolean", + "null" + ] + }, + "support_material": { + "type": [ + "boolean", + "null" + ] + }, + "total_height": { + "type": [ + "number", + "null" + ] + }, + "max_layer_z": { + "type": [ + "number", + "null" + ] + }, + "layer_height": { + "type": [ + "number", + "null" + ] + }, + "fill_density": { + "type": [ + "string", + "null" + ] + }, + "nozzle_diameter": { + "type": [ + "number", + "null" + ] + }, + "filament_used_m": { + "type": [ + "number", + "null" + ] + }, + "filament_used_mm": { + "type": [ + "number", + "null" + ] + }, + "filament_used_g": { + "type": [ + "number", + "null" + ] + }, + "filament_used_cm3": { + "type": [ + "number", + "null" + ] + }, + "filament_used_mm3": { + "type": [ + "number", + "null" + ] + }, + "estimated_print_time": { + "type": [ + "integer", + "null" + ], + "description": "Estimated print time in seconds." + }, + "estimated_printing_time_normal_mode": { + "type": [ + "string", + "null" + ] + }, + "printer_model": { + "type": [ + "string", + "null" + ] + }, + "producer": { + "type": [ + "string", + "null" + ] + }, + "slots": { + "type": [ + "array", + "null" + ], + "items": { + "type": "object" + } + }, + "objects_info": { + "type": [ + "object", + "null" + ] + } + } + }, + "FirmwareFileMeta": { + "type": "object", + "description": "Metadata associated with a firmware (.bbf) file.", + "properties": { + "device_type_id": { + "type": [ + "string", + "null" + ] + }, + "version": { + "type": [ + "string", + "null" + ] + }, + "sem_ver": { + "type": [ + "string", + "null" + ] + }, + "build_no": { + "type": [ + "integer", + "null" + ] + }, + "bbf_version": { + "type": [ + "integer", + "null" + ] + }, + "printer_model": { + "type": [ + "string", + "null" + ] + } + } + }, + "BaseFile": { + "type": "object", + "description": "Common fields shared by all file types.", + "required": [ + "type", + "name" + ], + "properties": { + "type": { + "type": "string", + "description": "Discriminator field. One of `FILE`, `PRINT_FILE`, `FIRMWARE`." + }, + "name": { + "type": "string" + }, + "display_name": { + "type": [ + "string", + "null" + ] + }, + "size": { + "type": [ + "integer", + "null" + ], + "description": "File size in bytes." + }, + "hash": { + "type": [ + "string", + "null" + ], + "description": "SHA256 hash of the file content." + }, + "team_id": { + "type": [ + "integer", + "null" + ] + }, + "upload_id": { + "type": [ + "integer", + "null" + ] + }, + "uploaded": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "path": { + "type": [ + "string", + "null" + ] + }, + "display_path": { + "type": [ + "string", + "null" + ] + }, + "read_only": { + "type": "boolean", + "default": false + }, + "m_timestamp": { + "type": [ + "integer", + "null" + ], + "description": "Modification timestamp (Unix)." + }, + "sync": { + "$ref": "#/components/schemas/SyncInfo" + }, + "owner": { + "$ref": "#/components/schemas/Owner" + } + } + }, + "RegularFile": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseFile" + } + ], + "description": "A generic non-print, non-firmware file.", + "properties": { + "type": { + "type": "string", + "enum": [ + "FILE" + ] + } + } + }, + "PrintFile": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseFile" + } + ], + "description": "A G-code or BG-code print file.", + "properties": { + "type": { + "type": "string", + "enum": [ + "PRINT_FILE" + ] + }, + "preview_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "preview_mimetype": { + "type": [ + "string", + "null" + ] + }, + "meta": { + "$ref": "#/components/schemas/PrintFileMeta" + } + } + }, + "FirmwareFile": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseFile" + } + ], + "description": "A firmware (.bbf) file.", + "properties": { + "type": { + "type": "string", + "enum": [ + "FIRMWARE" + ] + }, + "printer_type": { + "type": [ + "string", + "null" + ] + }, + "release_url": { + "type": [ + "string", + "null" + ], + "format": "uri" + }, + "meta": { + "$ref": "#/components/schemas/FirmwareFileMeta" + } + } + }, + "File": { + "description": "A file in Prusa Connect storage. Discriminated by the `type` field.", + "oneOf": [ + { + "$ref": "#/components/schemas/PrintFile" + }, + { + "$ref": "#/components/schemas/FirmwareFile" + }, + { + "$ref": "#/components/schemas/RegularFile" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "PRINT_FILE": "#/components/schemas/PrintFile", + "FIRMWARE": "#/components/schemas/FirmwareFile", + "FILE": "#/components/schemas/RegularFile" + } + } + }, + "UploadStatus": { + "type": "object", + "description": "Status of an in-progress or completed file upload.", + "required": [ + "id", + "team_id", + "name", + "size", + "state" + ], + "properties": { + "id": { + "type": "integer" + }, + "team_id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer", + "description": "File size in bytes." + }, + "hash": { + "type": [ + "string", + "null" + ] + }, + "state": { + "type": "string" + }, + "source": { + "type": [ + "string", + "null" + ] + } + } + }, + "TeamUser": { + "type": "object", + "description": "A user that is a member of a team.", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": [ + "string", + "null" + ] + }, + "last_name": { + "type": [ + "string", + "null" + ] + }, + "public_name": { + "type": [ + "string", + "null" + ] + }, + "avatar": { + "type": [ + "string", + "null" + ] + }, + "rights_ro": { + "type": [ + "boolean", + "null" + ], + "description": "Read-only rights." + }, + "rights_rw": { + "type": [ + "boolean", + "null" + ], + "description": "Read-write rights." + }, + "rights_use": { + "type": [ + "boolean", + "null" + ], + "description": "Usage rights (start/stop print)." + } + } + }, + "Team": { + "type": "object", + "description": "A Prusa Connect team (workspace).", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "role": { + "type": [ + "string", + "null" + ] + }, + "description": { + "type": [ + "string", + "null" + ] + }, + "capacity": { + "type": [ + "integer", + "null" + ], + "description": "Maximum number of printers." + }, + "organization_id": { + "type": [ + "string", + "null" + ], + "format": "uuid" + }, + "prusaconnect_api_key": { + "type": [ + "string", + "null" + ] + }, + "user_count": { + "type": [ + "integer", + "null" + ] + }, + "users": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/TeamUser" + } + }, + "invitees": { + "type": [ + "array", + "null" + ], + "items": {} + } + } + }, + "AuthConfig": { + "type": "object", + "required": [ + "backends", + "server_url", + "client_id", + "redirect_url" + ], + "properties": { + "backends": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "PRUSA_AUTH" + ] + }, + "description": "List of supported authentication providers." + }, + "server_url": { + "type": "string", + "format": "uri", + "description": "Base URL for the authentication server." + }, + "client_id": { + "type": "string", + "description": "OAuth2 Client ID." + }, + "redirect_url": { + "type": "string", + "format": "uri", + "description": "OAuth2 Redirect URL." + }, + "avatar_server_url": { + "type": [ + "string", + "null" + ], + "format": "uri", + "description": "Base URL for retrieving user avatars." + }, + "max_upload_size": { + "type": [ + "integer", + "null" + ], + "description": "Maximum allowed file upload size in bytes." + }, + "max_snapshot_size": { + "type": [ + "integer", + "null" + ], + "description": "Maximum allowed camera snapshot size in bytes." + }, + "max_preview_size": { + "type": [ + "integer", + "null" + ], + "description": "Maximum allowed G-code preview size in bytes." + }, + "afs_enabled": { + "type": [ + "boolean", + "null" + ], + "description": "Whether the Automated Filament System (AFS) is enabled." + }, + "afs_group_id": { + "type": [ + "integer", + "null" + ], + "description": "Group ID for AFS." + } + } + }, + "AppConfig": { + "type": "object", + "required": [ + "auth" + ], + "properties": { + "auth": { + "$ref": "#/components/schemas/AuthConfig" + } + } + }, + "PrinterTypeParameters": { + "type": "object", + "description": "Hardware parameter ranges for a printer type.", + "properties": { + "extrusion": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "print_flow": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "print_speed": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "min_temp_nozzle_e": { + "type": "integer" + }, + "temp_nozzle": { + "$ref": "#/components/schemas/Range" + }, + "temp_bed": { + "$ref": "#/components/schemas/Range" + }, + "feedrate_e": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "feedrate_x": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "feedrate_y": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "feedrate_z": { + "$ref": "#/components/schemas/RangeWithDefault" + }, + "position_x": { + "$ref": "#/components/schemas/Range" + }, + "position_y": { + "$ref": "#/components/schemas/Range" + }, + "position_z": { + "$ref": "#/components/schemas/Range" + }, + "mbl_positions": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer" + } + }, + "description": "Mesh Bed Leveling position grid." + } + } + }, + "Range": { + "type": "object", + "properties": { + "max": { + "type": "number" + }, + "min": { + "type": "number" + } + } + }, + "RangeWithDefault": { + "allOf": [ + { + "$ref": "#/components/schemas/Range" + } + ], + "properties": { + "default": { + "type": "number" + } + } + }, + "PrinterType": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "integer" + }, + "version": { + "type": "integer" + }, + "subversion": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parameters": { + "$ref": "#/components/schemas/PrinterTypeParameters" + }, + "decision_maker": { + "type": [ + "string", + "null" + ] + }, + "max_filename": { + "type": [ + "integer", + "null" + ] + }, + "fw_device_type_id": { + "type": [ + "string", + "null" + ] + }, + "support": { + "type": "object", + "properties": { + "stable": { + "type": "string" + }, + "unsupported": { + "type": "string" + } + } + } + } + }, + "PrintingNotPrintingEntry": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "integer", + "description": "Duration in seconds. SDK alias: `duration`." + } + } + }, + "PrintingNotPrinting": { + "type": "object", + "description": "Printer usage statistics: time spent printing vs idle.", + "required": [ + "from", + "to", + "name", + "uuid", + "data" + ], + "properties": { + "from": { + "type": "integer", + "description": "Range start as Unix timestamp." + }, + "to": { + "type": "integer", + "description": "Range end as Unix timestamp." + }, + "name": { + "type": "string", + "description": "Printer name. SDK alias: `printer_name`." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Printer UUID. SDK alias: `printer_uuid`." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PrintingNotPrintingEntry" + } + } + } + }, + "MaterialQuantity": { + "type": "object", + "description": "Printer usage statistics: material quantity consumed.", + "required": [ + "from", + "to", + "name", + "uuid", + "data" + ], + "properties": { + "from": { + "type": "integer", + "description": "Range start as Unix timestamp." + }, + "to": { + "type": "integer", + "description": "Range end as Unix timestamp." + }, + "name": { + "type": "string", + "description": "Printer name. SDK alias: `printer_name`." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Printer UUID. SDK alias: `printer_uuid`." + }, + "data": { + "type": "array", + "items": {} + } + } + }, + "PlannedTasksSeries": { + "type": "object", + "required": [ + "uuid", + "name", + "data" + ], + "properties": { + "uuid": { + "type": "string", + "format": "uuid", + "description": "SDK alias: `printer_uuid`." + }, + "name": { + "type": "string", + "description": "SDK alias: `printer_name`." + }, + "data": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 2, + "maxItems": 2 + }, + "description": "Array of [timestamp, count] pairs." + } + } + }, + "PlannedTasks": { + "type": "object", + "description": "Printer usage statistics: planned print tasks over time.", + "required": [ + "from", + "to", + "xAxis", + "series" + ], + "properties": { + "from": { + "type": "integer", + "description": "Range start as Unix timestamp." + }, + "to": { + "type": "integer", + "description": "Range end as Unix timestamp." + }, + "xAxis": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Time axis as Unix timestamps. SDK alias: `time_axis`." + }, + "series": { + "$ref": "#/components/schemas/PlannedTasksSeries" + } + } + }, + "JobsSuccessSeries": { + "type": "object", + "required": [ + "name", + "data" + ], + "properties": { + "name": { + "type": "string", + "description": "Job status value (e.g., `FIN_OK`). SDK alias: `status`.", + "enum": [ + "PRINTING", + "FINISHED", + "FIN_OK", + "FIN_STOPPED", + "FIN_ERROR", + "FIN_UNKNOWN" + ] + }, + "data": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Count per date bucket." + } + } + }, + "JobsSuccess": { + "type": "object", + "description": "Printer usage statistics: job success/failure counts over time.", + "required": [ + "from", + "to", + "xAxis", + "name", + "uuid", + "series", + "time_shift" + ], + "properties": { + "from": { + "type": "integer", + "description": "Range start as Unix timestamp." + }, + "to": { + "type": "integer", + "description": "Range end as Unix timestamp." + }, + "xAxis": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Date labels for each bucket. SDK alias: `date_axis`." + }, + "name": { + "type": "string", + "description": "Printer name. SDK alias: `printer_name`." + }, + "uuid": { + "type": "string", + "format": "uuid", + "description": "Printer UUID. SDK alias: `printer_uuid`." + }, + "series": { + "type": "array", + "items": { + "$ref": "#/components/schemas/JobsSuccessSeries" + } + }, + "time_shift": { + "type": "string" + } + } + }, + "ErrorResponse": { + "type": "object", + "description": "Standard error response body returned for 4xx/5xx responses. `code` and `message` are always present. `reason` provides additional context for input/validation errors (e.g. invalid path parameters on a 400) but is absent for auth errors.", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "Machine-readable error code. Known values: `BAD_REQUEST` (400), `UNAUTHORIZED` (401), `NOT_FOUND_CAMERA` (404), `NOT_FOUND_PRINTER` (404), `NOT_FOUND_TEAM` (404). Full enumeration is not documented.", + "example": "UNAUTHORIZED" + }, + "message": { + "type": "string", + "description": "Human-readable description of the error.", + "example": "Missing or invalid security token" + }, + "reason": { + "type": [ + "string", + "null" + ], + "description": "Additional context about what caused the error. Present for validation/input errors (e.g. `Invalid path parameter: printer_uuid`); absent for auth errors.", + "example": "Invalid path parameter: printer_uuid" + } + } + } + } + }, + "paths": { + "/o/authorize": { + "servers": [ + { + "url": "https://account.prusa3d.com", + "description": "Prusa Account Identity Provider" + } + ], + "get": { + "operationId": "initiateAuthorization", + "summary": "Initiate OAuth2 Authorization", + "description": "Step 1 of the OAuth2 PKCE flow. Renders the Prusa Account login page as an HTML document and sets a `csrftoken` cookie. The server embeds the full percent-encoded authorization continuation URL in a hidden `next` field within the login form. The `csrftoken` cookie value must be forwarded as `csrfmiddlewaretoken` and the `next` value must be forwarded verbatim to `POST /login`.", + "security": [], + "tags": ["Auth"], + "parameters": [ + { + "name": "response_type", + "in": "query", + "required": true, + "description": "OAuth2 response type. Must be `code` for the authorization code flow.", + "schema": { + "type": "string", + "enum": ["code"] + } + }, + { + "name": "client_id", + "in": "query", + "required": true, + "description": "The OAuth2 client ID for Prusa Connect.", + "schema": { + "type": "string" + }, + "example": "MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4" + }, + { + "name": "redirect_uri", + "in": "query", + "required": true, + "description": "The registered callback URI. Must exactly match the registered value.", + "schema": { + "type": "string", + "enum": ["https://connect.prusa3d.com/login/auth-callback"] + } + }, + { + "name": "code_challenge_method", + "in": "query", + "required": true, + "description": "PKCE challenge method. Only `S256` (SHA-256) is accepted.", + "schema": { + "type": "string", + "enum": ["S256"] + } + }, + { + "name": "code_challenge", + "in": "query", + "required": true, + "description": "Base64url-encoded SHA-256 hash of the PKCE `code_verifier`. Must be generated fresh for each authorization request.", + "schema": { + "type": "string" + }, + "example": "-KfhcQVIeAp3Hy1yXxq0jRQxKgYzpzSOTGzBSK8j81" + } + ], + "responses": { + "200": { + "description": "HTML login form. Sets a `csrftoken` cookie required as `csrfmiddlewaretoken` in the credential submission. The form (`id=\"recaptcha_form\"`) contains hidden fields `csrfmiddlewaretoken` and `next` carrying the percent-encoded `/o/authorize/` continuation URL through the login chain.", + "headers": { + "Set-Cookie": { + "description": "Sets the `csrftoken` cookie used for CSRF protection on subsequent form submissions to `/login` and `/login/totp`.", + "schema": { + "type": "string", + "example": "csrftoken=RsGaG6vlFBSlmaM9KNvNylBDUxA7I8zF; Path=/; SameSite=Lax" + } + } + }, + "content": { + "text/html": { + "schema": { + "type": "string", + "description": "HTML document containing a login form with hidden fields `csrfmiddlewaretoken` and `next` (the percent-encoded `/o/authorize/` URL to resume after authentication)." + } + } + }, + "links": { + "SubmitCredentials": { + "operationId": "submitCredentials", + "description": "Pass the `csrftoken` cookie value as `csrfmiddlewaretoken` and the embedded `next` form-field value to `POST /login` to continue the flow." + } + } + } + } + } + }, + "/login": { + "post": { + "operationId": "submitCredentials", + "summary": "Submit User Credentials", + "description": "Step 2 of the OAuth2 PKCE flow. Submits the user's email and password as a form POST together with the CSRF token from `GET /o/authorize` and the `next` continuation URL. On success without MFA the server issues a `302` redirect to `redirect_uri?code=...`. If the account has TOTP enabled the redirect targets `/login/totp/` instead.", + "security": [], + "tags": ["Auth"], + "servers": [ + { + "url": "https://account.prusa3d.com", + "description": "Prusa Account Identity Provider" + } + ], + "parameters": [ + { + "name": "next", + "in": "query", + "required": true, + "description": "Percent-encoded `/o/authorize/` URL including all PKCE parameters. Preserved verbatim through every redirect in the login chain.", + "schema": { + "type": "string" + }, + "example": "/o/authorize/?response_type=code&client_id=MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4&redirect_uri=https%3A%2F%2Fconnect.prusa3d.com%2Flogin%2Fauth-callback&code_challenge_method=S256&code_challenge=LupMrl9-u2zbT3ltQAZv6dGFEsokCe2L7dIh2O0R6qg" + } + ], + "requestBody": { + "required": true, + "description": "Form-encoded credentials and CSRF token. `csrfmiddlewaretoken` is the value of the `csrftoken` cookie set by `GET /o/authorize`. `next` mirrors the query parameter and is used by the server to reconstruct the redirect chain.", + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "required": ["csrfmiddlewaretoken", "next", "email", "password"], + "properties": { + "csrfmiddlewaretoken": { + "type": "string", + "description": "CSRF token value from the `csrftoken` cookie set by `GET /o/authorize`." + }, + "next": { + "type": "string", + "description": "The percent-encoded `/o/authorize/` URL forwarded from the login form's hidden `next` field." + }, + "email": { + "type": "string", + "format": "email", + "description": "The user's Prusa Account email address." + }, + "password": { + "type": "string", + "format": "password", + "description": "The user's Prusa Account password." + } + } + } + } + } + }, + "responses": { + "302": { + "description": "Redirect on successful credential verification. Without MFA the `Location` header is `{redirect_uri}?code={authorization_code}`. With TOTP MFA enabled the `Location` header is `https://account.prusa3d.com/login/totp/?next={encoded_authorize_url}`.", + "headers": { + "Location": { + "description": "Redirect destination. Either the `redirect_uri` with an authorization `code` parameter (no MFA), or the TOTP endpoint with the `next` parameter (MFA required).", + "schema": { + "type": "string" + }, + "examples": { + "no-mfa": { + "summary": "Authorization code issued directly (no MFA)", + "value": "https://connect.prusa3d.com/login/auth-callback?code=YWAV9pnaSuxc6CxA27PdSEWa7Ld2P" + }, + "mfa-required": { + "summary": "TOTP step required", + "value": "https://account.prusa3d.com/login/totp/?next=/o/authorize/%3Fresponse_type%3Dcode%26client_id%3DMRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4%26..." + } + } + } + }, + "links": { + "SubmitTotp": { + "operationId": "submitTotp", + "description": "When MFA is required, follow the redirect to `POST /login/totp` supplying the same CSRF token, the forwarded `next` value, and the 6-digit OTP code." + }, + "ExchangeAuthorizationCode": { + "operationId": "exchangeToken", + "description": "When MFA is not required, extract the `code` query parameter from the `Location` redirect URL and exchange it for tokens at `POST /o/token/`.", + "parameters": { + "code": "$response.header.Location" + } + } + } + }, + "200": { + "description": "Login page re-rendered with a validation error (invalid credentials or reCAPTCHA failure).", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/login/totp": { + "post": { + "operationId": "submitTotp", + "summary": "Submit TOTP MFA Code", + "description": "Step 3 of the OAuth2 PKCE flow — conditional, only required when the account has TOTP MFA enabled. Submits the 6-digit one-time password together with the CSRF token and forwarded `next` URL. On success the server redirects to `redirect_uri?code=...` containing the authorization code.", + "security": [], + "tags": ["Auth"], + "servers": [ + { + "url": "https://account.prusa3d.com", + "description": "Prusa Account Identity Provider" + } + ], + "parameters": [ + { + "name": "next", + "in": "query", + "required": true, + "description": "Percent-encoded `/o/authorize/` URL forwarded from the `/login` redirect `Location` header. Must be preserved exactly.", + "schema": { + "type": "string" + }, + "example": "/o/authorize/?response_type=code&client_id=MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4&redirect_uri=https%3A%2F%2Fconnect.prusa3d.com%2Flogin%2Fauth-callback&code_challenge_method=S256&code_challenge=LupMrl9-u2zbT3ltQAZv6dGFEsokCe2L7dIh2O0R6qg" + } + ], + "requestBody": { + "required": true, + "description": "Form-encoded TOTP submission. `csrfmiddlewaretoken` must match the `csrftoken` cookie from the originating `GET /o/authorize` response.", + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "required": ["csrfmiddlewaretoken", "next", "otp_token"], + "properties": { + "csrfmiddlewaretoken": { + "type": "string", + "description": "CSRF token value matching the `csrftoken` cookie from `GET /o/authorize`." + }, + "next": { + "type": "string", + "description": "The percent-encoded `/o/authorize/` URL forwarded from the `/login` redirect." + }, + "otp_token": { + "type": "string", + "pattern": "^[0-9]{6}$", + "description": "6-digit TOTP code from the user's authenticator app.", + "example": "012467" + } + } + } + } + } + }, + "responses": { + "302": { + "description": "Redirect to `{redirect_uri}?code={authorization_code}` on successful OTP verification.", + "headers": { + "Location": { + "description": "The registered `redirect_uri` with the short-lived authorization `code` appended as a query parameter. Extract this `code` and pass it to `POST /o/token/`.", + "schema": { + "type": "string" + }, + "example": "https://connect.prusa3d.com/login/auth-callback?code=YWAV9pnaSuxc6CxA27PdSEWa7Ld2P" + } + }, + "links": { + "ExchangeAuthorizationCode": { + "operationId": "exchangeToken", + "description": "Extract the `code` query parameter from the `Location` redirect URL and exchange it for tokens at `POST /o/token/`.", + "parameters": { + "code": "$response.header.Location" + } + } + } + }, + "200": { + "description": "TOTP form re-rendered with a validation error (invalid or expired OTP code).", + "content": { + "text/html": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + + "/o/token/": { + "post": { + "operationId": "exchangeToken", + "summary": "Exchange Authorization Code for Tokens", + "description": "Step 4 of the OAuth2 PKCE flow. Exchanges the short-lived authorization `code` (extracted from the `redirect_uri` callback query parameter) for a JWT access token, JWT refresh token, and OIDC ID token. The `code_verifier` must correspond to the `code_challenge` submitted in `GET /o/authorize` (RFC 7636). Access tokens are valid for 7200 seconds; refresh tokens are valid for approximately 30 days.", + "security": [], + "tags": ["Auth"], + "servers": [ + { + "url": "https://account.prusa3d.com", + "description": "Prusa Account Identity Provider" + } + ], + "requestBody": { + "required": true, + "description": "Standard OAuth2 authorization code token request (RFC 6749 §4.1.3) with PKCE `code_verifier` extension (RFC 7636 §4.5).", + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "type": "object", + "required": ["grant_type", "client_id", "code", "code_verifier", "redirect_uri"], + "properties": { + "grant_type": { + "type": "string", + "enum": ["authorization_code"], + "description": "Must be `authorization_code`." + }, + "client_id": { + "type": "string", + "description": "The OAuth2 client ID for Prusa Connect.", + "example": "MRHTlZhZqkNrrQ6FUPtjyusAz8nc59ErHXP8XkS4" + }, + "code": { + "type": "string", + "description": "The authorization code extracted from the `code` query parameter of the `redirect_uri` callback.", + "example": "ou6xxSTbr99XO0GOWfnvtFMDz1WEgY" + }, + "code_verifier": { + "type": "string", + "description": "The original PKCE code verifier (high-entropy random string) whose SHA-256 hash was sent as `code_challenge` in `GET /o/authorize`.", + "example": "MLeh9nWAw-c_juZoNOyFvmArmZv9GnmSrRRVFx7bU2A" + }, + "redirect_uri": { + "type": "string", + "description": "Must exactly match the `redirect_uri` used in `GET /o/authorize`.", + "enum": ["https://connect.prusa3d.com/login/auth-callback"] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Token response containing a Bearer access token, refresh token, and OIDC ID token.", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["access_token", "expires_in", "token_type", "scope", "refresh_token", "shared_session_key", "id_token"], + "properties": { + "access_token": { + "type": "string", + "description": "RS256-signed JWT Bearer token. Pass as `Authorization: Bearer ` on all authenticated Prusa Connect API calls. Decoded claims: `jti` (token ID), `sub` (account numeric ID string), `exp` (expiry epoch float), `sid` (session UUID), `app` (always `\"connect\"`), `type` (always `\"access\"`), `connect_id` (Connect-specific user ID string)." + }, + "expires_in": { + "type": "integer", + "description": "Access token lifetime in seconds.", + "example": 7200 + }, + "token_type": { + "type": "string", + "enum": ["Bearer"], + "description": "Token type. Always `Bearer`." + }, + "scope": { + "type": "string", + "description": "Space-separated list of granted OAuth2 scopes.", + "example": "basic_info connect user_operations email_lists openid" + }, + "refresh_token": { + "type": "string", + "description": "RS256-signed JWT refresh token used to obtain a new access token without re-authentication. Valid for approximately 30 days. Decoded claims: `jti`, `sub`, `exp`, `sid`, `app`, `type` (always `\"refresh\"`)." + }, + "shared_session_key": { + "type": "string", + "format": "uuid", + "description": "Session UUID shared across all tokens issued in this authorization session. Corresponds to the `sid` claim in both the access and refresh tokens.", + "example": "8144bf06-4ef4-42e0-9c99-cada1948415b" + }, + "id_token": { + "type": "string", + "description": "RS256-signed OIDC ID token (JWT). Standard claims: `jti`, `sub` (account ID), `exp`, `aud` (client_id), `iss` (`https://account.prusa3d.com`). Extended `user` claim contains full profile: `id`, `email`, `language`, `first_name`, `last_name`, `public_username`, `avatar` (relative path), `avatar_small` / `avatar_large` (absolute media URLs), `external_service_ids` (`id_verse`, `id_academy`, `id_connect`, `id_shopsy`, `id_knowledgebase`), `otp_enabled`, `is_employee`, `is_staff`, `providers` (e.g. `[\"GOOGLE\"]`), `department`, `custom_data`." + } + } + } + } + }, + "links": { + "UseAccessToken": { + "operationRef": "#/paths/~1app~1printers/get", + "description": "Pass the `access_token` value as `Authorization: Bearer ` on all authenticated Prusa Connect API endpoints (see `BearerAuth` security scheme). The token is valid for `expires_in` seconds; use `refresh_token` to obtain a new access token when it expires." + } + } + }, + "400": { + "description": "Invalid token request — malformed parameters, expired or already-used authorization code, or `code_verifier`/`code_challenge` mismatch.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "OAuth2 error code (e.g. `invalid_grant`, `invalid_client`, `invalid_request`).", + "example": "invalid_grant" + }, + "error_description": { + "type": "string", + "description": "Human-readable description of the error.", + "example": "Code has expired." + } + } + } + } + } + } + } + } + }, + "/version.json": { + "get": { + "summary": "Get API version", + "description": "Returns the current platform version. Does not require authentication.", + "operationId": "getVersion", + "security": [], + "parameters": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "commit": { + "type": "string", + "description": "8-character commitish string." + }, + "version": { + "type": "string", + "description": "Version string (currently same as commit)." + }, + "date": { + "type": "string", + "description": "ISO format UTC timestamp when this version was committed." + } + } + } + } + } + } + }, + "tags": [ + "Platform" + ] + } + }, + "/app/config": { + "get": { + "summary": "Get application configuration", + "description": "Returns public application configuration including OAuth2 parameters, upload limits, and feature flags. Does not require authentication.", + "operationId": "getAppConfig", + "security": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AppConfig" + } + } + } + } + }, + "tags": [ + "Platform" + ] + } + }, + "/app/printer-types": { + "get": { + "summary": "Get printer types", + "description": "Returns all known printer type definitions with hardware parameter ranges. Does not require authentication.", + "operationId": "getPrinterTypes", + "security": [], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "printer_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PrinterType" + } + } + } + } + } + } + } + }, + "tags": [ + "Platform" + ] + } + }, + "/app/printers": { + "get": { + "summary": "List printers", + "description": "Returns all printers accessible to the authenticated user. Pass `team_id` to filter to a specific team.", + "operationId": "listPrinters", + "parameters": [ + { + "$ref": "#/components/parameters/LimitParam" + }, + { + "$ref": "#/components/parameters/OffsetParam" + }, + { + "name": "team_id", + "in": "query", + "required": false, + "description": "Filter printers by team ID.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK. May return either a wrapped object `{\"printers\": [...]}` or a bare array.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "printers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Printer" + } + } + } + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Printer" + } + } + ] + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/groups": { + "get": { + "summary": "Get printer groups", + "description": "Returns printer groupings. Schema of individual group items is not yet fully documented.", + "operationId": "getPrinterGroups", + "parameters": [ + { + "$ref": "#/components/parameters/LimitParam" + }, + { + "$ref": "#/components/parameters/OffsetParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "groups": { + "type": "array", + "items": {} + }, + "pager": { + "$ref": "#/components/schemas/Pager" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/{uuid}": { + "get": { + "summary": "Get printer details", + "description": "Returns full details for a single printer by UUID.", + "operationId": "getPrinter", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Printer" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/{uuid}/commands": { + "get": { + "summary": "Get supported commands", + "description": "Returns the list of commands supported by the printer. Used to determine printer capabilities before sending commands. The SDK requires `STOP_PRINT` to be present and raises `PrusaCompatibilityError` if missing.", + "operationId": "getPrinterCommands", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + } + ], + "responses": { + "200": { + "description": "OK. May return a wrapped object or a bare array.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CommandDefinition" + } + } + } + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/CommandDefinition" + } + } + ] + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/{uuid}/commands/sync": { + "post": { + "summary": "Execute printer command", + "description": "Sends a synchronous command to the printer. The command must be listed in the printer's supported commands. Known commands (no additional args): `SET_PRINTER_READY`, `CANCEL_PRINTER_READY`, `PAUSE_PRINT`, `RESUME_PRINT`, `STOP_PRINT`, `RESET_PRINTER`, `UNLOAD_FILAMENT`, `SEND_INFO`, `STOP_TRANSFER`, `SEND_STATE_INFO`, `RESET`, `DISABLE_STEPPERS`, `BEEP`.", + "operationId": "executePrinterCommand", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "command" + ], + "properties": { + "command": { + "type": "string", + "description": "The command name to execute (e.g., `PAUSE_PRINT`)." + }, + "kwargs": { + "type": [ + "object", + "null" + ], + "description": "Optional named arguments for commands that accept parameters." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Command accepted." + }, + "204": { + "description": "Command accepted, no content returned." + }, + "400": { + "description": "Bad request — invalid command or arguments.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/{uuid}/files": { + "get": { + "summary": "List printer files", + "description": "Returns files stored on the printer's local storage.", + "operationId": "listPrinterFiles", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + } + } + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + } + ] + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/{uuid}/storages": { + "get": { + "summary": "Get printer storage devices", + "description": "Returns storage devices (USB, internal flash, etc.) attached to the printer.", + "operationId": "getPrinterStorages", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "storages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Storage" + } + } + } + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Storage" + } + } + ] + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Printers" + ] + } + }, + "/app/printers/{uuid}/jobs": { + "get": { + "summary": "List printer job history", + "description": "Returns the job history for a specific printer.", + "operationId": "listPrinterJobs", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Jobs" + ] + } + }, + "/app/printers/{uuid}/jobs/{job_id}": { + "get": { + "summary": "Get job details", + "description": "Returns details for a specific job on a printer.", + "operationId": "getPrinterJob", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "name": "job_id", + "in": "path", + "required": true, + "description": "The numeric ID of the job.", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer or job not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Jobs" + ] + }, + "patch": { + "summary": "Update job (set failure reason)", + "description": "Updates a job record. Currently used to attach a failure reason to a finished/failed job.", + "operationId": "patchPrinterJob", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "name": "job_id", + "in": "path", + "required": true, + "description": "The numeric ID of the job.", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "$ref": "#/components/schemas/JobFailureReason" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated." + }, + "204": { + "description": "Updated, no content." + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer or job not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Jobs" + ] + } + }, + "/app/printers/{uuid}/queue": { + "get": { + "summary": "Get print queue", + "description": "Returns the planned/queued jobs for a printer. Response shape varies — may use `planned_jobs`, `jobs`, or `queue` as the key.", + "operationId": "getPrinterQueue", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "$ref": "#/components/parameters/LimitParam" + }, + { + "$ref": "#/components/parameters/OffsetParam" + } + ], + "responses": { + "200": { + "description": "OK. Key name for the array varies across firmware versions.", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "planned_jobs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + { + "type": "object", + "properties": { + "queue": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + } + } + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Job" + } + } + ] + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Jobs" + ] + } + }, + "/app/users/teams": { + "get": { + "summary": "List teams", + "description": "Returns all teams accessible to the authenticated user.", + "operationId": "listTeams", + "parameters": [ + { + "$ref": "#/components/parameters/LimitParam" + }, + { + "name": "offset", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "teams": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Team" + } + }, + "pager": { + "$ref": "#/components/schemas/Pager" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Teams" + ] + } + }, + "/app/users/teams/{team_id}": { + "get": { + "summary": "Get team details", + "description": "Returns full details for a team, including its members (`users`) and invitees.", + "operationId": "getTeam", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Team" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Team not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Teams" + ] + } + }, + "/app/teams/{team_id}/add-user": { + "post": { + "summary": "Invite user to team", + "description": "Invites a user (by email) to join a team with specified permissions.", + "operationId": "addTeamUser", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "rights_ro": { + "type": "boolean", + "default": true, + "description": "Grant read-only rights." + }, + "rights_use": { + "type": "boolean", + "default": false, + "description": "Grant usage rights (start/stop print)." + }, + "rights_rw": { + "type": "boolean", + "default": false, + "description": "Grant read-write rights." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "User invited successfully." + }, + "204": { + "description": "User invited, no content returned." + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Team not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Teams" + ] + } + }, + "/app/teams/{team_id}/files": { + "get": { + "summary": "List team files", + "description": "Returns all files stored in the team's Connect storage.", + "operationId": "listTeamFiles", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/File" + } + } + } + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Team not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Files" + ] + } + }, + "/app/teams/{team_id}/files/{file_hash}": { + "get": { + "summary": "Get team file details", + "description": "Returns metadata for a specific file in team storage, identified by its hash.", + "operationId": "getTeamFile", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + }, + { + "name": "file_hash", + "in": "path", + "required": true, + "description": "SHA256 hash (or identifier) of the file.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/File" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "File not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Files" + ] + } + }, + "/app/teams/{team_id}/files/{file_hash}/raw": { + "get": { + "summary": "Download team file", + "description": "Downloads the raw binary content of a file from team storage.", + "operationId": "downloadTeamFile", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + }, + { + "name": "file_hash", + "in": "path", + "required": true, + "description": "SHA256 hash (or identifier) of the file.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK — binary file content.", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "File not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Files" + ] + } + }, + "/app/users/teams/{team_id}/uploads": { + "post": { + "summary": "Initiate file upload", + "description": "Creates an upload session and returns an `upload_id`. Follow up with `PUT /app/teams/{team_id}/files/raw?upload_id={id}` to send the binary data.", + "operationId": "initiateTeamUpload", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "destination", + "filename", + "size" + ], + "properties": { + "destination": { + "type": "string", + "description": "Target folder path in Connect storage (e.g., `connect/My Projects`)." + }, + "filename": { + "type": "string", + "description": "The name of the file to create." + }, + "size": { + "type": "integer", + "description": "File size in bytes." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Upload session created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UploadStatus" + } + } + } + }, + "400": { + "description": "Bad request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Team not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Files" + ] + } + }, + "/app/teams/{team_id}/files/raw": { + "put": { + "summary": "Upload file data", + "description": "Uploads the binary content of a file for a previously initiated upload session. Must call `POST /app/users/teams/{team_id}/uploads` first to obtain `upload_id`.", + "operationId": "uploadTeamFileData", + "parameters": [ + { + "$ref": "#/components/parameters/TeamIdParam" + }, + { + "name": "upload_id", + "in": "query", + "required": true, + "description": "The upload session ID returned by the initiate upload endpoint.", + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "parameters_note": "Additionally requires headers: `Content-Type` (e.g., `application/x-bgcode`) and `Upload-Size` (file size in bytes).", + "responses": { + "200": { + "description": "Upload accepted." + }, + "204": { + "description": "Upload complete, no content returned." + }, + "400": { + "description": "Bad request — missing or invalid upload ID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Files" + ] + } + }, + "/app/cameras": { + "get": { + "summary": "List cameras", + "description": "Returns all cameras accessible to the authenticated user.", + "operationId": "listCameras", + "parameters": [ + { + "$ref": "#/components/parameters/LimitParam" + }, + { + "$ref": "#/components/parameters/OffsetParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "cameras": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Camera" + } + } + } + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Camera" + } + } + ] + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Cameras" + ] + } + }, + "/app/cameras/{camera_id}/snapshots/last": { + "get": { + "summary": "Get latest camera snapshot", + "description": "Returns the most recent snapshot image for a camera, identified by its numeric ID.", + "operationId": "getLastCameraSnapshot", + "parameters": [ + { + "name": "camera_id", + "in": "path", + "required": true, + "description": "Numeric camera ID (the `id` field on the Camera object).", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK — raw image bytes.", + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Camera not found or no snapshot available.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Cameras" + ] + } + }, + "/app/cameras/{camera_token}/snapshots": { + "post": { + "summary": "Trigger camera snapshot", + "description": "Instructs the camera to capture a new snapshot. Uses the alphanumeric `token` field from the Camera object.", + "operationId": "triggerCameraSnapshot", + "parameters": [ + { + "name": "camera_token", + "in": "path", + "required": true, + "description": "Alphanumeric camera token (the `token` field on the Camera object).", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Snapshot triggered." + }, + "204": { + "description": "Snapshot triggered, no content." + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Camera not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Cameras" + ] + } + }, + "/app/notifications": { + "get": { + "summary": "Get notifications", + "description": "Returns notifications for the authenticated user.", + "operationId": "getNotifications", + "parameters": [ + { + "$ref": "#/components/parameters/LimitParam" + }, + { + "$ref": "#/components/parameters/OffsetParam" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notifications": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "category": { + "type": "string" + }, + "name": { + "type": "string" + }, + "severity": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "hash": { + "type": "string" + }, + "job_id": { + "type": "integer" + }, + "job_name": { + "type": "string" + }, + "job_state": { + "type": "string" + }, + "preview_url": { + "type": "string" + }, + "printer_name": { + "type": "string" + }, + "printer_uuid": { + "type": "string", + "format": "uuid" + } + } + }, + "created": { + "type": "integer", + "description": "Unix timestamp." + }, + "text": { + "type": "string" + }, + "seen": { + "type": "integer", + "description": "Unix timestamp when seen, or 0 if unseen." + } + } + } + }, + "pager": { + "$ref": "#/components/schemas/Pager" + } + } + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Notifications" + ] + } + }, + "/app/stats/printers/{uuid}/printing_not_printing": { + "get": { + "summary": "Get printer usage stats", + "description": "Returns time-based statistics on how long the printer spent printing vs idle over a date range.", + "operationId": "getPrinterUsageStats", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "Start of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "End of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PrintingNotPrinting" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Statistics" + ] + } + }, + "/app/stats/printers/{uuid}/material_quantity": { + "get": { + "summary": "Get material quantity stats", + "description": "Returns statistics on the amount of material consumed by the printer over a date range.", + "operationId": "getPrinterMaterialStats", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "Start of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "End of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaterialQuantity" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Statistics" + ] + } + }, + "/app/stats/printers/{uuid}/planned_tasks": { + "get": { + "summary": "Get planned tasks stats", + "description": "Returns time-series data of planned print tasks for the printer over a date range.", + "operationId": "getPrinterPlannedTasksStats", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "Start of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "End of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlannedTasks" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Statistics" + ] + } + }, + "/app/stats/printers/{uuid}/jobs_success": { + "get": { + "summary": "Get job success stats", + "description": "Returns historical job success/failure counts bucketed by date for the printer.", + "operationId": "getPrinterJobsSuccessStats", + "parameters": [ + { + "$ref": "#/components/parameters/PrinterUuidParam" + }, + { + "name": "from", + "in": "query", + "required": false, + "description": "Start of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + }, + { + "name": "to", + "in": "query", + "required": false, + "description": "End of date range as Unix timestamp (seconds).", + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JobsSuccess" + } + } + } + }, + "401": { + "description": "Unauthorized — missing or invalid Bearer token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Printer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "tags": [ + "Statistics" + ] + } + } + } +} diff --git a/docs/css/mkdocsoad.css b/docs/css/mkdocsoad.css new file mode 100644 index 0000000..e9daefe --- /dev/null +++ b/docs/css/mkdocsoad.css @@ -0,0 +1,1814 @@ +/** + * All CSS for the neoteroi-mkdocs extensions. + * + * https://github.com/Neoteroi/mkdocs-plugins +**/ +:root { + --nt-color-0: #CD853F; + --nt-color-1: #B22222; + --nt-color-2: #000080; + --nt-color-3: #4B0082; + --nt-color-4: #3CB371; + --nt-color-5: #D2B48C; + --nt-color-6: #FF00FF; + --nt-color-7: #98FB98; + --nt-color-8: #FFEBCD; + --nt-color-9: #2E8B57; + --nt-color-10: #6A5ACD; + --nt-color-11: #48D1CC; + --nt-color-12: #FFA500; + --nt-color-13: #F4A460; + --nt-color-14: #A52A2A; + --nt-color-15: #FFE4C4; + --nt-color-16: #FF4500; + --nt-color-17: #AFEEEE; + --nt-color-18: #FA8072; + --nt-color-19: #2F4F4F; + --nt-color-20: #FFDAB9; + --nt-color-21: #BC8F8F; + --nt-color-22: #FFC0CB; + --nt-color-23: #00FA9A; + --nt-color-24: #F0FFF0; + --nt-color-25: #FFFACD; + --nt-color-26: #F5F5F5; + --nt-color-27: #FF6347; + --nt-color-28: #FFFFF0; + --nt-color-29: #7FFFD4; + --nt-color-30: #E9967A; + --nt-color-31: #7B68EE; + --nt-color-32: #FFF8DC; + --nt-color-33: #0000CD; + --nt-color-34: #D2691E; + --nt-color-35: #708090; + --nt-color-36: #5F9EA0; + --nt-color-37: #008080; + --nt-color-38: #008000; + --nt-color-39: #FFE4E1; + --nt-color-40: #FFFF00; + --nt-color-41: #FFFAF0; + --nt-color-42: #DCDCDC; + --nt-color-43: #ADFF2F; + --nt-color-44: #ADD8E6; + --nt-color-45: #8B008B; + --nt-color-46: #7FFF00; + --nt-color-47: #800000; + --nt-color-48: #20B2AA; + --nt-color-49: #556B2F; + --nt-color-50: #778899; + --nt-color-51: #E6E6FA; + --nt-color-52: #FFFAFA; + --nt-color-53: #FF7F50; + --nt-color-54: #FF0000; + --nt-color-55: #F5DEB3; + --nt-color-56: #008B8B; + --nt-color-57: #66CDAA; + --nt-color-58: #808000; + --nt-color-59: #FAF0E6; + --nt-color-60: #00BFFF; + --nt-color-61: #C71585; + --nt-color-62: #00FFFF; + --nt-color-63: #8B4513; + --nt-color-64: #F0F8FF; + --nt-color-65: #FAEBD7; + --nt-color-66: #8B0000; + --nt-color-67: #4682B4; + --nt-color-68: #F0E68C; + --nt-color-69: #BDB76B; + --nt-color-70: #A0522D; + --nt-color-71: #FAFAD2; + --nt-color-72: #FFD700; + --nt-color-73: #DEB887; + --nt-color-74: #E0FFFF; + --nt-color-75: #8A2BE2; + --nt-color-76: #32CD32; + --nt-color-77: #87CEFA; + --nt-color-78: #00CED1; + --nt-color-79: #696969; + --nt-color-80: #DDA0DD; + --nt-color-81: #EE82EE; + --nt-color-82: #FFB6C1; + --nt-color-83: #8FBC8F; + --nt-color-84: #D8BFD8; + --nt-color-85: #9400D3; + --nt-color-86: #A9A9A9; + --nt-color-87: #FFFFE0; + --nt-color-88: #FFF5EE; + --nt-color-89: #FFF0F5; + --nt-color-90: #FFDEAD; + --nt-color-91: #800080; + --nt-color-92: #B0E0E6; + --nt-color-93: #9932CC; + --nt-color-94: #DAA520; + --nt-color-95: #F0FFFF; + --nt-color-96: #40E0D0; + --nt-color-97: #00FF7F; + --nt-color-98: #006400; + --nt-color-99: #808080; + --nt-color-100: #87CEEB; + --nt-color-101: #0000FF; + --nt-color-102: #6495ED; + --nt-color-103: #FDF5E6; + --nt-color-104: #B8860B; + --nt-color-105: #BA55D3; + --nt-color-106: #C0C0C0; + --nt-color-107: #000000; + --nt-color-108: #F08080; + --nt-color-109: #B0C4DE; + --nt-color-110: #00008B; + --nt-color-111: #6B8E23; + --nt-color-112: #FFE4B5; + --nt-color-113: #FFA07A; + --nt-color-114: #9ACD32; + --nt-color-115: #FFFFFF; + --nt-color-116: #F5F5DC; + --nt-color-117: #90EE90; + --nt-color-118: #1E90FF; + --nt-color-119: #7CFC00; + --nt-color-120: #FF69B4; + --nt-color-121: #F8F8FF; + --nt-color-122: #F5FFFA; + --nt-color-123: #00FF00; + --nt-color-124: #D3D3D3; + --nt-color-125: #DB7093; + --nt-color-126: #DA70D6; + --nt-color-127: #FF1493; + --nt-color-128: #228B22; + --nt-color-129: #FFEFD5; + --nt-color-130: #4169E1; + --nt-color-131: #191970; + --nt-color-132: #9370DB; + --nt-color-133: #483D8B; + --nt-color-134: #FF8C00; + --nt-color-135: #EEE8AA; + --nt-color-136: #CD5C5C; + --nt-color-137: #DC143C; +} + +:root { + --nt-group-0-main: #000000; + --nt-group-0-dark: #FFFFFF; + --nt-group-0-light: #000000; + --nt-group-0-main-bg: #F44336; + --nt-group-0-dark-bg: #BA000D; + --nt-group-0-light-bg: #FF7961; + --nt-group-1-main: #000000; + --nt-group-1-dark: #FFFFFF; + --nt-group-1-light: #000000; + --nt-group-1-main-bg: #E91E63; + --nt-group-1-dark-bg: #B0003A; + --nt-group-1-light-bg: #FF6090; + --nt-group-2-main: #FFFFFF; + --nt-group-2-dark: #FFFFFF; + --nt-group-2-light: #000000; + --nt-group-2-main-bg: #9C27B0; + --nt-group-2-dark-bg: #6A0080; + --nt-group-2-light-bg: #D05CE3; + --nt-group-3-main: #FFFFFF; + --nt-group-3-dark: #FFFFFF; + --nt-group-3-light: #000000; + --nt-group-3-main-bg: #673AB7; + --nt-group-3-dark-bg: #320B86; + --nt-group-3-light-bg: #9A67EA; + --nt-group-4-main: #FFFFFF; + --nt-group-4-dark: #FFFFFF; + --nt-group-4-light: #000000; + --nt-group-4-main-bg: #3F51B5; + --nt-group-4-dark-bg: #002984; + --nt-group-4-light-bg: #757DE8; + --nt-group-5-main: #000000; + --nt-group-5-dark: #FFFFFF; + --nt-group-5-light: #000000; + --nt-group-5-main-bg: #2196F3; + --nt-group-5-dark-bg: #0069C0; + --nt-group-5-light-bg: #6EC6FF; + --nt-group-6-main: #000000; + --nt-group-6-dark: #FFFFFF; + --nt-group-6-light: #000000; + --nt-group-6-main-bg: #03A9F4; + --nt-group-6-dark-bg: #007AC1; + --nt-group-6-light-bg: #67DAFF; + --nt-group-7-main: #000000; + --nt-group-7-dark: #000000; + --nt-group-7-light: #000000; + --nt-group-7-main-bg: #00BCD4; + --nt-group-7-dark-bg: #008BA3; + --nt-group-7-light-bg: #62EFFF; + --nt-group-8-main: #000000; + --nt-group-8-dark: #FFFFFF; + --nt-group-8-light: #000000; + --nt-group-8-main-bg: #009688; + --nt-group-8-dark-bg: #00675B; + --nt-group-8-light-bg: #52C7B8; + --nt-group-9-main: #000000; + --nt-group-9-dark: #FFFFFF; + --nt-group-9-light: #000000; + --nt-group-9-main-bg: #4CAF50; + --nt-group-9-dark-bg: #087F23; + --nt-group-9-light-bg: #80E27E; + --nt-group-10-main: #000000; + --nt-group-10-dark: #000000; + --nt-group-10-light: #000000; + --nt-group-10-main-bg: #8BC34A; + --nt-group-10-dark-bg: #5A9216; + --nt-group-10-light-bg: #BEF67A; + --nt-group-11-main: #000000; + --nt-group-11-dark: #000000; + --nt-group-11-light: #000000; + --nt-group-11-main-bg: #CDDC39; + --nt-group-11-dark-bg: #99AA00; + --nt-group-11-light-bg: #FFFF6E; + --nt-group-12-main: #000000; + --nt-group-12-dark: #000000; + --nt-group-12-light: #000000; + --nt-group-12-main-bg: #FFEB3B; + --nt-group-12-dark-bg: #C8B900; + --nt-group-12-light-bg: #FFFF72; + --nt-group-13-main: #000000; + --nt-group-13-dark: #000000; + --nt-group-13-light: #000000; + --nt-group-13-main-bg: #FFC107; + --nt-group-13-dark-bg: #C79100; + --nt-group-13-light-bg: #FFF350; + --nt-group-14-main: #000000; + --nt-group-14-dark: #000000; + --nt-group-14-light: #000000; + --nt-group-14-main-bg: #FF9800; + --nt-group-14-dark-bg: #C66900; + --nt-group-14-light-bg: #FFC947; + --nt-group-15-main: #000000; + --nt-group-15-dark: #FFFFFF; + --nt-group-15-light: #000000; + --nt-group-15-main-bg: #FF5722; + --nt-group-15-dark-bg: #C41C00; + --nt-group-15-light-bg: #FF8A50; + --nt-group-16-main: #FFFFFF; + --nt-group-16-dark: #FFFFFF; + --nt-group-16-light: #000000; + --nt-group-16-main-bg: #795548; + --nt-group-16-dark-bg: #4B2C20; + --nt-group-16-light-bg: #A98274; + --nt-group-17-main: #000000; + --nt-group-17-dark: #FFFFFF; + --nt-group-17-light: #000000; + --nt-group-17-main-bg: #9E9E9E; + --nt-group-17-dark-bg: #707070; + --nt-group-17-light-bg: #CFCFCF; + --nt-group-18-main: #000000; + --nt-group-18-dark: #FFFFFF; + --nt-group-18-light: #000000; + --nt-group-18-main-bg: #607D8B; + --nt-group-18-dark-bg: #34515E; + --nt-group-18-light-bg: #8EACBB; +} + +.nt-pastello { + --nt-group-0-main: #000000; + --nt-group-0-dark: #000000; + --nt-group-0-light: #000000; + --nt-group-0-main-bg: #EF9A9A; + --nt-group-0-dark-bg: #BA6B6C; + --nt-group-0-light-bg: #FFCCCB; + --nt-group-1-main: #000000; + --nt-group-1-dark: #000000; + --nt-group-1-light: #000000; + --nt-group-1-main-bg: #F48FB1; + --nt-group-1-dark-bg: #BF5F82; + --nt-group-1-light-bg: #FFC1E3; + --nt-group-2-main: #000000; + --nt-group-2-dark: #000000; + --nt-group-2-light: #000000; + --nt-group-2-main-bg: #CE93D8; + --nt-group-2-dark-bg: #9C64A6; + --nt-group-2-light-bg: #FFC4FF; + --nt-group-3-main: #000000; + --nt-group-3-dark: #000000; + --nt-group-3-light: #000000; + --nt-group-3-main-bg: #B39DDB; + --nt-group-3-dark-bg: #836FA9; + --nt-group-3-light-bg: #E6CEFF; + --nt-group-4-main: #000000; + --nt-group-4-dark: #000000; + --nt-group-4-light: #000000; + --nt-group-4-main-bg: #9FA8DA; + --nt-group-4-dark-bg: #6F79A8; + --nt-group-4-light-bg: #D1D9FF; + --nt-group-5-main: #000000; + --nt-group-5-dark: #000000; + --nt-group-5-light: #000000; + --nt-group-5-main-bg: #90CAF9; + --nt-group-5-dark-bg: #5D99C6; + --nt-group-5-light-bg: #C3FDFF; + --nt-group-6-main: #000000; + --nt-group-6-dark: #000000; + --nt-group-6-light: #000000; + --nt-group-6-main-bg: #81D4FA; + --nt-group-6-dark-bg: #4BA3C7; + --nt-group-6-light-bg: #B6FFFF; + --nt-group-7-main: #000000; + --nt-group-7-dark: #000000; + --nt-group-7-light: #000000; + --nt-group-7-main-bg: #80DEEA; + --nt-group-7-dark-bg: #4BACB8; + --nt-group-7-light-bg: #B4FFFF; + --nt-group-8-main: #000000; + --nt-group-8-dark: #000000; + --nt-group-8-light: #000000; + --nt-group-8-main-bg: #80CBC4; + --nt-group-8-dark-bg: #4F9A94; + --nt-group-8-light-bg: #B2FEF7; + --nt-group-9-main: #000000; + --nt-group-9-dark: #000000; + --nt-group-9-light: #000000; + --nt-group-9-main-bg: #A5D6A7; + --nt-group-9-dark-bg: #75A478; + --nt-group-9-light-bg: #D7FFD9; + --nt-group-10-main: #000000; + --nt-group-10-dark: #000000; + --nt-group-10-light: #000000; + --nt-group-10-main-bg: #C5E1A5; + --nt-group-10-dark-bg: #94AF76; + --nt-group-10-light-bg: #F8FFD7; + --nt-group-11-main: #000000; + --nt-group-11-dark: #000000; + --nt-group-11-light: #000000; + --nt-group-11-main-bg: #E6EE9C; + --nt-group-11-dark-bg: #B3BC6D; + --nt-group-11-light-bg: #FFFFCE; + --nt-group-12-main: #000000; + --nt-group-12-dark: #000000; + --nt-group-12-light: #000000; + --nt-group-12-main-bg: #FFF59D; + --nt-group-12-dark-bg: #CBC26D; + --nt-group-12-light-bg: #FFFFCF; + --nt-group-13-main: #000000; + --nt-group-13-dark: #000000; + --nt-group-13-light: #000000; + --nt-group-13-main-bg: #FFE082; + --nt-group-13-dark-bg: #CAAE53; + --nt-group-13-light-bg: #FFFFB3; + --nt-group-14-main: #000000; + --nt-group-14-dark: #000000; + --nt-group-14-light: #000000; + --nt-group-14-main-bg: #FFCC80; + --nt-group-14-dark-bg: #CA9B52; + --nt-group-14-light-bg: #FFFFB0; + --nt-group-15-main: #000000; + --nt-group-15-dark: #000000; + --nt-group-15-light: #000000; + --nt-group-15-main-bg: #FFAB91; + --nt-group-15-dark-bg: #C97B63; + --nt-group-15-light-bg: #FFDDC1; + --nt-group-16-main: #000000; + --nt-group-16-dark: #000000; + --nt-group-16-light: #000000; + --nt-group-16-main-bg: #BCAAA4; + --nt-group-16-dark-bg: #8C7B75; + --nt-group-16-light-bg: #EFDCD5; + --nt-group-17-main: #000000; + --nt-group-17-dark: #000000; + --nt-group-17-light: #000000; + --nt-group-17-main-bg: #EEEEEE; + --nt-group-17-dark-bg: #BCBCBC; + --nt-group-17-light-bg: #FFFFFF; + --nt-group-18-main: #000000; + --nt-group-18-dark: #000000; + --nt-group-18-light: #000000; + --nt-group-18-main-bg: #B0BEC5; + --nt-group-18-dark-bg: #808E95; + --nt-group-18-light-bg: #E2F1F8; +} + +.nt-group-0 .nt-plan-group-summary, +.nt-group-0 .nt-timeline-dot { + color: var(--nt-group-0-dark); + background-color: var(--nt-group-0-dark-bg); +} +.nt-group-0 .period { + color: var(--nt-group-0-main); + background-color: var(--nt-group-0-main-bg); +} + +.nt-group-1 .nt-plan-group-summary, +.nt-group-1 .nt-timeline-dot { + color: var(--nt-group-1-dark); + background-color: var(--nt-group-1-dark-bg); +} +.nt-group-1 .period { + color: var(--nt-group-1-main); + background-color: var(--nt-group-1-main-bg); +} + +.nt-group-2 .nt-plan-group-summary, +.nt-group-2 .nt-timeline-dot { + color: var(--nt-group-2-dark); + background-color: var(--nt-group-2-dark-bg); +} +.nt-group-2 .period { + color: var(--nt-group-2-main); + background-color: var(--nt-group-2-main-bg); +} + +.nt-group-3 .nt-plan-group-summary, +.nt-group-3 .nt-timeline-dot { + color: var(--nt-group-3-dark); + background-color: var(--nt-group-3-dark-bg); +} +.nt-group-3 .period { + color: var(--nt-group-3-main); + background-color: var(--nt-group-3-main-bg); +} + +.nt-group-4 .nt-plan-group-summary, +.nt-group-4 .nt-timeline-dot { + color: var(--nt-group-4-dark); + background-color: var(--nt-group-4-dark-bg); +} +.nt-group-4 .period { + color: var(--nt-group-4-main); + background-color: var(--nt-group-4-main-bg); +} + +.nt-group-5 .nt-plan-group-summary, +.nt-group-5 .nt-timeline-dot { + color: var(--nt-group-5-dark); + background-color: var(--nt-group-5-dark-bg); +} +.nt-group-5 .period { + color: var(--nt-group-5-main); + background-color: var(--nt-group-5-main-bg); +} + +.nt-group-6 .nt-plan-group-summary, +.nt-group-6 .nt-timeline-dot { + color: var(--nt-group-6-dark); + background-color: var(--nt-group-6-dark-bg); +} +.nt-group-6 .period { + color: var(--nt-group-6-main); + background-color: var(--nt-group-6-main-bg); +} + +.nt-group-7 .nt-plan-group-summary, +.nt-group-7 .nt-timeline-dot { + color: var(--nt-group-7-dark); + background-color: var(--nt-group-7-dark-bg); +} +.nt-group-7 .period { + color: var(--nt-group-7-main); + background-color: var(--nt-group-7-main-bg); +} + +.nt-group-8 .nt-plan-group-summary, +.nt-group-8 .nt-timeline-dot { + color: var(--nt-group-8-dark); + background-color: var(--nt-group-8-dark-bg); +} +.nt-group-8 .period { + color: var(--nt-group-8-main); + background-color: var(--nt-group-8-main-bg); +} + +.nt-group-9 .nt-plan-group-summary, +.nt-group-9 .nt-timeline-dot { + color: var(--nt-group-9-dark); + background-color: var(--nt-group-9-dark-bg); +} +.nt-group-9 .period { + color: var(--nt-group-9-main); + background-color: var(--nt-group-9-main-bg); +} + +.nt-group-10 .nt-plan-group-summary, +.nt-group-10 .nt-timeline-dot { + color: var(--nt-group-10-dark); + background-color: var(--nt-group-10-dark-bg); +} +.nt-group-10 .period { + color: var(--nt-group-10-main); + background-color: var(--nt-group-10-main-bg); +} + +.nt-group-11 .nt-plan-group-summary, +.nt-group-11 .nt-timeline-dot { + color: var(--nt-group-11-dark); + background-color: var(--nt-group-11-dark-bg); +} +.nt-group-11 .period { + color: var(--nt-group-11-main); + background-color: var(--nt-group-11-main-bg); +} + +.nt-group-12 .nt-plan-group-summary, +.nt-group-12 .nt-timeline-dot { + color: var(--nt-group-12-dark); + background-color: var(--nt-group-12-dark-bg); +} +.nt-group-12 .period { + color: var(--nt-group-12-main); + background-color: var(--nt-group-12-main-bg); +} + +.nt-group-13 .nt-plan-group-summary, +.nt-group-13 .nt-timeline-dot { + color: var(--nt-group-13-dark); + background-color: var(--nt-group-13-dark-bg); +} +.nt-group-13 .period { + color: var(--nt-group-13-main); + background-color: var(--nt-group-13-main-bg); +} + +.nt-group-14 .nt-plan-group-summary, +.nt-group-14 .nt-timeline-dot { + color: var(--nt-group-14-dark); + background-color: var(--nt-group-14-dark-bg); +} +.nt-group-14 .period { + color: var(--nt-group-14-main); + background-color: var(--nt-group-14-main-bg); +} + +.nt-group-15 .nt-plan-group-summary, +.nt-group-15 .nt-timeline-dot { + color: var(--nt-group-15-dark); + background-color: var(--nt-group-15-dark-bg); +} +.nt-group-15 .period { + color: var(--nt-group-15-main); + background-color: var(--nt-group-15-main-bg); +} + +.nt-group-16 .nt-plan-group-summary, +.nt-group-16 .nt-timeline-dot { + color: var(--nt-group-16-dark); + background-color: var(--nt-group-16-dark-bg); +} +.nt-group-16 .period { + color: var(--nt-group-16-main); + background-color: var(--nt-group-16-main-bg); +} + +.nt-group-17 .nt-plan-group-summary, +.nt-group-17 .nt-timeline-dot { + color: var(--nt-group-17-dark); + background-color: var(--nt-group-17-dark-bg); +} +.nt-group-17 .period { + color: var(--nt-group-17-main); + background-color: var(--nt-group-17-main-bg); +} + +.nt-group-18 .nt-plan-group-summary, +.nt-group-18 .nt-timeline-dot { + color: var(--nt-group-18-dark); + background-color: var(--nt-group-18-dark-bg); +} +.nt-group-18 .period { + color: var(--nt-group-18-main); + background-color: var(--nt-group-18-main-bg); +} + +/** + * Extra CSS file for MkDocs and the neoteroi.timeline extension. + * + * https://github.com/Neoteroi/mkdocs-plugins +**/ +.nt-error { + border: 2px dashed darkred; + padding: 0 1rem; + background: #faf9ba; + color: darkred; +} + +.nt-timeline { + margin-top: 30px; +} +.nt-timeline .nt-timeline-title { + font-size: 1.1rem; + margin-top: 0; +} +.nt-timeline .nt-timeline-sub-title { + margin-top: 0; +} +.nt-timeline .nt-timeline-content { + font-size: 0.8rem; + border-bottom: 2px dashed #ccc; + padding-bottom: 1.2rem; +} +.nt-timeline.horizontal .nt-timeline-items { + flex-direction: row; + overflow-x: scroll; +} +.nt-timeline.horizontal .nt-timeline-items > div { + min-width: 400px; + margin-right: 50px; +} +.nt-timeline.horizontal.reverse .nt-timeline-items { + flex-direction: row-reverse; +} +.nt-timeline.horizontal.center .nt-timeline-before { + background-image: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%); + background-repeat: no-repeat; + background-size: 100% 2px; + background-position: 0 center; +} +.nt-timeline.horizontal.center .nt-timeline-after { + background-image: linear-gradient(180deg, rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%); + background-repeat: no-repeat; + background-size: 100% 2px; + background-position: 0 center; +} +.nt-timeline.horizontal.center .nt-timeline-items { + background-image: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%); + background-repeat: no-repeat; + background-size: 100% 2px; + background-position: 0 center; +} +.nt-timeline.horizontal .nt-timeline-dot { + left: 50%; +} +.nt-timeline.horizontal .nt-timeline-dot:not(.bigger) { + top: calc(50% - 4px); +} +.nt-timeline.horizontal .nt-timeline-dot.bigger { + top: calc(50% - 15px); +} +.nt-timeline.vertical .nt-timeline-items { + flex-direction: column; +} +.nt-timeline.vertical.reverse .nt-timeline-items { + flex-direction: column-reverse; +} +.nt-timeline.vertical.center .nt-timeline-before { + background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%; +} +.nt-timeline.vertical.center .nt-timeline-after { + background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat center/2px 100%; +} +.nt-timeline.vertical.center .nt-timeline-items { + background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%; +} +.nt-timeline.vertical.center .nt-timeline-dot { + left: calc(50% - 10px); +} +.nt-timeline.vertical.center .nt-timeline-dot:not(.bigger) { + top: 10px; +} +.nt-timeline.vertical.center .nt-timeline-dot.bigger { + left: calc(50% - 20px); +} +.nt-timeline.vertical.left { + padding-left: 100px; +} +.nt-timeline.vertical.left .nt-timeline-item { + padding-left: 70px; +} +.nt-timeline.vertical.left .nt-timeline-sub-title { + left: -100px; + width: 100px; +} +.nt-timeline.vertical.left .nt-timeline-before { + background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%; +} +.nt-timeline.vertical.left .nt-timeline-after { + background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat 30px/2px 100%; +} +.nt-timeline.vertical.left .nt-timeline-items { + background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%; +} +.nt-timeline.vertical.left .nt-timeline-dot { + left: 21px; + top: 8px; +} +.nt-timeline.vertical.left .nt-timeline-dot.bigger { + top: 0px; + left: 10px; +} +.nt-timeline.vertical.right { + padding-right: 100px; +} +.nt-timeline.vertical.right .nt-timeline-sub-title { + right: -100px; + text-align: left; + width: 100px; +} +.nt-timeline.vertical.right .nt-timeline-item { + padding-right: 70px; +} +.nt-timeline.vertical.right .nt-timeline-before { + background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%; +} +.nt-timeline.vertical.right .nt-timeline-after { + background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat calc(100% - 30px)/2px 100%; +} +.nt-timeline.vertical.right .nt-timeline-items { + background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%; +} +.nt-timeline.vertical.right .nt-timeline-dot { + right: 21px; + top: 8px; +} +.nt-timeline.vertical.right .nt-timeline-dot.bigger { + top: 10px; + right: 10px; +} + +.nt-timeline-items { + display: flex; + position: relative; +} +.nt-timeline-items > div { + min-height: 100px; + padding-top: 2px; + padding-bottom: 20px; +} + +.nt-timeline-before { + content: ""; + height: 15px; +} + +.nt-timeline-after { + content: ""; + height: 60px; + margin-bottom: 20px; +} + +.nt-timeline-sub-title { + position: absolute; + width: 50%; + top: 4px; + font-size: 18px; + color: var(--nt-color-50); +} + +[data-md-color-scheme=slate] .nt-timeline-sub-title { + color: var(--nt-color-51); +} + +.nt-timeline-item { + position: relative; +} + +.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item { + padding-left: calc(50% + 40px); +} +.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title { + left: 0; + padding-right: 40px; + text-align: right; +} +.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) { + padding-left: calc(50% + 40px); +} +.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title { + left: 0; + padding-right: 40px; + text-align: right; +} +.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) { + text-align: right; + padding-right: calc(50% + 40px); +} +.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title { + right: 0; + padding-left: 40px; + text-align: left; +} + +.nt-timeline-dot { + position: relative; + width: 20px; + height: 20px; + border-radius: 100%; + background-color: #fc5b5b; + position: absolute; + top: 0px; + z-index: 2; + display: flex; + justify-content: center; + align-items: center; + box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12); + border: 3px solid white; +} +.nt-timeline-dot:not(.bigger) .icon { + font-size: 10px; +} +.nt-timeline-dot.bigger { + width: 40px; + height: 40px; + padding: 3px; +} +.nt-timeline-dot .icon { + color: white; + position: relative; + top: 1px; +} + +/* Fix for webkit (Chrome, Safari) */ +@supports not (-moz-appearance: none) { + /* + This fix is necessary, for some reason, to render the timeline properly + inside `details` elements used by pymdownx. Firefox doesn't need this fix, + it renders elements properly. + */ + details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title, +details .nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title { + left: -40px; + } + details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title { + right: -40px; + } + details .nt-timeline.vertical.center .nt-timeline-dot { + left: calc(50% - 12px); + } + details .nt-timeline-dot.bigger { + font-size: 1rem !important; + } +} +/* default colors */ +.nt-timeline-item:nth-child(0) .nt-timeline-dot { + background-color: var(--nt-color-0); +} + +.nt-timeline-item:nth-child(1) .nt-timeline-dot { + background-color: var(--nt-color-1); +} + +.nt-timeline-item:nth-child(2) .nt-timeline-dot { + background-color: var(--nt-color-2); +} + +.nt-timeline-item:nth-child(3) .nt-timeline-dot { + background-color: var(--nt-color-3); +} + +.nt-timeline-item:nth-child(4) .nt-timeline-dot { + background-color: var(--nt-color-4); +} + +.nt-timeline-item:nth-child(5) .nt-timeline-dot { + background-color: var(--nt-color-5); +} + +.nt-timeline-item:nth-child(6) .nt-timeline-dot { + background-color: var(--nt-color-6); +} + +.nt-timeline-item:nth-child(7) .nt-timeline-dot { + background-color: var(--nt-color-7); +} + +.nt-timeline-item:nth-child(8) .nt-timeline-dot { + background-color: var(--nt-color-8); +} + +.nt-timeline-item:nth-child(9) .nt-timeline-dot { + background-color: var(--nt-color-9); +} + +.nt-timeline-item:nth-child(10) .nt-timeline-dot { + background-color: var(--nt-color-10); +} + +.nt-timeline-item:nth-child(11) .nt-timeline-dot { + background-color: var(--nt-color-11); +} + +.nt-timeline-item:nth-child(12) .nt-timeline-dot { + background-color: var(--nt-color-12); +} + +.nt-timeline-item:nth-child(13) .nt-timeline-dot { + background-color: var(--nt-color-13); +} + +.nt-timeline-item:nth-child(14) .nt-timeline-dot { + background-color: var(--nt-color-14); +} + +.nt-timeline-item:nth-child(15) .nt-timeline-dot { + background-color: var(--nt-color-15); +} + +.nt-timeline-item:nth-child(16) .nt-timeline-dot { + background-color: var(--nt-color-16); +} + +.nt-timeline-item:nth-child(17) .nt-timeline-dot { + background-color: var(--nt-color-17); +} + +.nt-timeline-item:nth-child(18) .nt-timeline-dot { + background-color: var(--nt-color-18); +} + +.nt-timeline-item:nth-child(19) .nt-timeline-dot { + background-color: var(--nt-color-19); +} + +.nt-timeline-item:nth-child(20) .nt-timeline-dot { + background-color: var(--nt-color-20); +} + +/** + * Extra CSS for the neoteroi.projects.gantt extension. + * + * https://github.com/Neoteroi/mkdocs-plugins +**/ +:root { + --nt-scrollbar-color: #2751b0; + --nt-plan-actions-height: 24px; + --nt-units-background: #ff9800; + --nt-months-background: #2751b0; + --nt-plan-vertical-line-color: #a3a3a3ad; +} + +.nt-pastello { + --nt-scrollbar-color: #9fb8f4; + --nt-units-background: #f5dc82; + --nt-months-background: #5b7fd1; +} + +[data-md-color-scheme=slate] { + --nt-units-background: #003773; +} +[data-md-color-scheme=slate] .nt-pastello { + --nt-units-background: #3f4997; +} + +.nt-plan-root { + min-height: 200px; + scrollbar-width: 20px; + scrollbar-color: var(--nt-scrollbar-color); + display: flex; +} +.nt-plan-root ::-webkit-scrollbar { + width: 20px; +} +.nt-plan-root ::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px grey; + border-radius: 10px; +} +.nt-plan-root ::-webkit-scrollbar-thumb { + background: var(--nt-scrollbar-color); + border-radius: 10px; +} +.nt-plan-root .nt-plan { + flex: 80%; +} +.nt-plan-root.no-groups .nt-plan-periods { + padding-left: 0; +} +.nt-plan-root.no-groups .nt-plan-group-summary { + display: none; +} +.nt-plan-root .nt-timeline-dot.bigger { + top: -10px; +} +.nt-plan-root .nt-timeline-dot.bigger[title] { + cursor: help; +} + +.nt-plan { + white-space: nowrap; + overflow-x: auto; + display: flex; +} +.nt-plan .ug-timeline-dot { + left: 368px; + top: -8px; + cursor: help; +} + +.months { + display: flex; +} + +.month { + flex: auto; + display: inline-block; + box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px inset; + background-color: var(--nt-months-background); + color: white; + text-transform: uppercase; + font-family: Roboto, Helvetica, Arial, sans-serif; + padding: 2px 5px; + font-size: 12px; + border: 1px solid #000; + width: 150px; + border-radius: 8px; +} + +.nt-plan-group-activities { + flex: auto; + position: relative; +} + +.nt-vline { + border-left: 1px dashed var(--nt-plan-vertical-line-color); + height: 100%; + left: 0; + position: absolute; + margin-left: -0.5px; + top: 0; + -webkit-transition: all 0.5s linear !important; + -moz-transition: all 0.5s linear !important; + -ms-transition: all 0.5s linear !important; + -o-transition: all 0.5s linear !important; + transition: all 0.5s linear !important; + z-index: -2; +} + +.nt-plan-activity { + display: flex; + margin: 2px 0; + background-color: rgba(187, 187, 187, 0.2509803922); +} + +.actions { + height: var(--nt-plan-actions-height); +} + +.actions { + position: relative; +} + +.period { + display: inline-block; + height: var(--nt-plan-actions-height); + width: 120px; + position: absolute; + left: 0px; + background: #1da1f2; + border-radius: 5px; + transition: all 0.5s; + cursor: help; + -webkit-transition: width 1s ease-in-out; + -moz-transition: width 1s ease-in-out; + -o-transition: width 1s ease-in-out; + transition: width 1s ease-in-out; +} +.period .nt-tooltip { + display: none; + top: 30px; + position: relative; + padding: 1rem; + text-align: center; + font-size: 12px; +} +.period:hover .nt-tooltip { + display: inline-block; +} + +.period-0 { + left: 340px; + visibility: visible; + background-color: rgb(69, 97, 101); +} + +.period-1 { + left: 40px; + visibility: visible; + background-color: green; +} + +.period-2 { + left: 120px; + visibility: visible; + background-color: pink; + width: 80px; +} + +.period-3 { + left: 190px; + visibility: visible; + background-color: darkred; + width: 150px; +} + +.weeks > span, +.days > span { + height: 25px; +} + +.weeks > span { + display: inline-block; + margin: 0; + padding: 0; + font-weight: bold; +} +.weeks > span .week-text { + font-size: 10px; + position: absolute; + display: inline-block; + padding: 3px 4px; +} + +.days { + z-index: -2; + position: relative; +} + +.day-text { + font-size: 10px; + position: absolute; + display: inline-block; + padding: 3px 4px; +} + +.period span { + font-size: 12px; + vertical-align: top; + margin-left: 4px; + color: black; + background: rgba(255, 255, 255, 0.6588235294); + border-radius: 6px; + padding: 0 4px; +} + +.weeks, +.days { + height: 20px; + display: flex; + box-sizing: content-box; +} + +.months { + display: flex; +} + +.week, +.day { + height: 20px; + position: relative; + border: 1; + flex: auto; + border: 2px solid white; + border-radius: 4px; + background-color: var(--nt-units-background); + cursor: help; +} + +.years { + display: flex; +} + +.year { + text-align: center; + border-right: 1px solid var(--nt-plan-vertical-line-color); + font-weight: bold; +} +.year:first-child { + border-left: 1px solid var(--nt-plan-vertical-line-color); +} +.year:first-child:last-child { + width: 100%; +} + +.quarters { + display: flex; +} + +.quarter { + width: 12.5%; + text-align: center; + border-right: 1px solid var(--nt-plan-vertical-line-color); + font-weight: bold; +} +.quarter:first-child { + border-left: 1px solid var(--nt-plan-vertical-line-color); +} + +.nt-plan-group { + margin: 20px 0; + position: relative; +} + +.nt-plan-group { + display: flex; +} + +.nt-plan-group-summary { + background: #2751b0; + width: 150px; + white-space: normal; + padding: 0.1rem 0.5rem; + border-radius: 5px; + color: #fff; + z-index: 3; +} +.nt-plan-group-summary p { + margin: 0; + padding: 0; + font-size: 0.6rem; + color: #fff; +} + +.nt-plan-group-summary, +.month, +.period, +.week, +.day, +.nt-tooltip { + border: 3px solid white; + box-shadow: 0 2px 3px -1px rgba(0, 0, 0, 0.2), 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12); +} + +.nt-plan-periods { + padding-left: 150px; +} + +.months { + z-index: 2; + position: relative; +} + +.weeks { + position: relative; + top: -2px; + z-index: 0; +} + +.month, +.quarter, +.year, +.week, +.day, +.nt-tooltip { + font-family: Roboto, Helvetica, Arial, sans-serif; + box-sizing: border-box; +} + +.nt-cards.nt-grid { + display: grid; + grid-auto-columns: 1fr; + gap: 0.5rem; + max-width: 100vw; + overflow-x: auto; + padding: 1px; +} +.nt-cards.nt-grid.cols-1 { + grid-template-columns: repeat(1, 1fr); +} +.nt-cards.nt-grid.cols-2 { + grid-template-columns: repeat(2, 1fr); +} +.nt-cards.nt-grid.cols-3 { + grid-template-columns: repeat(3, 1fr); +} +.nt-cards.nt-grid.cols-4 { + grid-template-columns: repeat(4, 1fr); +} +.nt-cards.nt-grid.cols-5 { + grid-template-columns: repeat(5, 1fr); +} +.nt-cards.nt-grid.cols-6 { + grid-template-columns: repeat(6, 1fr); +} + +@media only screen and (max-width: 400px) { + .nt-cards.nt-grid { + grid-template-columns: repeat(1, 1fr) !important; + } +} +.nt-card { + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); +} +.nt-card:hover { + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 3px 1px -2px rgba(0, 0, 0, 0.3), 0 1px 5px 0 rgba(0, 0, 0, 0.22); +} + +[data-md-color-scheme=slate] .nt-card { + box-shadow: 0 2px 2px 0 rgba(4, 40, 33, 0.14), 0 3px 1px -2px rgba(40, 86, 94, 0.47), 0 1px 5px 0 rgba(139, 252, 255, 0.64); +} +[data-md-color-scheme=slate] .nt-card:hover { + box-shadow: 0 2px 2px 0 rgba(0, 255, 206, 0.14), 0 3px 1px -2px rgba(33, 156, 177, 0.47), 0 1px 5px 0 rgba(96, 251, 255, 0.64); +} + +.nt-card > a { + color: var(--md-default-fg-color); +} + +.nt-card > a > div { + cursor: pointer; +} + +.nt-card { + padding: 5px; + margin-bottom: 0.5rem; +} + +.nt-card-title { + font-size: 1rem; + font-weight: bold; + margin: 4px 0 8px 0; + line-height: 22px; +} + +.nt-card-content { + padding: 0.4rem 0.8rem 0.8rem 0.8rem; +} + +.nt-card-text { + font-size: 14px; + padding: 0; + margin: 0; +} + +.nt-card .nt-card-image { + text-align: center; + border-radius: 2px; + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + min-height: 120px; +} + +.nt-card .nt-card-image.tags img { + margin-top: 12px; +} + +.nt-card .nt-card-image img { + height: 105px; + margin-top: 5px; +} + +.nt-card .nt-card-icon { + text-align: center; + padding-top: 12px; + min-height: 120px; +} + +.nt-card .nt-card-icon .icon { + font-size: 95px; + line-height: 1; +} + +.nt-card a:hover, +.nt-card a:focus { + color: var(--md-accent-fg-color); +} + +.nt-card h2 { + margin: 0; +} + +/** + * Extra CSS file recommended for MkDocs and neoteroi.spantable extension. + * + * https://github.com/Neoteroi/mkdocs-plugins +**/ +.span-table-wrapper table { + border-collapse: collapse; + margin-bottom: 2rem; + border-radius: 0.1rem; +} + +.span-table td, +.span-table th { + padding: 0.2rem; + background-color: var(--md-default-bg-color); + font-size: 0.64rem; + max-width: 100%; + overflow: auto; + touch-action: auto; + border-top: 0.05rem solid var(--md-typeset-table-color); + padding: 0.9375em 1.25em; + vertical-align: top; +} + +.span-table tr:first-child td { + font-weight: 700; + min-width: 5rem; + padding: 0.9375em 1.25em; + vertical-align: top; +} + +.span-table td:first-child { + border-left: 0.05rem solid var(--md-typeset-table-color); +} + +.span-table td:last-child { + border-right: 0.05rem solid var(--md-typeset-table-color); +} + +.span-table tr:last-child { + border-bottom: 0.05rem solid var(--md-typeset-table-color); +} + +.span-table [colspan], +.span-table [rowspan] { + font-weight: bold; + border: 0.05rem solid var(--md-typeset-table-color); +} + +.span-table tr:not(:first-child):hover td:not([colspan]):not([rowspan]), +.span-table td[colspan]:hover, +.span-table td[rowspan]:hover { + background-color: rgba(0, 0, 0, 0.035); + box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset; + transition: background-color 125ms; +} + +.nt-contribs { + margin-top: 2rem; + font-size: small; + border-top: 1px dotted lightgray; + padding-top: 0.5rem; +} +.nt-contribs .nt-contributors { + padding-top: 0.5rem; + display: flex; + flex-wrap: wrap; +} +.nt-contribs .nt-contributor { + background: lightgrey; + background-size: cover; + width: 40px; + height: 40px; + border-radius: 100%; + margin: 0 6px 6px 0; + cursor: help; + opacity: 0.7; +} +.nt-contribs .nt-contributor:hover { + opacity: 1; +} +.nt-contribs .nt-contributors-title { + font-style: italic; + margin-bottom: 0; +} +.nt-contribs .nt-initials { + text-transform: uppercase; + font-size: 20px; + text-align: center; + width: 40px; + height: 40px; + display: inline-block; + vertical-align: middle; + position: relative; + top: 4px; + color: inherit; + font-weight: bold; +} +.nt-contribs .nt-group-0 { + background-color: var(--nt-color-0); +} +.nt-contribs .nt-group-1 { + background-color: var(--nt-color-1); +} +.nt-contribs .nt-group-2 { + background-color: var(--nt-color-2); +} +.nt-contribs .nt-group-3 { + background-color: var(--nt-color-3); +} +.nt-contribs .nt-group-4 { + background-color: var(--nt-color-4); +} +.nt-contribs .nt-group-5 { + background-color: var(--nt-color-5); +} +.nt-contribs .nt-group-6 { + background-color: var(--nt-color-6); +} +.nt-contribs .nt-group-7 { + color: #000; + background-color: var(--nt-color-7); +} +.nt-contribs .nt-group-8 { + color: #000; + background-color: var(--nt-color-8); +} +.nt-contribs .nt-group-9 { + background-color: var(--nt-color-9); +} +.nt-contribs .nt-group-10 { + background-color: var(--nt-color-10); +} +.nt-contribs .nt-group-11 { + background-color: var(--nt-color-11); +} +.nt-contribs .nt-group-12 { + background-color: var(--nt-color-12); +} +.nt-contribs .nt-group-13 { + background-color: var(--nt-color-13); +} +.nt-contribs .nt-group-14 { + background-color: var(--nt-color-14); +} +.nt-contribs .nt-group-15 { + color: #000; + background-color: var(--nt-color-15); +} +.nt-contribs .nt-group-16 { + background-color: var(--nt-color-16); +} +.nt-contribs .nt-group-17 { + color: #000; + background-color: var(--nt-color-17); +} +.nt-contribs .nt-group-18 { + background-color: var(--nt-color-18); +} +.nt-contribs .nt-group-19 { + background-color: var(--nt-color-19); +} +.nt-contribs .nt-group-20 { + color: #000; + background-color: var(--nt-color-20); +} +.nt-contribs .nt-group-21 { + color: #000; + background-color: var(--nt-color-21); +} +.nt-contribs .nt-group-22 { + color: #000; + background-color: var(--nt-color-22); +} +.nt-contribs .nt-group-23 { + color: #000; + background-color: var(--nt-color-23); +} +.nt-contribs .nt-group-24 { + color: #000; + background-color: var(--nt-color-24); +} +.nt-contribs .nt-group-25 { + color: #000; + background-color: var(--nt-color-25); +} +.nt-contribs .nt-group-26 { + color: #000; + background-color: var(--nt-color-26); +} +.nt-contribs .nt-group-27 { + background-color: var(--nt-color-27); +} +.nt-contribs .nt-group-28 { + color: #000; + background-color: var(--nt-color-28); +} +.nt-contribs .nt-group-29 { + color: #000; + background-color: var(--nt-color-29); +} +.nt-contribs .nt-group-30 { + background-color: var(--nt-color-30); +} +.nt-contribs .nt-group-31 { + background-color: var(--nt-color-31); +} +.nt-contribs .nt-group-32 { + color: #000; + background-color: var(--nt-color-32); +} +.nt-contribs .nt-group-33 { + background-color: var(--nt-color-33); +} +.nt-contribs .nt-group-34 { + background-color: var(--nt-color-34); +} +.nt-contribs .nt-group-35 { + background-color: var(--nt-color-35); +} +.nt-contribs .nt-group-36 { + background-color: var(--nt-color-36); +} +.nt-contribs .nt-group-37 { + background-color: var(--nt-color-37); +} +.nt-contribs .nt-group-38 { + background-color: var(--nt-color-38); +} +.nt-contribs .nt-group-39 { + color: #000; + background-color: var(--nt-color-39); +} +.nt-contribs .nt-group-40 { + color: #000; + background-color: var(--nt-color-40); +} +.nt-contribs .nt-group-41 { + color: #000; + background-color: var(--nt-color-41); +} +.nt-contribs .nt-group-42 { + color: #000; + background-color: var(--nt-color-42); +} +.nt-contribs .nt-group-43 { + color: #000; + background-color: var(--nt-color-43); +} +.nt-contribs .nt-group-44 { + color: #000; + background-color: var(--nt-color-44); +} +.nt-contribs .nt-group-45 { + background-color: var(--nt-color-45); +} +.nt-contribs .nt-group-46 { + color: #000; + background-color: var(--nt-color-46); +} +.nt-contribs .nt-group-47 { + background-color: var(--nt-color-47); +} +.nt-contribs .nt-group-48 { + background-color: var(--nt-color-48); +} +.nt-contribs .nt-group-49 { + background-color: var(--nt-color-49); +} + +/** + * CSS for OpenAPI HTML generated with PyMdown Extensions option. + * + * This CSS file works when using the OAD plugin with pymdownx. + * See here how to use it: + * https://www.neoteroi.dev/mkdocs-plugins/web/oad/ + * + * https://github.com/Neoteroi/mkdocs-plugins +**/ +:root { + --http-get-color: green; + --http-delete-color: #dc0101; + --http-head-color: slateblue; + --http-options-color: steelblue; + --http-patch-color: darkorange; + --http-post-color: darkblue; + --http-put-color: darkmagenta; + --http-trace-color: darkcyan; + --http-route-param-color: rgb(51, 128, 210); + --oad-operation-separator-border-color: gray; + --oad-block-border-color: #00bfa5; + --oad-small-note-color: #666; + --oad-indent-border-color: #c5c5c5; +} + +@media screen { + /* Slate theme, i.e. dark mode */ + [data-md-color-scheme=slate] { + --http-get-color: #2ea82e; + --http-post-color: #0093c0; + --http-put-color: #c333c3; + --oad-small-note-color: #afafaf; + } +} +.api-tag { + font-weight: bold; +} + +span[class^=http-] { + font-weight: bold; + color: #fff; + padding: 4px 1rem; + border-radius: 2px; + margin-right: 0.5rem; +} + +.http-get { + background-color: var(--http-get-color); +} + +.http-delete { + background-color: var(--http-delete-color); +} + +.http-post { + background-color: var(--http-post-color); +} + +.http-patch { + background-color: var(--http-patch-color); +} + +.http-trace { + background-color: var(--http-trace-color); +} + +.http-put { + background-color: var(--http-put-color); +} + +.http-head { + background-color: var(--http-head-color); +} + +.http-options { + background-color: var(--http-options-color); +} + +.route-param { + color: var(--http-route-param-color); +} + +.operation-separator + h3[id^=get] .route-param { + color: var(--http-get-color); +} + +.operation-separator + h3[id^=delete] .route-param { + color: var(--http-delete-color); +} + +.operation-separator + h3[id^=post] .route-param { + color: var(--http-post-color); +} + +.operation-separator + h3[id^=patch] .route-param { + color: var(--http-patch-color); +} + +.operation-separator + h3[id^=trace] .route-param { + color: var(--http-trace-color); +} + +.operation-separator + h3[id^=put] .route-param { + color: var(--http-put-color); +} + +.operation-separator + h3[id^=head] .route-param { + color: var(--http-head-color); +} + +.operation-separator + h3[id^=options] .route-param { + color: var(--http-options-color); +} + +.api-version { + font-size: 1.2rem; +} + +.operation-separator { + margin: 0 !important; + border-bottom: 2px dotted var(--oad-operation-separator-border-color) !important; + padding-top: 0.5rem; +} + +.operation-separator + h3 { + margin-top: 1rem; +} + +.string-type { + color: var(--md-code-hl-string-color); +} + +.integer-type, .number-type { + color: var(--md-code-hl-number-color); +} + +.boolean-type { + color: var(--md-code-hl-keyword-color); +} + +.format { + color: var(--md-code-hl-name-color); +} + +.null-type { + color: var(--md-code-hl-keyword-color); +} + +a.ref-link { + color: var(--md-code-hl-special-color); +} + +.request-block + div { + padding-left: 1rem; + border-left: 2px dashed var(--oad-block-border-color); +} + +.small-note { + font-size: 14px; + color: var(--oad-small-note-color); +} + +.request-body-title { + margin-bottom: 4px; +} + +.request-body-title + .tabbed-set, +.response-title + .tabbed-set, +.message-separator + .tabbed-set, +.common-response, +.response-section { + margin-top: 2px; + padding-left: 1rem; + border-left: 2px dotted var(--oad-indent-border-color); +} + +.info-data { + font-size: 0.6rem; +} + +.message-separator { + visibility: hidden; +} + +.sub-section-title { + font-style: italic; + font-size: 14px; +} diff --git a/docs/development_notes/rest_api_reference.md b/docs/development_notes/rest_api_reference.md index 2d007cd..24ce896 100644 --- a/docs/development_notes/rest_api_reference.md +++ b/docs/development_notes/rest_api_reference.md @@ -1,6 +1,7 @@ # Captured Prusa Connect API Interactions -NOTE: This is mostly notes that I made as I went through API calls +NOTE: This is mostly notes that I made as I went through API calls. See +`/docs/api/openapi.json` for better documentation. ## Overview diff --git a/mkdocs.yml b/mkdocs.yml index 1dbc9bd..07dceb6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,7 @@ markdown_extensions: - attr_list - codehilite - markdown_gfm_admonition + - pymdownx.details - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg @@ -39,6 +40,8 @@ plugins: exclude: - index.md - cyclopts + - neoteroi.mkdocsoad: + use_pymdownx: true - mkdocstrings: handlers: python: @@ -61,6 +64,7 @@ exclude_docs: | development_notes/ extra_css: - css/extra.css + - css/mkdocsoad.css - css/mkdocstrings.css extra: analytics: @@ -88,6 +92,7 @@ nav: - Auth: api/auth.md - Client: api/client.md - Models: api/models.md + - OpenAPI Reference: api/openapi-reference.md - Development Resources: - Contributing: CONTRIBUTING.md - Architecture Notes: internal_architecture.md diff --git a/pyproject.toml b/pyproject.toml index d5a5ad3..4f0cbc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -66,6 +66,10 @@ name = "pypi" url = "https://pypi.org/simple" default = true +[tool.uv.sources] +# Temporary workaround until PR #63 upstream is merged and released +essentials-openapi = { git = "https://github.com/dcode/essentials-openapi", branch = "main" } + [build-system] requires = ["hatchling", "hatch-protobuf", "mypy-protobuf"] build-backend = "hatchling.build" @@ -74,6 +78,8 @@ build-backend = "hatchling.build" dev = [ "hatch>=1.16.3", "hatch-protobuf>=0.5.0", + "grpcio==1.78.0", # force downgrade due to "https://github.com/grpc/grpc/issues/41725" + "grpcio-tools==1.78.0", # force downgrade due to "https://github.com/grpc/grpc/issues/41725" "mdformat-gfm>=1.0.0", "mdformat-ruff>=0.1.3", "mypy-protobuf>=5.0.0", @@ -97,6 +103,8 @@ docs = [ "mkdocs-git-revision-date-localized-plugin>=1.5.1", "mkdocs-material[imaging,recommended]>=9.7.1", "mkdocstrings[python]>=1.0.3", + "neoteroi-mkdocs>=1.2.0", + "essentials-openapi @ git+https://github.com/dcode/essentials-openapi@main", "pymdown-extensions>=10.20.1", ] diff --git a/src/prusa/connect/client/auth.py b/src/prusa/connect/client/auth.py index 474c98c..b6329cd 100644 --- a/src/prusa/connect/client/auth.py +++ b/src/prusa/connect/client/auth.py @@ -248,7 +248,8 @@ def refresh(self) -> None: if resp.status_code == 200: new_data = resp.json() # We need to update our PrusaJWTTokenSet. - # The refresh response typically contains a new access_token and optionally a new refresh_token. + # The refresh response typically contains a new access_token + # and optionally a new refresh_token. # Use dump_tokens() to get current state as dict of raw strings current_raw = self.tokens.dump_tokens() @@ -392,11 +393,15 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C } logger.info("Initiating login flow...") - logger.debug("Login params", url=consts.AUTH_URL, params=params) + # logger.debug("Initial login request", method="GET", params=params, + # url=consts.AUTH_URL) resp = session.get(consts.AUTH_URL, params=params) + # logger.debug("Initial login flow reponse", status_code=resp.status_code, + # text=resp.text, headers=resp.headers) if resp.status_code != 200: raise exceptions.PrusaAuthError(f"Could not load login page (Status: {resp.status_code})") + # csrf could optionally be extracted from set-cookie header csrf = _extract_csrf(resp.text) if not csrf: logger.error("CSRF token missing from login page") @@ -411,7 +416,11 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C } headers = {"Referer": resp.url, "Origin": "https://account.prusa3d.com"} + # logger.debug("Submit user credentialls", method="POST", data=payload, + # headers=headers, url=resp.url) resp = session.post(resp.url, data=payload, headers=headers) + # logger.debug("Creds response", status_code=resp.status_code, + # url=resp.url, body=resp.text, headers=resp.headers) # 4. Check for errors or TOTP if 'class="invalid-feedback"' in resp.text: @@ -420,6 +429,8 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C if "/login/totp/" in resp.url: logger.info("2FA Challenge detected.") resp = _handle_totp(session, resp, otp_callback) + # logger.debug("TOTP Response", status_code=resp.status_code, url=resp.url, + # body=resp.text, headers=resp.headers) # 5. Handle Callback parsed = urllib.parse.urlparse(resp.url) @@ -431,24 +442,28 @@ def interactive_login(email: str, password: str, otp_callback: collections.abc.C raise exceptions.PrusaAuthError("Login failed: No authorization code in callback.") auth_code = query["code"][0] + payload = { + "grant_type": "authorization_code", + "client_id": consts.CLIENT_ID, + "code": auth_code, + "code_verifier": verifier, + "redirect_uri": consts.REDIRECT_URI, + } # 6. Exchange Code + # logger.debug("Submit authorization for user token", method="POST", + # data=payload, url=consts.TOKEN_URL) token_resp = session.post( consts.TOKEN_URL, - data={ - "grant_type": "authorization_code", - "client_id": consts.CLIENT_ID, - "code": auth_code, - "code_verifier": verifier, - "redirect_uri": consts.REDIRECT_URI, - }, + data=payload, ) if token_resp.status_code != 200: raise exceptions.PrusaAuthError(f"Token exchange failed: {token_resp.text}") logger.info("Token exchange successful.", user_id=token_resp.json().get("sub")) - logger.debug("Token response", json=token_resp.json()) + # logger.debug("Token response", status_code=token_resp.status_code, + # json=token_resp.json(), headers=token_resp.headers) return PrusaJWTTokenSet(**token_resp.json()) @@ -470,6 +485,8 @@ def _handle_totp( field_name: otp_code, } + # logger.debug("Handling TOTP", method="POST", data=payload, + # url=resp.url, referer=resp.url) return session.post(resp.url, data=payload, headers={"Referer": resp.url}) diff --git a/uv.lock b/uv.lock index 42ebcad..7cec256 100644 --- a/uv.lock +++ b/uv.lock @@ -131,16 +131,16 @@ wheels = [ [[package]] name = "backrefs" -version = "6.1" +version = "6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, ] [[package]] @@ -238,11 +238,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -468,20 +468,20 @@ sdist = { url = "https://files.pythonhosted.org/packages/f1/2a/8c3ac3d8bc94e6de8 [[package]] name = "cssselect2" -version = "0.8.0" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "tinycss2" }, { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/86/fd7f58fc498b3166f3a7e8e0cddb6e620fe1da35b02248b1bd59e95dbaaa/cssselect2-0.8.0.tar.gz", hash = "sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a", size = 35716, upload-time = "2025-03-05T14:46:07.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/20/92eaa6b0aec7189fa4b75c890640e076e9e793095721db69c5c81142c2e1/cssselect2-0.9.0.tar.gz", hash = "sha256:759aa22c216326356f65e62e791d66160a0f9c91d1424e8d8adc5e74dddfc6fb", size = 35595, upload-time = "2026-02-12T17:16:39.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/e7/aa315e6a749d9b96c2504a1ba0ba031ba2d0517e972ce22682e3fccecb09/cssselect2-0.8.0-py3-none-any.whl", hash = "sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e", size = 15454, upload-time = "2025-03-05T14:46:06.463Z" }, + { url = "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl", hash = "sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563", size = 15453, upload-time = "2026-02-12T17:16:38.317Z" }, ] [[package]] name = "cyclopts" -version = "4.5.1" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -489,9 +489,9 @@ dependencies = [ { name = "rich" }, { name = "rich-rst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/93/6085aa89c3fff78a5180987354538d72e43b0db27e66a959302d0c07821a/cyclopts-4.5.1.tar.gz", hash = "sha256:fadc45304763fd9f5d6033727f176898d17a1778e194436964661a005078a3dd", size = 162075, upload-time = "2026-01-25T15:23:54.07Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/5c/88a4068c660a096bbe87efc5b7c190080c9e86919c36ec5f092cb08d852f/cyclopts-4.6.0.tar.gz", hash = "sha256:483c4704b953ea6da742e8de15972f405d2e748d19a848a4d61595e8e5360ee5", size = 162724, upload-time = "2026-02-23T15:44:49.286Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/7c/996760c30f1302704af57c66ff2d723f7d656d0d0b93563b5528a51484bb/cyclopts-4.5.1-py3-none-any.whl", hash = "sha256:0642c93601e554ca6b7b9abd81093847ea4448b2616280f2a0952416574e8c7a", size = 199807, upload-time = "2026-01-25T15:23:55.219Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/1e8337755a70dc7d7ff10a73dc8f20e9352c9ad6c2256ed863ac95cd3539/cyclopts-4.6.0-py3-none-any.whl", hash = "sha256:0a891cb55bfd79a3cdce024db8987b33316aba11071e5258c21ac12a640ba9f2", size = 200518, upload-time = "2026-02-23T15:44:47.854Z" }, ] [package.optional-dependencies] @@ -558,13 +558,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/fd/a40c621ff207f3ce8e484aa0fc8ba4eb6e3ecf52e15b42ba764b457a9550/editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", size = 16360, upload-time = "2025-06-09T08:21:35.654Z" }, ] +[[package]] +name = "essentials" +version = "1.1.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/ff/587c8789abad9c76ef2a5bf613c6d7922a0d09879e3eda1864d108f11b75/essentials-1.1.9.tar.gz", hash = "sha256:7fbea3a518cbeafe5374fb7e2ea2c15a109e8a7fd1eaab62ae87cbd1b3b1e8d0", size = 25068, upload-time = "2025-11-23T20:34:46.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/8b/68aa342fa78931429ea09e6dc8d9ffa46fa36cfb0cf423e612d22365a9c2/essentials-1.1.9-py2.py3-none-any.whl", hash = "sha256:71ef161e0e27ef77cd6f5fc05e0b8688a575fcab870c01c95940f832e321dfbb", size = 16151, upload-time = "2025-11-23T20:34:45.025Z" }, +] + +[[package]] +name = "essentials-openapi" +version = "1.3.0" +source = { git = "https://github.com/dcode/essentials-openapi?branch=main#6f2556a0ee2970790ff5bea0f9ea2d75b5381668" } +dependencies = [ + { name = "essentials" }, + { name = "markupsafe" }, + { name = "pyyaml" }, +] + [[package]] name = "filelock" -version = "3.20.3" +version = "3.24.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, ] [[package]] @@ -603,30 +622,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, ] -[[package]] -name = "griffe" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "griffecli" }, - { name = "griffelib" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, -] - -[[package]] -name = "griffecli" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama" }, - { name = "griffelib" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, -] - [[package]] name = "griffelib" version = "2.0.0" @@ -730,7 +725,7 @@ wheels = [ [[package]] name = "hatch" -version = "1.16.3" +version = "1.16.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "backports-zstd", marker = "python_full_version < '3.14'" }, @@ -751,9 +746,9 @@ dependencies = [ { name = "uv" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/c1/976b807478878d31d467dd17b9fe642962f292e16ed13c34b593c0453fde/hatch-1.16.3.tar.gz", hash = "sha256:2a50ecc912adfc8122cd2ccdcc15254cdef829e5d158be9014180cd7f0fb7ea9", size = 5219621, upload-time = "2026-01-21T01:36:19.822Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/78/44051bc8b0061b523065e3f67e42fd9b3857ae1ed8b69febdde4f1dd2a3b/hatch-1.16.4.tar.gz", hash = "sha256:1539141fe014be89c4290418964cf4a79e6bee9b6b7638787aa1d1576ff448b1", size = 5220311, upload-time = "2026-02-23T22:45:51.599Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/b4/5c5fa4ca8c59e7ef0a224ff10e6336e73ca61c5e0eff09ee691441c9275f/hatch-1.16.3-py3-none-any.whl", hash = "sha256:f5169025cf1cdfe981366eb96127cab1d1bc59f5f2acb87c4cc308c25d95a4b1", size = 141305, upload-time = "2026-01-21T01:36:18.13Z" }, + { url = "https://files.pythonhosted.org/packages/6b/29/742010d61a7665b863a36208bfa3df93476e9a86fde45413cd13db76f7d0/hatch-1.16.4-py3-none-any.whl", hash = "sha256:0b434f522a5b1b0303ffe6195ad056b7f815403413c72561308001fde82a3b6f", size = 141355, upload-time = "2026-02-23T22:45:49.862Z" }, ] [[package]] @@ -784,7 +779,7 @@ wheels = [ [[package]] name = "hatchling" -version = "1.28.0" +version = "1.29.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, @@ -792,9 +787,9 @@ dependencies = [ { name = "pluggy" }, { name = "trove-classifiers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/8e/e480359492affde4119a131da729dd26da742c2c9b604dff74836e47eef9/hatchling-1.28.0.tar.gz", hash = "sha256:4d50b02aece6892b8cd0b3ce6c82cb218594d3ec5836dbde75bf41a21ab004c8", size = 55365, upload-time = "2025-11-27T00:31:13.766Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/9c/b4cfe330cd4f49cff17fd771154730555fa4123beb7f292cf0098b4e6c20/hatchling-1.29.0.tar.gz", hash = "sha256:793c31816d952cee405b83488ce001c719f325d9cda69f1fc4cd750527640ea6", size = 55656, upload-time = "2026-02-23T19:42:06.539Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/a5/48cb7efb8b4718b1a4c0c331e3364a3a33f614ff0d6afd2b93ee883d3c47/hatchling-1.28.0-py3-none-any.whl", hash = "sha256:dc48722b68b3f4bbfa3ff618ca07cdea6750e7d03481289ffa8be1521d18a961", size = 76075, upload-time = "2025-11-27T00:31:12.544Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/44032265776062a89171285ede55a0bdaadc8ac00f27f0512a71a9e3e1c8/hatchling-1.29.0-py3-none-any.whl", hash = "sha256:50af9343281f34785fab12da82e445ed987a6efb34fd8c2fc0f6e6630dbcc1b0", size = 76356, upload-time = "2026-02-23T19:42:05.197Z" }, ] [[package]] @@ -1289,16 +1284,16 @@ wheels = [ [[package]] name = "mkdocs-autorefs" -version = "1.4.3" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, ] [[package]] @@ -1346,7 +1341,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.7.2" +version = "9.7.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -1361,9 +1356,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/57/5d3c8c9e2ff9d66dc8f63aa052eb0bac5041fecff7761d8689fe65c39c13/mkdocs_material-9.7.2.tar.gz", hash = "sha256:6776256552290b9b7a7aa002780e25b1e04bc9c3a8516b6b153e82e16b8384bd", size = 4097818, upload-time = "2026-02-18T15:53:07.763Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/b4/f900fcb8e6f510241e334ca401eddcb61ed880fb6572f7f32e4228472ca1/mkdocs_material-9.7.3.tar.gz", hash = "sha256:e5f0a18319699da7e78c35e4a8df7e93537a888660f61a86bd773a7134798f22", size = 4097748, upload-time = "2026-02-24T12:06:22.646Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/19/d194e75e82282b1d688f0720e21b5ac250ed64ddea333a228aaf83105f2e/mkdocs_material-9.7.2-py3-none-any.whl", hash = "sha256:9bf6f53452d4a4d527eac3cef3f92b7b6fc4931c55d57766a7d87890d47e1b92", size = 9305052, upload-time = "2026-02-18T15:53:05.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/16ad0193079bb8a15aa1d2620813a9cd15b18de150a4ea1b2c607fb4c74d/mkdocs_material-9.7.3-py3-none-any.whl", hash = "sha256:37ebf7b4788c992203faf2e71900be3c197c70a4be9b0d72aed537b08a91dd9d", size = 9305078, upload-time = "2026-02-24T12:06:19.155Z" }, ] [package.optional-dependencies] @@ -1453,16 +1448,16 @@ python = [ [[package]] name = "mkdocstrings-python" -version = "2.0.2" +version = "2.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "griffe" }, + { name = "griffelib" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/84/78243847ad9d5c21d30a2842720425b17e880d99dfe824dee11d6b2149b4/mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8", size = 199124, upload-time = "2026-02-09T15:12:01.543Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/33/c225eaf898634bdda489a6766fc35d1683c640bffe0e0acd10646b13536d/mkdocstrings_python-2.0.3.tar.gz", hash = "sha256:c518632751cc869439b31c9d3177678ad2bfa5c21b79b863956ad68fc92c13b8", size = 199083, upload-time = "2026-02-20T10:38:36.368Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/31/7ee938abbde2322e553a2cb5f604cdd1e4728e08bba39c7ee6fae9af840b/mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0", size = 104900, upload-time = "2026-02-09T15:12:00.166Z" }, + { url = "https://files.pythonhosted.org/packages/32/28/79f0f8de97cce916d5ae88a7bee1ad724855e83e6019c0b4d5b3fabc80f3/mkdocstrings_python-2.0.3-py3-none-any.whl", hash = "sha256:0b83513478bdfd803ff05aa43e9b1fca9dd22bcd9471f09ca6257f009bc5ee12", size = 104779, upload-time = "2026-02-20T10:38:34.517Z" }, ] [[package]] @@ -1531,6 +1526,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/b1/ab1e7a49930a8c1d1f7a570bbd4ec7d552ef035acc7aa4b97906e17a34a9/mypy_protobuf-5.0.0-py3-none-any.whl", hash = "sha256:3a7dd753ef3e3b8783a824eb51f07983f62812f9ec066e4fbb1b22d6c5dc36d0", size = 26008, upload-time = "2026-01-13T17:10:11.053Z" }, ] +[[package]] +name = "neoteroi-mkdocs" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "essentials-openapi" }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "mkdocs" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/36/7cc711922112a1293568b8bc2f94bfaae0104bf5c1e5b3cdcfd6068023c7/neoteroi_mkdocs-1.2.0.tar.gz", hash = "sha256:58e25cb1b9db093ffa8d12bdb33264bf567cac30fb964b56e0a493efa749ad6e", size = 25354, upload-time = "2025-11-23T10:49:23.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/e9/d365600c11aa84142b878e6655d23c0add7a056d3d6e28ff8f83d7dd2381/neoteroi_mkdocs-1.2.0-py3-none-any.whl", hash = "sha256:91b6aa95a4e46c9bb93e00e021d2044cb0c7d80c0b4600434ff8f440d613a0a8", size = 38485, upload-time = "2025-11-23T10:49:22.149Z" }, +] + [[package]] name = "nodeenv" version = "1.10.0" @@ -1659,11 +1671,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -1734,6 +1746,8 @@ cli = [ [package.dev-dependencies] dev = [ + { name = "grpcio" }, + { name = "grpcio-tools" }, { name = "hatch" }, { name = "hatch-protobuf" }, { name = "mdformat-gfm" }, @@ -1749,6 +1763,7 @@ dev = [ ] docs = [ { name = "cyclopts", extra = ["mkdocs"] }, + { name = "essentials-openapi" }, { name = "hatch-mkdocs" }, { name = "markdown-callouts" }, { name = "markdown-gfm-admonition" }, @@ -1759,6 +1774,7 @@ docs = [ { name = "mkdocs-git-revision-date-localized-plugin" }, { name = "mkdocs-material", extra = ["imaging", "recommended"] }, { name = "mkdocstrings", extra = ["python"] }, + { name = "neoteroi-mkdocs" }, { name = "pymdown-extensions" }, ] @@ -1783,6 +1799,8 @@ provides-extras = ["all", "cli"] [package.metadata.requires-dev] dev = [ + { name = "grpcio", specifier = "==1.78.0" }, + { name = "grpcio-tools", specifier = "==1.78.0" }, { name = "hatch", specifier = ">=1.16.3" }, { name = "hatch-protobuf", specifier = ">=0.5.0" }, { name = "mdformat-gfm", specifier = ">=1.0.0" }, @@ -1798,6 +1816,7 @@ dev = [ ] docs = [ { name = "cyclopts", extras = ["mkdocs"], specifier = ">=4.5.1" }, + { name = "essentials-openapi", git = "https://github.com/dcode/essentials-openapi?branch=main" }, { name = "hatch-mkdocs", specifier = ">=0.1.0" }, { name = "markdown-callouts", specifier = ">=0.4.0" }, { name = "markdown-gfm-admonition", specifier = ">=0.3.0" }, @@ -1808,6 +1827,7 @@ docs = [ { name = "mkdocs-git-revision-date-localized-plugin", specifier = ">=1.5.1" }, { name = "mkdocs-material", extras = ["imaging", "recommended"], specifier = ">=9.7.1" }, { name = "mkdocstrings", extras = ["python"], specifier = ">=1.0.3" }, + { name = "neoteroi-mkdocs", specifier = ">=1.2.0" }, { name = "pymdown-extensions", specifier = ">=10.20.1" }, ] @@ -1929,16 +1949,16 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.12.0" +version = "2.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, ] [[package]] @@ -2055,15 +2075,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "10.20.1" +version = "10.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/6c/9e370934bfa30e889d12e61d0dae009991294f40055c238980066a7fbd83/pymdown_extensions-10.20.1.tar.gz", hash = "sha256:e7e39c865727338d434b55f1dd8da51febcffcaebd6e1a0b9c836243f660740a", size = 852860, upload-time = "2026-01-24T05:56:56.758Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl", hash = "sha256:24af7feacbca56504b313b7b418c4f5e1317bb5fea60f03d57be7fcc40912aa0", size = 268768, upload-time = "2026-01-24T05:56:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" }, ] [[package]] @@ -2077,18 +2097,18 @@ wheels = [ [[package]] name = "pyrefly" -version = "0.50.1" +version = "0.54.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/74/f59a827418a74d5163d600db0e99b29a81cc7265ce62694dbfa0407bd95c/pyrefly-0.50.1.tar.gz", hash = "sha256:1859f36fb1dc4a903ba2298442c224dfadcda7fce5691aebd6bbc21c5f703299", size = 4901970, upload-time = "2026-01-29T00:10:06.42Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/44/c10b16a302fda90d0af1328f880b232761b510eab546616a7be2fdf35a57/pyrefly-0.54.0.tar.gz", hash = "sha256:c6663be64d492f0d2f2a411ada9f28a6792163d34133639378b7f3dd9a8dca94", size = 5098893, upload-time = "2026-02-23T15:44:35.111Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/6c/7135b5b2a4d8827b37d5bce0255cf993e4f418566810bbcd69b1c69b7acd/pyrefly-0.50.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:16ea4052b3df75206f5677a87ca7ee6c4c3a92b086081250206e98c60d85e7a8", size = 11832985, upload-time = "2026-01-29T00:09:43.739Z" }, - { url = "https://files.pythonhosted.org/packages/0d/a0/34a40589a89d859db0d8926d60c2e30748a0fbc52d139269344b525d0c6d/pyrefly-0.50.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:aac15d38f3ad548b2cd50a7fc054241743e32753a32872fe5d4bb88982d29b35", size = 11412746, upload-time = "2026-01-29T00:09:46.843Z" }, - { url = "https://files.pythonhosted.org/packages/ee/2f/6b25c59e4ee7b55cc7bbb536c625711a9c3d9de3bb781429ae0239998747/pyrefly-0.50.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:603c4f7893eba4f8472523cfceb41f200a30ae89c0af90f4ba03de4eebf9732b", size = 32329171, upload-time = "2026-01-29T00:09:49.561Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/1c343197688d63052bf12fac41ed820d1e73302b810b2f77e20d30c0c6e3/pyrefly-0.50.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d2408be4b1f618ef80855042d6a3599128546a067654878002e34fdd56bfc54", size = 34591076, upload-time = "2026-01-29T00:09:52.559Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4a/fad923b203cba9efd60f2d5d81040b87270af426c7cff9197652f3501e25/pyrefly-0.50.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9483e700ec0e156b249c2805896833c1393eda787f0b83ab430084c2723fec7e", size = 35705744, upload-time = "2026-01-29T00:09:55.693Z" }, - { url = "https://files.pythonhosted.org/packages/db/36/bc1a814d4ad19d7aed5638a0ef40787d3a0ae8ef6f1d08e7fdd909999039/pyrefly-0.50.1-py3-none-win32.whl", hash = "sha256:68ff5eac338bfc74469f67ff79cc3500e826b51a753bba349ac2cfcb5705b32b", size = 10832356, upload-time = "2026-01-29T00:09:58.494Z" }, - { url = "https://files.pythonhosted.org/packages/c6/4c/26e21c50c6bc305c86890b7c3865c3f9476356478c757c98717e83eda5ac/pyrefly-0.50.1-py3-none-win_amd64.whl", hash = "sha256:ef9b5644cfef560fefe3e5093a78128f17d8d055eb01c5306e88644063c37a38", size = 11590948, upload-time = "2026-01-29T00:10:01.193Z" }, - { url = "https://files.pythonhosted.org/packages/3d/6a/2ed28e2e96421e8b214d8b996cce5f7a1f8eecc9924a56d1ff4af8d0f784/pyrefly-0.50.1-py3-none-win_arm64.whl", hash = "sha256:b813120b2de86122933c424ebdb8e905e13191e72fca6403ea75ca94154eb898", size = 11126993, upload-time = "2026-01-29T00:10:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/8fdcdb4e55f0227fdd9f6abce36b619bab1ecb0662b83b66adc8cba3c788/pyrefly-0.54.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:58a3f092b6dc25ef79b2dc6c69a40f36784ca157c312bfc0baea463926a9db6d", size = 12223973, upload-time = "2026-02-23T15:44:14.278Z" }, + { url = "https://files.pythonhosted.org/packages/90/35/c2aaf87a76003ad27b286594d2e5178f811eaa15bfe3d98dba2b47d56dd1/pyrefly-0.54.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:615081414106dd95873bc39c3a4bed68754c6cc24a8177ac51d22f88f88d3eb3", size = 11785585, upload-time = "2026-02-23T15:44:17.468Z" }, + { url = "https://files.pythonhosted.org/packages/c4/4a/ced02691ed67e5a897714979196f08ad279ec7ec7f63c45e00a75a7f3c0e/pyrefly-0.54.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbcaf20f5fe585079079a95205c1f3cd4542d17228cdf1df560288880623b70", size = 33381977, upload-time = "2026-02-23T15:44:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ce/72a117ed437c8f6950862181014b41e36f3c3997580e29b772b71e78d587/pyrefly-0.54.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d5da116c0d34acfbd66663addd3ca8aa78a636f6692a66e078126d3620a883", size = 35962821, upload-time = "2026-02-23T15:44:22.357Z" }, + { url = "https://files.pythonhosted.org/packages/85/de/89013f5ae0a35d2b6b01274a92a35ee91431ea001050edf0a16748d39875/pyrefly-0.54.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef3ac27f1a4baaf67aead64287d3163350844794aca6315ad1a9650b16ec26a", size = 38496689, upload-time = "2026-02-23T15:44:25.236Z" }, + { url = "https://files.pythonhosted.org/packages/9f/9a/33b097c7bf498b924742dca32dd5d9c6a3fa6c2b52b63a58eb9e1980ca89/pyrefly-0.54.0-py3-none-win32.whl", hash = "sha256:7d607d72200a8afbd2db10bfefb40160a7a5d709d207161c21649cedd5cfc09a", size = 11295268, upload-time = "2026-02-23T15:44:27.551Z" }, + { url = "https://files.pythonhosted.org/packages/d4/21/9263fd1144d2a3d7342b474f183f7785b3358a1565c864089b780110b933/pyrefly-0.54.0-py3-none-win_amd64.whl", hash = "sha256:fd416f04f89309385696f685bd5c9141011f18c8072f84d31ca20c748546e791", size = 12081810, upload-time = "2026-02-23T15:44:29.461Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5b/fad062a196c064cbc8564de5b2f4d3cb6315f852e3b31e8a1ce74c69a1ea/pyrefly-0.54.0-py3-none-win_arm64.whl", hash = "sha256:f06ab371356c7b1925e0bffe193b738797e71e5dbbff7fb5a13f90ee7521211d", size = 11564930, upload-time = "2026-02-23T15:44:33.053Z" }, ] [[package]] @@ -2133,6 +2153,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -2251,29 +2284,29 @@ wheels = [ [[package]] name = "responses" -version = "0.25.8" +version = "0.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "requests" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/95/89c054ad70bfef6da605338b009b2e283485835351a9935c7bfbfaca7ffc/responses-0.25.8.tar.gz", hash = "sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4", size = 79320, upload-time = "2025-08-08T19:01:46.709Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/b4/b7e040379838cc71bf5aabdb26998dfbe5ee73904c92c1c161faf5de8866/responses-0.26.0.tar.gz", hash = "sha256:c7f6923e6343ef3682816ba421c006626777893cb0d5e1434f674b649bac9eb4", size = 81303, upload-time = "2026-02-19T14:38:05.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/4c/cc276ce57e572c102d9542d383b2cfd551276581dc60004cb94fe8774c11/responses-0.25.8-py3-none-any.whl", hash = "sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c", size = 34769, upload-time = "2025-08-08T19:01:45.018Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/7f73d05b556da048923e31a0cc878f03be7c5425ed1f268082255c75d872/responses-0.26.0-py3-none-any.whl", hash = "sha256:03ec4409088cd5c66b71ecbbbd27fe2c58ddfad801c66203457b3e6a04868c37", size = 35099, upload-time = "2026-02-19T14:38:03.847Z" }, ] [[package]] name = "rich" -version = "14.3.1" +version = "14.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/84/4831f881aa6ff3c976f6d6809b58cdfa350593ffc0dc3c58f5f6586780fb/rich-14.3.1.tar.gz", hash = "sha256:b8c5f568a3a749f9290ec6bddedf835cec33696bfc1e48bcfecb276c7386e4b8", size = 230125, upload-time = "2026-01-24T21:40:44.847Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, ] [[package]] @@ -2300,28 +2333,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +version = "0.15.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/3b/20d9a0bc954d51b63f20cf710cf506bfe675d1e6138139342dd5ccc90326/ruff-0.15.3.tar.gz", hash = "sha256:78757853320d8ddb9da24e614ef69a37bcbcfd477e5a6435681188d4bce4eaa1", size = 4569031, upload-time = "2026-02-26T15:39:38.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/00/c544ab1d70f86dc50a2f2a8e1262e5af5025897ccd820415f559f9f2f63f/ruff-0.15.3-py3-none-linux_armv6l.whl", hash = "sha256:f7df0fd6f889a8d8de2ddb48a9eb55150954400f2157ea15b21a2f49ecaaf988", size = 10444066, upload-time = "2026-02-26T15:39:47.708Z" }, + { url = "https://files.pythonhosted.org/packages/fb/15/9dee3f4e891261adbd690f8c6f075418a7cd76e845601b00a0da2ae2ad6e/ruff-0.15.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0198b5445197d443c3bbf2cc358f4bd477fb3951e3c7f2babc13e9bb490614a8", size = 10853125, upload-time = "2026-02-26T15:40:18.943Z" }, + { url = "https://files.pythonhosted.org/packages/88/ba/fc5aeda852c89faf821d36c951df866117342e88439e1b1e1e762a07b7fd/ruff-0.15.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:adf95b5be57b25fbbbc07cd68d37414bee8729e807ad0217219558027186967e", size = 10180833, upload-time = "2026-02-26T15:40:13.282Z" }, + { url = "https://files.pythonhosted.org/packages/06/87/e2f80a39164476fac4d45752a0d4721d6645f40b7f851e48add12af9947e/ruff-0.15.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b56dbd9cd86489ccbad96bb58fa4c958342b5510fdeb60ea13d9d3566bd845c", size = 10536806, upload-time = "2026-02-26T15:40:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/fd/89/2e5bf0ed30ea3778460ea4d8cc6cb4d88ba96d9732d2c0cc33349cd65196/ruff-0.15.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6f263ce511871955d8c5401b62c7e863988ea4d0527aa0a3b1b7ecff4d4abc4", size = 10276093, upload-time = "2026-02-26T15:39:44.654Z" }, + { url = "https://files.pythonhosted.org/packages/82/cb/318206d778c7f42917ca7b0f9436cf27652d1731fe434d3c9990c4a611fa/ruff-0.15.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90fa1bed82ffede5768232b9bd23212c547ab7cd74c752007ecade1d895ee1a", size = 11051593, upload-time = "2026-02-26T15:39:35.157Z" }, + { url = "https://files.pythonhosted.org/packages/58/8f/65ee4c1b88e49dd4c0a3fc43e81832536c7942f0c702b6f3d25db0f95d6c/ruff-0.15.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e9d53760b7061ddbe5ea9e25381332c607fc14c40bde78f8a25392a93a68d74", size = 11885820, upload-time = "2026-02-26T15:39:59.504Z" }, + { url = "https://files.pythonhosted.org/packages/db/04/d4261f6729ad9a356bc6e3223ba297acf3b66118cef4795b4a8953b255ff/ruff-0.15.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec90e3b78c56c4acca4264d371dd48e29215ecb673cc2fa3c4b799b72050e491", size = 11340583, upload-time = "2026-02-26T15:39:50.781Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/490f38b2bc104e0fdc9496c2a66a48fb2d24a01de46ba0c60c4f6c4d4590/ruff-0.15.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ce448fd395f822e34c8f6f7dfcd84b6726340082950858f92c4daa6baf8915", size = 11160701, upload-time = "2026-02-26T15:40:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/ad/25/eae9cb7b6c28b425ed8cbe797da89c78146071102181ba74c4cdfd06bbeb/ruff-0.15.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14f7d763962d385f75b9b3b57fcc5661c56c20d8b1ddc9f5c881b5fa0ba499fa", size = 11111482, upload-time = "2026-02-26T15:39:56.462Z" }, + { url = "https://files.pythonhosted.org/packages/95/18/16d0b5ef143cb9e52724f18cbccb4b3c5cd4d4e2debbd95e2be3aeb64c9e/ruff-0.15.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b57084e3a3d65418d376c7023711c37cce023cd2fb038a76ba15ee21f3c2c2ee", size = 10497151, upload-time = "2026-02-26T15:40:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/1829314241ddba07c54a742ab387da343fe56a0267a6b6498f3e2ae99821/ruff-0.15.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d567523ff7dcf3112b0f71231d18c3506dd06943359476ee64dea0f9c8f63976", size = 10281955, upload-time = "2026-02-26T15:40:16.033Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/80a4ec4bd3cf58ca9b49dccf2bd232b520db14184912fb7e0eb6f3ecc484/ruff-0.15.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4223088d255bf31a50b6640445b39f668164d64c23e5fa403edfb1e0b11122e5", size = 10766613, upload-time = "2026-02-26T15:40:21.55Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/fe016b862295dc57499997e7f2edc58119469b210f4f03ccb763fa65f130/ruff-0.15.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32399ddae088970b2db6efd8d3f49981375cb828075359b6c088ed1fe63d64e1", size = 11262113, upload-time = "2026-02-26T15:39:41.5Z" }, + { url = "https://files.pythonhosted.org/packages/42/b1/77dcd05940388d9ba3de03ac4b8b598826d57935728071e1be9f2ef5b714/ruff-0.15.3-py3-none-win32.whl", hash = "sha256:1f1eb95ff614351e3a89a862b6d94e6c42c170e61916e1f20facd6c38477f5f3", size = 10509423, upload-time = "2026-02-26T15:40:05.217Z" }, + { url = "https://files.pythonhosted.org/packages/29/d5/76aab0fabbd54e8c77d02fcff2494906ba85b539d22aa9b7124f7100f008/ruff-0.15.3-py3-none-win_amd64.whl", hash = "sha256:2b22dffe5f5e1e537097aa5208684f069e495f980379c4491b1cfb198a444d0c", size = 11637739, upload-time = "2026-02-26T15:39:53.951Z" }, + { url = "https://files.pythonhosted.org/packages/f2/61/9b4e3682dfd26054321e1b2fdb67a51361dd6ec2fb63f2b50d711f8832ae/ruff-0.15.3-py3-none-win_arm64.whl", hash = "sha256:82443c14d694d4cbd9e598ede27ef5d6f08389ccad91c933be775ea2f4e66f76", size = 10957794, upload-time = "2026-02-26T15:40:08.045Z" }, ] [[package]] @@ -2530,11 +2562,11 @@ wheels = [ [[package]] name = "types-protobuf" -version = "6.32.1.20251210" +version = "6.32.1.20260221" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/59/c743a842911887cd96d56aa8936522b0cd5f7a7f228c96e81b59fced45be/types_protobuf-6.32.1.20251210.tar.gz", hash = "sha256:c698bb3f020274b1a2798ae09dc773728ce3f75209a35187bd11916ebfde6763", size = 63900, upload-time = "2025-12-10T03:14:25.451Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/e2/9aa4a3b2469508bd7b4e2ae11cbedaf419222a09a1b94daffcd5efca4023/types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e", size = 64408, upload-time = "2026-02-21T03:55:13.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/43/58e75bac4219cbafee83179505ff44cae3153ec279be0e30583a73b8f108/types_protobuf-6.32.1.20251210-py3-none-any.whl", hash = "sha256:2641f78f3696822a048cfb8d0ff42ccd85c25f12f871fbebe86da63793692140", size = 77921, upload-time = "2025-12-10T03:14:24.477Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e8/1fd38926f9cf031188fbc5a96694203ea6f24b0e34bd64a225ec6f6291ba/types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4", size = 77956, upload-time = "2026-02-21T03:55:12.894Z" }, ] [[package]] @@ -2590,42 +2622,42 @@ wheels = [ [[package]] name = "uv" -version = "0.9.28" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/7d/005ab1cab03ca928cef75b424284d14d62c5f18775cf8114a63f210a0c9c/uv-0.9.28.tar.gz", hash = "sha256:253c04b26fb40f74c56ead12ce83db3c018bdefde1fcd1a542bcb88fdca4189c", size = 3834456, upload-time = "2026-01-29T20:15:49.794Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/77/dc/e70698756f1bb74c88bf1eaea63a114a580a38f296ea1567a01db9007490/uv-0.9.28-py3-none-linux_armv6l.whl", hash = "sha256:aede961243bb2c0ca09d0e04ea0bf580d7128dd3b14661b79d133be9a5b69894", size = 22040477, upload-time = "2026-01-29T20:16:11.24Z" }, - { url = "https://files.pythonhosted.org/packages/f0/ed/77294752bf722e1d6b666bd6592b6ac975dabcf1fde49e98a75cac23d45c/uv-0.9.28-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fe9aa2822d24f6ecec035a06dfdd1fbed570ed40b83a864e71714bad37ddfd3", size = 21025194, upload-time = "2026-01-29T20:15:36.504Z" }, - { url = "https://files.pythonhosted.org/packages/b1/a9/78f2da6217c1bbae3371d68515fe747e1160bab049d6898a03e517802573/uv-0.9.28-py3-none-macosx_11_0_arm64.whl", hash = "sha256:58a36bf623c6d36b3d60d3c76eeb7275199d607938786e927d40ce213980059d", size = 19783994, upload-time = "2026-01-29T20:16:19.451Z" }, - { url = "https://files.pythonhosted.org/packages/14/79/55639c444e91b96c81c326d39a0a06551d2e611be0cc917b89010ba9ba88/uv-0.9.28-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:4d479a1d387b1464ad2c1f960b0b26a9ac1dfba67ea2c6789e9643fe6d1e7b9a", size = 21568230, upload-time = "2026-01-29T20:15:39.35Z" }, - { url = "https://files.pythonhosted.org/packages/14/2e/95d7992c0a39981cfbcf56ff8f069c09e0567feb0e70cb8b52bc8a2947a0/uv-0.9.28-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:29eefd4642f55954a2b9a40619cde3d02856300f59b8cf63ed1a161ca0ca9b77", size = 21633679, upload-time = "2026-01-29T20:15:52.363Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/b6778e03714b1f9da095c8bf0f8e5007f4867d9196c1ae8053504ddf2877/uv-0.9.28-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4155496f624deb753f5ddd80fbe3797587c8480d1250e83c9fd816b4b02e3a41", size = 21632238, upload-time = "2026-01-29T20:15:55.003Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f8/0db6ea9fd8f2752a8723a637e3ed881eb212516665ccb2e8066bbea62a52/uv-0.9.28-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dc98e2d6db0dc9a2f65ce4cda6a34283fa80f3fbfff129befdf40ad7a3d1615", size = 22779474, upload-time = "2026-01-29T20:15:33.513Z" }, - { url = "https://files.pythonhosted.org/packages/54/88/ef70e04113393f4e19e67281cae9f83c82030d14eb4eb811bda83fcd8f44/uv-0.9.28-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d267280b3878aa6ef8e00bff1f11bf61580d0a8bbb69fa95b5d3526d00f77485", size = 24124596, upload-time = "2026-01-29T20:16:05.062Z" }, - { url = "https://files.pythonhosted.org/packages/81/07/9fda9149bc57e79bde5f00cabcef323a68817c1cca9d44e2aa08d18c6b52/uv-0.9.28-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba2a320ff77996468789f4b2c573fd766f9330717c440335af8790043b2b3703", size = 23655701, upload-time = "2026-01-29T20:16:07.735Z" }, - { url = "https://files.pythonhosted.org/packages/18/b5/1f1e910ca1a0aca0d0ede3ba0eaca867fd3c575f44b2fe103a5c9511f071/uv-0.9.28-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c8fd93c5bee89ed88908215f81a3baa0d2a98e35caf995b97e9c226c1c29340", size = 22856456, upload-time = "2026-01-29T20:16:16.582Z" }, - { url = "https://files.pythonhosted.org/packages/9a/fd/82561751105ed232f1781747bc336b20e8d57ee07b4d2ed3fa6cf2718d71/uv-0.9.28-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b8460a2b624d8ab27cb293a2c9f2393f9efc4e36e0fb886a6c2360e23fb48be", size = 22685296, upload-time = "2026-01-29T20:16:13.857Z" }, - { url = "https://files.pythonhosted.org/packages/a9/e4/b905daff0bfde347c49b9c9ba31d09d504c4b84f2749a07db77a9da16dba/uv-0.9.28-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3798c486ec627bbd7ca41fa219e997ad403b1f803371edf5c8e75893e46161ba", size = 21669854, upload-time = "2026-01-29T20:15:30.277Z" }, - { url = "https://files.pythonhosted.org/packages/9a/01/9a90574fe7290c775332e54f163cba58c767445b655e97646708f9c66050/uv-0.9.28-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e479cc5cbfd72ebdbea3c909d0ab997162e0dfa1ee622b50e2f9dc8d07d4eee3", size = 22388944, upload-time = "2026-01-29T20:15:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/cc35014bab3c17b4fe8f6bae84e640ce64d9bb4c8a24694a935e0c0af538/uv-0.9.28-py3-none-musllinux_1_1_i686.whl", hash = "sha256:97d61cdf2436e83a0f188d55d1974e46679d9a787c3a54cb0a40de717c6bf435", size = 22073327, upload-time = "2026-01-29T20:15:58.119Z" }, - { url = "https://files.pythonhosted.org/packages/26/cd/e848570be5c5be4e139b90237cc64f68d5d51e8e92c40a5ac7cf0c34ad4a/uv-0.9.28-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:cbfa56c833caa37b1f14166327fcaf8aa87290451406921eb07296ffef17fef1", size = 22915580, upload-time = "2026-01-29T20:15:42.468Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2a/6c3d839ea289bf8509da32f703a47accd63ab409b33627728aebcd2a1b65/uv-0.9.28-py3-none-win32.whl", hash = "sha256:d5cb780d5b821f837f63e7fd14e2bf75f01824b4575a1e89639888771bfd9efd", size = 20856809, upload-time = "2026-01-29T20:15:45.141Z" }, - { url = "https://files.pythonhosted.org/packages/06/a8/d72229dd90d1e5a3c8368d51a70219018d579380945e67c8dcffbe8e53c0/uv-0.9.28-py3-none-win_amd64.whl", hash = "sha256:203ab59710c0c1b3c5ecc684f9cfc9264340a69c8706aaa8aea75415779f0d74", size = 23447461, upload-time = "2026-01-29T20:16:22.563Z" }, - { url = "https://files.pythonhosted.org/packages/23/df/5852eb0c59e5224f4cb0323906efae348f782f8a7f1069197e7cf6ec9b74/uv-0.9.28-py3-none-win_arm64.whl", hash = "sha256:c29406e1dc6b1b312c478c76b42b9f94b684855a4c001901b5488bab6ccf4ec7", size = 21860859, upload-time = "2026-01-29T20:16:00.764Z" }, +version = "0.10.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/53/7a4274dad70b1d17efb99e36d45fc1b5e4e1e531b43247e518604394c761/uv-0.10.6.tar.gz", hash = "sha256:de86e5e1eb264e74a20fccf56889eea2463edb5296f560958e566647c537b52e", size = 3921763, upload-time = "2026-02-25T00:26:27.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/f9/faf599c6928dc00d941629260bef157dadb67e8ffb7f4b127b8601f41177/uv-0.10.6-py3-none-linux_armv6l.whl", hash = "sha256:2b46ad78c86d68de6ec13ffaa3a8923467f757574eeaf318e0fce0f63ff77d7a", size = 22412946, upload-time = "2026-02-25T00:26:10.826Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8f/82dd6aa8acd2e1b1ba12fd49210bd19843383538e0e63e8d7a23a7d39d93/uv-0.10.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a1d9873eb26cbef9138f8c52525bc3fd63be2d0695344cdcf84f0dc2838a6844", size = 21524262, upload-time = "2026-02-25T00:27:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/3b/48/5767af19db6f21176e43dfde46ea04e33c49ba245ac2634e83db15d23c8f/uv-0.10.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5a62cdf5ba356dcc792b960e744d67056b0e6d778ce7381e1d78182357bd82e8", size = 20184248, upload-time = "2026-02-25T00:26:20.281Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/13c2fcdb776ae78b5c22eb2d34931bb3ef9bd71b9578b8fa7af8dd7c11c4/uv-0.10.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:b70a04d51e2239b3aee0e4d4ed9af18c910360155953017cecded5c529588e65", size = 22049300, upload-time = "2026-02-25T00:26:07.039Z" }, + { url = "https://files.pythonhosted.org/packages/6f/43/348e2c378b3733eba15f6144b35a8c84af5c884232d6bbed29e256f74b6f/uv-0.10.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:2b622059a1ae287f8b995dcb6f5548de83b89b745ff112801abbf09e25fd8fa9", size = 22030505, upload-time = "2026-02-25T00:26:46.171Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3f/dcec580099bc52f73036bfb09acb42616660733de1cc3f6c92287d2c7f3e/uv-0.10.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f43db1aa80776386646453c07d5590e1ae621f031a2afe6efba90f89c34c628c", size = 22041360, upload-time = "2026-02-25T00:26:53.725Z" }, + { url = "https://files.pythonhosted.org/packages/2c/96/f70abe813557d317998806517bb53b3caa5114591766db56ae9cc142ff39/uv-0.10.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ca8a26694ba7d0ae902f11054734805741f2b080fe8397401b80c99264edab6", size = 23309916, upload-time = "2026-02-25T00:27:12.99Z" }, + { url = "https://files.pythonhosted.org/packages/db/1d/d8b955937dd0153b48fdcfd5ff70210d26e4b407188e976df620572534fd/uv-0.10.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f2cddae800d14159a9ccb4ff161648b0b0d1b31690d9c17076ec00f538c52ac", size = 24191174, upload-time = "2026-02-25T00:26:30.051Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/3d0669d65bf4a270420d70ca0670917ce5c25c976c8b0acd52465852509b/uv-0.10.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:153fcf5375c988b2161bf3a6a7d9cc907d6bbe38f3cb16276da01b2dae4df72c", size = 23320328, upload-time = "2026-02-25T00:26:23.82Z" }, + { url = "https://files.pythonhosted.org/packages/85/f2/f2ccc2196fd6cf1321c2e8751a96afabcbc9509b184c671ece3e804effda/uv-0.10.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27f2d135d4533f88537ecd254c72dfd25311d912da8649d15804284d70adb93", size = 23229798, upload-time = "2026-02-25T00:26:50.12Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b9/1008266a041e8a55430a92aef8ecc58aaaa7eb7107a26cf4f7c127d14363/uv-0.10.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:dd993ec2bf5303a170946342955509559763cf8dcfe334ec7bb9f115a0f86021", size = 22143661, upload-time = "2026-02-25T00:26:42.507Z" }, + { url = "https://files.pythonhosted.org/packages/93/e4/1f8de7da5f844b4c9eafa616e262749cd4e3d9c685190b7967c4681869da/uv-0.10.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8529e4d4aac40b4e7588177321cb332cc3309d36d7cc482470a1f6cfe7a7e14a", size = 22888045, upload-time = "2026-02-25T00:26:15.935Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2b/03b840dd0101dc69ef6e83ceb2e2970e4b4f118291266cf3332a4b64092c/uv-0.10.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:ed9e16453a5f73ee058c566392885f445d00534dc9e754e10ab9f50f05eb27a5", size = 22549404, upload-time = "2026-02-25T00:27:05.333Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4e/1ee4d4301874136a4b3bbd9eeba88da39f4bafa6f633b62aef77d8195c56/uv-0.10.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:33e5362039bfa91599df0b7487854440ffef1386ac681ec392d9748177fb1d43", size = 23426872, upload-time = "2026-02-25T00:26:35.01Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e3/e000030118ff1a82ecfc6bd5af70949821edac739975a027994f5b17258f/uv-0.10.6-py3-none-win32.whl", hash = "sha256:fa7c504a1e16713b845d457421b07dd9c40f40d911ffca6897f97388de49df5a", size = 21501863, upload-time = "2026-02-25T00:26:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cc/dd88c9f20c054ef0aea84ad1dd9f8b547463824857e4376463a948983bed/uv-0.10.6-py3-none-win_amd64.whl", hash = "sha256:ecded4d21834b21002bc6e9a2628d21f5c8417fd77a5db14250f1101bcb69dac", size = 23981891, upload-time = "2026-02-25T00:26:38.773Z" }, + { url = "https://files.pythonhosted.org/packages/cf/06/ca117002cd64f6701359253d8566ec7a0edcf61715b4969f07ee41d06f61/uv-0.10.6-py3-none-win_arm64.whl", hash = "sha256:4b5688625fc48565418c56a5cd6c8c32020dbb7c6fb7d10864c2d2c93c508302", size = 22339889, upload-time = "2026-02-25T00:27:00.818Z" }, ] [[package]] name = "virtualenv" -version = "20.36.1" +version = "21.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/4f/d6a5ff3b020c801c808b14e2d2330cdc8ebefe1cdfbc457ecc368e971fec/virtualenv-21.0.0.tar.gz", hash = "sha256:e8efe4271b4a5efe7a4dce9d60a05fd11859406c0d6aa8464f4cf451bc132889", size = 5836591, upload-time = "2026-02-25T20:21:07.691Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, + { url = "https://files.pythonhosted.org/packages/29/d1/3f62e4f9577b28c352c11623a03fb916096d5c131303d4861b4914481b6b/virtualenv-21.0.0-py3-none-any.whl", hash = "sha256:d44e70637402c7f4b10f48491c02a6397a3a187152a70cba0b6bc7642d69fb05", size = 5817167, upload-time = "2026-02-25T20:21:05.476Z" }, ] [[package]]