statuspro-openapi-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. statuspro_openapi_client-0.1.0.dist-info/METADATA +337 -0
  2. statuspro_openapi_client-0.1.0.dist-info/RECORD +74 -0
  3. statuspro_openapi_client-0.1.0.dist-info/WHEEL +4 -0
  4. statuspro_openapi_client-0.1.0.dist-info/entry_points.txt +3 -0
  5. statuspro_openapi_client-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. statuspro_public_api_client/__init__.py +36 -0
  7. statuspro_public_api_client/_logging.py +33 -0
  8. statuspro_public_api_client/api/__init__.py +1 -0
  9. statuspro_public_api_client/api/orders/__init__.py +1 -0
  10. statuspro_public_api_client/api/orders/add_order_comment.py +215 -0
  11. statuspro_public_api_client/api/orders/bulk_update_order_status.py +194 -0
  12. statuspro_public_api_client/api/orders/get_order.py +188 -0
  13. statuspro_public_api_client/api/orders/get_viable_statuses.py +193 -0
  14. statuspro_public_api_client/api/orders/list_orders.py +366 -0
  15. statuspro_public_api_client/api/orders/lookup_order.py +208 -0
  16. statuspro_public_api_client/api/orders/set_order_due_date.py +215 -0
  17. statuspro_public_api_client/api/orders/update_order_status.py +215 -0
  18. statuspro_public_api_client/api/statuses/__init__.py +1 -0
  19. statuspro_public_api_client/api/statuses/get_statuses.py +161 -0
  20. statuspro_public_api_client/api_wrapper/__init__.py +15 -0
  21. statuspro_public_api_client/api_wrapper/_namespace.py +40 -0
  22. statuspro_public_api_client/api_wrapper/_registry.py +43 -0
  23. statuspro_public_api_client/api_wrapper/_resource.py +116 -0
  24. statuspro_public_api_client/client.py +267 -0
  25. statuspro_public_api_client/client_types.py +54 -0
  26. statuspro_public_api_client/domain/__init__.py +33 -0
  27. statuspro_public_api_client/domain/base.py +117 -0
  28. statuspro_public_api_client/domain/converters.py +71 -0
  29. statuspro_public_api_client/domain/order.py +87 -0
  30. statuspro_public_api_client/domain/status.py +30 -0
  31. statuspro_public_api_client/errors.py +16 -0
  32. statuspro_public_api_client/helpers/__init__.py +21 -0
  33. statuspro_public_api_client/helpers/base.py +26 -0
  34. statuspro_public_api_client/helpers/orders.py +78 -0
  35. statuspro_public_api_client/helpers/statuses.py +37 -0
  36. statuspro_public_api_client/log_setup.py +99 -0
  37. statuspro_public_api_client/models/__init__.py +53 -0
  38. statuspro_public_api_client/models/add_order_comment_request.py +68 -0
  39. statuspro_public_api_client/models/bulk_status_update_request.py +124 -0
  40. statuspro_public_api_client/models/bulk_status_update_response.py +72 -0
  41. statuspro_public_api_client/models/customer.py +74 -0
  42. statuspro_public_api_client/models/error_response.py +58 -0
  43. statuspro_public_api_client/models/history_item.py +171 -0
  44. statuspro_public_api_client/models/list_orders_financial_status_item.py +15 -0
  45. statuspro_public_api_client/models/list_orders_fulfillment_status_item.py +11 -0
  46. statuspro_public_api_client/models/locale_translation.py +66 -0
  47. statuspro_public_api_client/models/mail_log.py +82 -0
  48. statuspro_public_api_client/models/message_response.py +58 -0
  49. statuspro_public_api_client/models/order_list_item.py +180 -0
  50. statuspro_public_api_client/models/order_list_meta.py +120 -0
  51. statuspro_public_api_client/models/order_list_response.py +81 -0
  52. statuspro_public_api_client/models/order_response.py +220 -0
  53. statuspro_public_api_client/models/progress_timeline_item.py +93 -0
  54. statuspro_public_api_client/models/set_due_date_request.py +95 -0
  55. statuspro_public_api_client/models/status.py +190 -0
  56. statuspro_public_api_client/models/status_definition.py +82 -0
  57. statuspro_public_api_client/models/status_translations.py +62 -0
  58. statuspro_public_api_client/models/update_order_status_request.py +92 -0
  59. statuspro_public_api_client/models/validation_error_response.py +81 -0
  60. statuspro_public_api_client/models/validation_error_response_errors.py +54 -0
  61. statuspro_public_api_client/models/viable_status.py +82 -0
  62. statuspro_public_api_client/models_pydantic/__init__.py +122 -0
  63. statuspro_public_api_client/models_pydantic/_auto_registry.py +115 -0
  64. statuspro_public_api_client/models_pydantic/_base.py +349 -0
  65. statuspro_public_api_client/models_pydantic/_generated/__init__.py +53 -0
  66. statuspro_public_api_client/models_pydantic/_generated/errors.py +24 -0
  67. statuspro_public_api_client/models_pydantic/_generated/orders.py +136 -0
  68. statuspro_public_api_client/models_pydantic/_generated/statuses.py +25 -0
  69. statuspro_public_api_client/models_pydantic/_registry.py +171 -0
  70. statuspro_public_api_client/models_pydantic/converters.py +184 -0
  71. statuspro_public_api_client/py.typed +1 -0
  72. statuspro_public_api_client/statuspro-openapi.yaml +859 -0
  73. statuspro_public_api_client/statuspro_client.py +1156 -0
  74. statuspro_public_api_client/utils.py +290 -0
