Source code for acmeow.models.order
"""Order model for ACME protocol.
Represents ACME certificate orders.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from acmeow.enums import OrderStatus
from acmeow.models.authorization import Authorization
from acmeow.models.identifier import Identifier
[docs]
@dataclass(slots=True)
class Order:
"""An ACME certificate order.
Orders represent a request for a certificate covering one or more
identifiers. They progress through states as authorizations are
completed and the certificate is issued.
Note: This class is mutable because orders are updated as their
status changes during the certificate issuance process.
Args:
status: Current order status.
url: URL for polling order status.
identifiers: List of identifiers the certificate will cover.
finalize_url: URL for submitting the CSR.
expires: Expiration timestamp for the order.
certificate_url: URL for downloading the certificate (when ready).
authorizations: List of authorizations for this order.
error: Error details if the order failed.
"""
status: OrderStatus
url: str
identifiers: tuple[Identifier, ...]
finalize_url: str
expires: str | None = None
certificate_url: str | None = None
authorizations: list[Authorization] = field(default_factory=list)
error: dict[str, str] | None = None
[docs]
@classmethod
def from_dict(cls, data: dict[str, Any], url: str) -> Order:
"""Create an Order from an ACME response dictionary.
Args:
data: Order object from ACME server response.
url: The order URL.
Returns:
New Order instance.
"""
identifiers = tuple(
Identifier.from_dict(i)
for i in data.get("identifiers", [])
)
return cls(
status=OrderStatus(data.get("status", "pending")),
url=url,
identifiers=identifiers,
finalize_url=data["finalize"],
expires=data.get("expires"),
certificate_url=data.get("certificate"),
error=data.get("error"),
)
[docs]
def update_from_dict(self, data: dict[str, Any]) -> None:
"""Update the order from an ACME response dictionary.
Args:
data: Order object from ACME server response.
"""
self.status = OrderStatus(data.get("status", self.status.value))
if "certificate" in data:
self.certificate_url = data["certificate"]
if "error" in data:
self.error = data["error"]
@property
def is_pending(self) -> bool:
"""Check if the order is pending authorization completion."""
return self.status == OrderStatus.PENDING
@property
def is_ready(self) -> bool:
"""Check if the order is ready for finalization."""
return self.status == OrderStatus.READY
@property
def is_processing(self) -> bool:
"""Check if the order is being processed."""
return self.status == OrderStatus.PROCESSING
@property
def is_valid(self) -> bool:
"""Check if the order is complete and certificate is ready."""
return self.status == OrderStatus.VALID
@property
def is_invalid(self) -> bool:
"""Check if the order failed."""
return self.status == OrderStatus.INVALID
@property
def is_finalized(self) -> bool:
"""Check if the order has been finalized (processing or valid)."""
return self.status in (OrderStatus.PROCESSING, OrderStatus.VALID)
@property
def domains(self) -> list[str]:
"""Get the list of domain names in this order.
Returns:
List of domain/identifier values.
"""
return [i.value for i in self.identifiers]
@property
def common_name(self) -> str:
"""Get the common name (first identifier) for this order.
Returns:
The first identifier value.
"""
return self.identifiers[0].value if self.identifiers else ""
[docs]
def __str__(self) -> str:
"""Return a human-readable string representation."""
domains = ", ".join(self.domains[:3])
if len(self.domains) > 3:
domains += f", ... ({len(self.domains)} total)"
return f"Order({domains}, status={self.status.value})"