@@ -0,0 +1,54 @@
1
+ """Contains some shared types for properties"""
2
+
3
+ from collections.abc import Mapping, MutableMapping
4
+ from http import HTTPStatus
5
+ from typing import IO, BinaryIO, Literal, TypeVar
6
+
7
+ from attrs import define
8
+
9
+
10
+ class Unset:
11
+ def __bool__(self) -> Literal[False]:
12
+ return False
13
+
14
+
15
+ UNSET: Unset = Unset()
16
+
17
+ # The types that `httpx.Client(files=)` can accept, copied from that library.
18
+ FileContent = IO[bytes] | bytes | str
19
+ FileTypes = (
20
+ # (filename, file (or bytes), content_type)
21
+ tuple[str | None, FileContent, str | None]
22
+ # (filename, file (or bytes), content_type, headers)
23
+ | tuple[str | None, FileContent, str | None, Mapping[str, str]]
24
+ )
25
+ RequestFiles = list[tuple[str, FileTypes]]
26
+
27
+
28
+ @define
29
+ class File:
30
+ """Contains information for file uploads"""
31
+
32
+ payload: BinaryIO
33
+ file_name: str | None = None
34
+ mime_type: str | None = None
35
+
36
+ def to_tuple(self) -> FileTypes:
37
+ """Return a tuple representation that httpx will accept for multipart/form-data"""
38
+ return self.file_name, self.payload, self.mime_type
39
+
40
+
41
+ T = TypeVar("T")
42
+
43
+
44
+ @define
45
+ class Response[T]:
46
+ """A response from an endpoint"""
47
+
48
+ status_code: HTTPStatus
49
+ content: bytes
50
+ headers: MutableMapping[str, str]
51
+ parsed: T | None
52
+
53
+
54
+ __all__ = ["UNSET", "File", "FileTypes", "RequestFiles", "Response", "Unset"]
@@ -0,0 +1,33 @@
1
+ """Pydantic domain models for StatusPro entities.
2
+
3
+ Hand-written domain models representing business entities from the StatusPro API,
4
+ separate from the generated attrs API request/response models. Use these for
5
+ business logic, validation, and ergonomic access to API data.
6
+
7
+ Example:
8
+ ```python
9
+ from statuspro_public_api_client import StatusProClient
10
+ from statuspro_public_api_client.domain import Order
11
+
12
+ async with StatusProClient() as client:
13
+ orders = await client.orders.list(per_page=50)
14
+ for order in orders:
15
+ print(f"{order.name}: {order.status.name}")
16
+ ```
17
+ """
18
+
19
+ from .base import StatusProBaseModel
20
+ from .converters import to_unset, unwrap_unset
21
+ from .order import Customer, Order, OrderStatus, PageMeta
22
+ from .status import Status
23
+
24
+ __all__ = [
25
+ "Customer",
26
+ "Order",
27
+ "OrderStatus",
28
+ "PageMeta",
29
+ "Status",
30
+ "StatusProBaseModel",
31
+ "to_unset",
32
+ "unwrap_unset",
33
+ ]
@@ -0,0 +1,117 @@
1
+ """Base domain model for StatusPro entities.
2
+
3
+ This module provides the foundation for Pydantic domain models that represent
4
+ business entities from the StatusPro Manufacturing ERP system.
5
+
6
+ Domain models are separate from the generated API request/response models and
7
+ are optimized for:
8
+ - ETL and data processing
9
+ - Business logic
10
+ - Data validation
11
+ - JSON schema generation
12
+ - Clean, ergonomic APIs
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any, ClassVar
18
+
19
+ from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
20
+
21
+
22
+ class StatusProBaseModel(BaseModel):
23
+ """Base class for all Pydantic domain models.
24
+
25
+ Provides:
26
+ - Immutability by default (frozen=True)
27
+ - Automatic validation
28
+ - JSON schema generation
29
+ - Easy serialization for ETL
30
+ - Common timestamp fields
31
+
32
+ Example:
33
+ ```python
34
+ class OrderDomain(StatusProBaseModel):
35
+ id: int
36
+ name: str
37
+ order_number: str | None = None
38
+
39
+ @computed_field
40
+ @property
41
+ def display_name(self) -> str:
42
+ return f"Order {self.name} (#{self.order_number})"
43
+ ```
44
+ """
45
+
46
+ model_config = ConfigDict(
47
+ frozen=True, # Immutable by default
48
+ validate_assignment=True, # Validate on updates (if unfrozen)
49
+ arbitrary_types_allowed=True, # Allow datetime, etc.
50
+ str_strip_whitespace=True, # Clean string inputs
51
+ json_schema_extra={
52
+ "source": "StatusPro Manufacturing ERP",
53
+ "version": "v1",
54
+ },
55
+ )
56
+
57
+ # Metadata about the source model
58
+ _source_model: ClassVar[str] = "attrs"
59
+ _api_version: ClassVar[str] = "v1"
60
+
61
+ # Common timestamp fields (most StatusPro entities have these)
62
+ # Using AwareDatetime to match generated Pydantic models and ensure timezone awareness
63
+ created_at: AwareDatetime | None = Field(
64
+ None, description="Timestamp when entity was created"
65
+ )
66
+ updated_at: AwareDatetime | None = Field(
67
+ None, description="Timestamp when entity was last updated"
68
+ )
69
+ deleted_at: AwareDatetime | None = Field(
70
+ None, description="Timestamp when entity was soft-deleted (if applicable)"
71
+ )
72
+
73
+ def model_dump_for_etl(self) -> dict[str, Any]:
74
+ """Export to ETL-friendly format.
75
+
76
+ Removes None values and uses field aliases for cleaner output.
77
+
78
+ Returns:
79
+ Dictionary with all non-None fields
80
+
81
+ Example:
82
+ ```python
83
+ order = Order(id=123, name="#1188", order_number="1188")
84
+ data = order.model_dump_for_etl()
85
+ # {"id": 123, "name": "#1188", "order_number": "1188"}
86
+ ```
87
+ """
88
+ return self.model_dump(exclude_none=True, by_alias=True)
89
+
90
+ def to_warehouse_json(self) -> str:
91
+ """Export as JSON for data warehouse.
92
+
93
+ Returns:
94
+ JSON string with all non-None fields
95
+
96
+ Example:
97
+ ```python
98
+ order = Order(id=123, name="#1188")
99
+ json_str = order.to_warehouse_json()
100
+ # '{"id":123,"name":"#1188"}'
101
+ ```
102
+ """
103
+ return self.model_dump_json(exclude_none=True, by_alias=True)
104
+
105
+ def to_dict_with_computed(self) -> dict[str, Any]:
106
+ """Export including computed fields.
107
+
108
+ Unlike model_dump(), this includes @computed_field properties.
109
+
110
+ Returns:
111
+ Dictionary with all fields including computed ones
112
+ """
113
+ # Pydantic v2 automatically includes computed fields in model_dump
114
+ return self.model_dump(mode="python", exclude_none=True)
115
+
116
+
117
+ __all__ = ["StatusProBaseModel"]
@@ -0,0 +1,71 @@
1
+ """Generic conversion utilities for attrs API models.
2
+
3
+ These helpers bridge the attrs-based generated models (which use ``UNSET`` for
4
+ unprovided fields) and Pydantic domain models (which use ``None``).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import overload
10
+
11
+ from ..client_types import UNSET, Unset
12
+
13
+
14
+ def to_unset[T](value: T | None) -> T | Unset:
15
+ """Convert None to UNSET sentinel value.
16
+
17
+ Useful when building attrs API request models from optional Pydantic fields,
18
+ where None means "not provided" and should be sent as UNSET to avoid
19
+ overwriting existing values.
20
+
21
+ Args:
22
+ value: Value that might be None
23
+
24
+ Returns:
25
+ The value unchanged if not None, or UNSET if None
26
+
27
+ Example:
28
+ ```python
29
+ from statuspro_public_api_client.domain.converters import to_unset
30
+
31
+ to_unset(42) # 42
32
+ to_unset(None) # UNSET
33
+ to_unset("USD") # "USD"
34
+ ```
35
+ """
36
+ return UNSET if value is None else value
37
+
38
+
39
+ @overload
40
+ def unwrap_unset[T](value: T | Unset | None) -> T | None: ...
41
+
42
+
43
+ @overload
44
+ def unwrap_unset[T](value: T | Unset | None, default: T) -> T: ...
45
+
46
+
47
+ def unwrap_unset[T](value: T | Unset | None, default: T | None = None) -> T | None:
48
+ """Unwrap an Unset or None sentinel value.
49
+
50
+ Args:
51
+ value: Value that might be Unset or None
52
+ default: Default value to return if Unset or None
53
+
54
+ Returns:
55
+ The unwrapped value, or default if value is Unset or None. When a
56
+ non-None default is provided, the return type is narrowed to ``T``
57
+ (never None).
58
+
59
+ Example:
60
+ ```python
61
+ from statuspro_public_api_client.client_types import UNSET
62
+
63
+ unwrap_unset(42) # 42
64
+ unwrap_unset(UNSET) # None
65
+ unwrap_unset(UNSET, 0) # 0
66
+ unwrap_unset(None, 0) # 0
67
+ ```
68
+ """
69
+ if value is None or isinstance(value, Unset):
70
+ return default
71
+ return value
@@ -0,0 +1,87 @@
1
+ """Domain models for StatusPro orders.
2
+
3
+ Hand-written Pydantic models for business logic, separate from the generated
4
+ attrs API models. Shapes mirror the OpenAPI ``Customer``, ``Status``,
5
+ ``OrderListItem``, ``OrderResponse``, and ``OrderListMeta`` schemas.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
11
+
12
+
13
+ class _Frozen(BaseModel):
14
+ """Immutable Pydantic base used for nested types that don't carry timestamps."""
15
+
16
+ model_config = ConfigDict(
17
+ frozen=True,
18
+ str_strip_whitespace=True,
19
+ extra="ignore",
20
+ )
21
+
22
+
23
+ class Customer(_Frozen):
24
+ """Customer details attached to an order."""
25
+
26
+ name: str | None = None
27
+ email: str | None = None
28
+ locale: str | None = None
29
+
30
+
31
+ class OrderStatus(_Frozen):
32
+ """The status currently set on an order (nested ``Status`` schema).
33
+
34
+ Distinct from :class:`Status`, which is a top-level status definition
35
+ returned by ``/statuses`` and includes ``color`` instead of transition
36
+ metadata.
37
+ """
38
+
39
+ is_set: bool | None = None
40
+ code: str | None = Field(None, description="8-char status code, e.g. 'st000002'")
41
+ name: str | None = None
42
+ public_name: str | None = None
43
+ description: str | None = None
44
+ public: bool | None = None
45
+ set_at: AwareDatetime | None = None
46
+ auto_change_at: AwareDatetime | None = None
47
+
48
+
49
+ class Order(_Frozen):
50
+ """An order as returned by ``/orders`` list pages or ``/orders/{id}``."""
51
+
52
+ id: int
53
+ name: str | None = Field(None, description="Display name, e.g. '#1188'")
54
+ order_number: str | None = None
55
+ customer: Customer | None = None
56
+ status: OrderStatus | None = None
57
+ due_date: AwareDatetime | None = None
58
+ due_date_to: AwareDatetime | None = None
59
+ history_count: int | None = Field(
60
+ None,
61
+ description="Number of history entries; only present on list responses",
62
+ )
63
+
64
+
65
+ class PageMeta(_Frozen):
66
+ """Pagination envelope returned alongside list endpoints.
67
+
68
+ Matches the StatusPro ``OrderListMeta`` schema. ``current_page`` and
69
+ ``last_page`` are used by the transport layer to auto-walk pages.
70
+ """
71
+
72
+ current_page: int
73
+ last_page: int
74
+ per_page: int
75
+ total: int
76
+ from_: int | None = Field(None, alias="from")
77
+ to: int | None = None
78
+
79
+ model_config = ConfigDict(
80
+ frozen=True,
81
+ str_strip_whitespace=True,
82
+ extra="ignore",
83
+ populate_by_name=True,
84
+ )
85
+
86
+
87
+ __all__ = ["Customer", "Order", "OrderStatus", "PageMeta"]
@@ -0,0 +1,30 @@
1
+ """Domain model for StatusPro status definitions.
2
+
3
+ Matches the OpenAPI ``StatusDefinition`` schema (returned by ``/statuses``)
4
+ and is structurally identical to ``ViableStatus`` (returned by
5
+ ``/orders/{id}/viable-statuses``).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field
11
+
12
+
13
+ class Status(BaseModel):
14
+ """A top-level status definition from the account's status catalog."""
15
+
16
+ code: str = Field(..., description="8-char code, e.g. 'st000002'")
17
+ name: str | None = None
18
+ description: str | None = None
19
+ color: str | None = Field(
20
+ None, description="Display color, either a name (e.g. 'pink') or hex"
21
+ )
22
+
23
+ model_config = ConfigDict(
24
+ frozen=True,
25
+ str_strip_whitespace=True,
26
+ extra="ignore",
27
+ )
28
+
29
+
30
+ __all__ = ["Status"]
@@ -0,0 +1,16 @@
1
+ """Contains shared errors types that can be raised from API functions"""
2
+
3
+
4
+ class UnexpectedStatus(Exception):
5
+ """Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True"""
6
+
7
+ def __init__(self, status_code: int, content: bytes):
8
+ self.status_code = status_code
9
+ self.content = content
10
+
11
+ super().__init__(
12
+ f"Unexpected status code: {status_code}\n\nResponse content:\n{content.decode(errors='ignore')}"
13
+ )
14
+
15
+
16
+ __all__ = ["UnexpectedStatus"]
@@ -0,0 +1,21 @@
1
+ """Ergonomic helper facades for the StatusPro API client.
2
+
3
+ These classes wrap the generated API with domain-specific methods that reduce
4
+ boilerplate for common workflows. Each helper is accessed as an attribute on
5
+ ``StatusProClient`` (e.g. ``client.orders.list(...)``).
6
+
7
+ Example:
8
+ >>> async with StatusProClient() as client:
9
+ ... orders = await client.orders.list(per_page=50)
10
+ ... statuses = await client.statuses.list()
11
+ """
12
+
13
+ from statuspro_public_api_client.helpers.base import Base
14
+ from statuspro_public_api_client.helpers.orders import Orders
15
+ from statuspro_public_api_client.helpers.statuses import Statuses
16
+
17
+ __all__ = [
18
+ "Base",
19
+ "Orders",
20
+ "Statuses",
21
+ ]
@@ -0,0 +1,26 @@
1
+ """Base class for domain classes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from statuspro_public_api_client.statuspro_client import StatusProClient
9
+
10
+
11
+ class Base:
12
+ """Base class for all domain classes.
13
+
14
+ Provides common functionality and access to the StatusProClient instance.
15
+
16
+ Args:
17
+ client: The StatusProClient instance to use for API calls.
18
+ """
19
+
20
+ def __init__(self, client: StatusProClient) -> None:
21
+ """Initialize with a client instance.
22
+
23
+ Args:
24
+ client: The StatusProClient instance to use for API calls.
25
+ """
26
+ self._client = client
@@ -0,0 +1,78 @@
1
+ """Order helper facade — ergonomic wrappers around the generated order endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from statuspro_public_api_client.helpers.base import Base
9
+
10
+ if TYPE_CHECKING:
11
+ from statuspro_public_api_client.domain import Order
12
+
13
+
14
+ class Orders(Base):
15
+ """Ergonomic order operations that return domain ``Order`` models."""
16
+
17
+ async def list(
18
+ self,
19
+ *,
20
+ search: str | None = None,
21
+ status_code: str | None = None,
22
+ tags: builtins.list[str] | None = None,
23
+ tags_any: builtins.list[str] | None = None,
24
+ financial_status: builtins.list[str] | None = None,
25
+ fulfillment_status: builtins.list[str] | None = None,
26
+ exclude_cancelled: bool | None = None,
27
+ due_date_from: str | None = None,
28
+ due_date_to: str | None = None,
29
+ page: int | None = None,
30
+ per_page: int | None = None,
31
+ ) -> builtins.list[Order]:
32
+ """List orders. Auto-paginates when ``page`` is not set."""
33
+ from statuspro_public_api_client.api.orders import list_orders
34
+ from statuspro_public_api_client.domain import Order
35
+ from statuspro_public_api_client.utils import unwrap_data
36
+
37
+ kwargs: dict[str, Any] = {
38
+ "client": self._client,
39
+ "search": search,
40
+ "status_code": status_code,
41
+ "tags": tags,
42
+ "tags_any": tags_any,
43
+ "financial_status": financial_status,
44
+ "fulfillment_status": fulfillment_status,
45
+ "exclude_cancelled": exclude_cancelled,
46
+ "due_date_from": due_date_from,
47
+ "due_date_to": due_date_to,
48
+ "page": page,
49
+ "per_page": per_page,
50
+ }
51
+ kwargs = {k: v for k, v in kwargs.items() if v is not None}
52
+ response = await list_orders.asyncio_detailed(**kwargs)
53
+ raw_orders = unwrap_data(response, default=[])
54
+ return [Order.model_validate(o.to_dict()) for o in raw_orders]
55
+
56
+ async def get(self, order_id: int) -> Order:
57
+ """Get a single order by id."""
58
+ from statuspro_public_api_client.api.orders import get_order
59
+ from statuspro_public_api_client.domain import Order
60
+ from statuspro_public_api_client.models.order_response import OrderResponse
61
+ from statuspro_public_api_client.utils import unwrap_as
62
+
63
+ response = await get_order.asyncio_detailed(client=self._client, order=order_id)
64
+ raw = unwrap_as(response, OrderResponse)
65
+ return Order.model_validate(raw.to_dict())
66
+
67
+ async def lookup(self, *, number: str, email: str) -> Order:
68
+ """Look up an order by order number and customer email."""
69
+ from statuspro_public_api_client.api.orders import lookup_order
70
+ from statuspro_public_api_client.domain import Order
71
+ from statuspro_public_api_client.models.order_response import OrderResponse
72
+ from statuspro_public_api_client.utils import unwrap_as
73
+
74
+ response = await lookup_order.asyncio_detailed(
75
+ client=self._client, number=number, email=email
76
+ )
77
+ raw = unwrap_as(response, OrderResponse)
78
+ return Order.model_validate(raw.to_dict())
@@ -0,0 +1,37 @@
1
+ """Status helper facade — ergonomic wrappers around the generated status endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import builtins
6
+ from typing import TYPE_CHECKING
7
+
8
+ from statuspro_public_api_client.helpers.base import Base
9
+
10
+ if TYPE_CHECKING:
11
+ from statuspro_public_api_client.domain import Status
12
+
13
+
14
+ class Statuses(Base):
15
+ """Ergonomic status catalog operations."""
16
+
17
+ async def list(self) -> builtins.list[Status]:
18
+ """Return the full status catalog (``/statuses``)."""
19
+ from statuspro_public_api_client.api.statuses import get_statuses
20
+ from statuspro_public_api_client.domain import Status
21
+ from statuspro_public_api_client.utils import unwrap_data
22
+
23
+ response = await get_statuses.asyncio_detailed(client=self._client)
24
+ raw = unwrap_data(response, default=[])
25
+ return [Status.model_validate(s.to_dict()) for s in raw]
26
+
27
+ async def viable_for(self, order_id: int) -> builtins.list[Status]:
28
+ """Return statuses that are valid transitions from the order's current state."""
29
+ from statuspro_public_api_client.api.orders import get_viable_statuses
30
+ from statuspro_public_api_client.domain import Status
31
+ from statuspro_public_api_client.utils import unwrap_data
32
+
33
+ response = await get_viable_statuses.asyncio_detailed(
34
+ client=self._client, order=order_id
35
+ )
36
+ raw = unwrap_data(response, default=[])
37
+ return [Status.model_validate(s.to_dict()) for s in raw]
@@ -0,0 +1,99 @@
1
+ """Logging setup utilities for the StatusPro API client.
2
+
3
+ This module provides logging configuration helpers for the enhanced StatusPro client.
4
+
5
+ """
6
+
7
+ import logging
8
+ import logging.handlers
9
+ from pathlib import Path
10
+
11
+
12
+ class InfoToDebugFilter(logging.Filter):
13
+ """Filter to convert INFO level logs to DEBUG level for specific loggers."""
14
+
15
+ def filter(self, record: logging.LogRecord) -> bool:
16
+ if record.levelno == logging.INFO:
17
+ record.levelno = logging.DEBUG
18
+ record.levelname = "DEBUG"
19
+ return True
20
+
21
+
22
+ def setup_logging(
23
+ log_dir: str = "logs",
24
+ log_file_prefix: str = "statuspro_client",
25
+ log_level: int = logging.INFO,
26
+ console_level: int = logging.INFO,
27
+ max_bytes: int = 10 * 1024 * 1024, # 10MB
28
+ backup_count: int = 5,
29
+ ) -> logging.Logger:
30
+ """Set up logging configuration for the StatusPro client.
31
+
32
+ Args:
33
+ log_dir: Directory to store log files
34
+ log_file_prefix: Prefix for log file names
35
+ log_level: Logging level for file handler
36
+ console_level: Logging level for console handler
37
+ max_bytes: Maximum size of each log file before rotation
38
+ backup_count: Number of backup files to keep
39
+
40
+ Returns:
41
+ Configured logger instance
42
+
43
+ """
44
+ # Create log directory if it doesn't exist
45
+ log_path = Path(log_dir)
46
+ log_path.mkdir(exist_ok=True)
47
+
48
+ # Create logger
49
+ logger = logging.getLogger("statuspro_client")
50
+ logger.setLevel(logging.DEBUG)
51
+
52
+ # Remove existing handlers to avoid duplicates
53
+ for handler in logger.handlers[:]:
54
+ logger.removeHandler(handler)
55
+
56
+ # Create formatters
57
+ detailed_formatter = logging.Formatter(
58
+ "%(asctime)s - %(name)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s"
59
+ )
60
+ simple_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
61
+
62
+ # File handler with rotation
63
+ log_file = log_path / f"{log_file_prefix}.log"
64
+ file_handler = logging.handlers.RotatingFileHandler(
65
+ log_file, maxBytes=max_bytes, backupCount=backup_count
66
+ )
67
+ file_handler.setLevel(log_level)
68
+ file_handler.setFormatter(detailed_formatter)
69
+ logger.addHandler(file_handler)
70
+
71
+ # Console handler
72
+ console_handler = logging.StreamHandler()
73
+ console_handler.setLevel(console_level)
74
+ console_handler.setFormatter(simple_formatter)
75
+ logger.addHandler(console_handler)
76
+
77
+ # Set up httpx logger with filter
78
+ httpx_logger = logging.getLogger("httpx")
79
+ httpx_logger.setLevel(logging.DEBUG)
80
+ httpx_logger.addFilter(InfoToDebugFilter())
81
+
82
+ logger.info(
83
+ f"Logging configured - File: {log_file}, Level: {logging.getLevelName(log_level)}"
84
+ )
85
+
86
+ return logger
87
+
88
+
89
+ def get_logger(name: str | None = None) -> logging.Logger:
90
+ """Get a logger instance.
91
+
92
+ Args:
93
+ name: Logger name (defaults to statuspro_client)
94
+
95
+ Returns:
96
+ Logger instance
97
+
98
+ """
99
+ return logging.getLogger(name or "statuspro_client")