diff --git a/postmark/clients/account_client.py b/postmark/clients/account_client.py index b735bd7..e6abd52 100644 --- a/postmark/clients/account_client.py +++ b/postmark/clients/account_client.py @@ -2,7 +2,7 @@ import os import sys import uuid -from typing import Any, Dict, List, Optional, Union +from typing import Any import httpx from tenacity import ( @@ -40,7 +40,7 @@ def __init__( account_token: str, retries: int = 3, timeout: float = 30.0, - base_url: Optional[str] = None, + base_url: str | None = None, ): """ Initialize the Postmark Account Client. @@ -187,28 +187,28 @@ async def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response: raise AssertionError("The Postmark API is unreachable.") async def get( - self, endpoint: str, params: Optional[Dict[str, Any]] = None + self, endpoint: str, params: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("GET", endpoint, params=params) async def post( self, endpoint: str, - json: Union[Dict[str, Any], List[Dict[str, Any]], None] = None, + json: dict[str, Any] | list[dict[str, Any]] | None = None, ) -> httpx.Response: return await self.request("POST", endpoint, json=json) async def put( - self, endpoint: str, json: Optional[Dict[str, Any]] = None + self, endpoint: str, json: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("PUT", endpoint, json=json) async def patch( - self, endpoint: str, json: Optional[Dict[str, Any]] = None + self, endpoint: str, json: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("PATCH", endpoint, json=json) async def delete( - self, endpoint: str, params: Optional[Dict[str, Any]] = None + self, endpoint: str, params: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("DELETE", endpoint, params=params) diff --git a/postmark/clients/server_client.py b/postmark/clients/server_client.py index 8e0af27..1e2e4c6 100644 --- a/postmark/clients/server_client.py +++ b/postmark/clients/server_client.py @@ -2,7 +2,7 @@ import os import sys import uuid -from typing import Any, Dict, List, Optional, Union +from typing import Any import httpx from tenacity import ( @@ -45,7 +45,7 @@ def __init__( server_token: str, retries: int = 3, timeout: float = 5, - base_url: Optional[str] = None, + base_url: str | None = None, ): """ Initialize the Postmark Server Client. @@ -202,28 +202,28 @@ async def request(self, method: str, endpoint: str, **kwargs) -> httpx.Response: raise AssertionError("The Postmark API is unreachable.") async def get( - self, endpoint: str, params: Optional[Dict[str, Any]] = None + self, endpoint: str, params: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("GET", endpoint, params=params) async def post( self, endpoint: str, - json: Union[Dict[str, Any], List[Dict[str, Any]], None] = None, + json: dict[str, Any] | list[dict[str, Any]] | None = None, ) -> httpx.Response: return await self.request("POST", endpoint, json=json) async def put( - self, endpoint: str, json: Optional[Dict[str, Any]] = None + self, endpoint: str, json: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("PUT", endpoint, json=json) async def patch( - self, endpoint: str, json: Optional[Dict[str, Any]] = None + self, endpoint: str, json: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("PATCH", endpoint, json=json) async def delete( - self, endpoint: str, params: Optional[Dict[str, Any]] = None + self, endpoint: str, params: dict[str, Any] | None = None ) -> httpx.Response: return await self.request("DELETE", endpoint, params=params) diff --git a/postmark/exceptions.py b/postmark/exceptions.py index 84e9108..0f08aa0 100644 --- a/postmark/exceptions.py +++ b/postmark/exceptions.py @@ -1,7 +1,7 @@ """Postmark API exceptions.""" import re -from typing import Any, Optional +from typing import Any class PostmarkException(Exception): @@ -10,8 +10,8 @@ class PostmarkException(Exception): def __init__( self, message: str, - error_code: Optional[int] = None, - http_status: Optional[int] = None, + error_code: int | None = None, + http_status: int | None = None, ): super().__init__(message) self.error_code = error_code @@ -51,7 +51,7 @@ def __init__( message: str, error_code: int, http_status: int, - request_id: Optional[str] = None, + request_id: str | None = None, ): super().__init__(message, error_code, http_status) self.request_id = request_id @@ -75,7 +75,7 @@ def __init__( message: str, error_code: int, http_status: int, - request_id: Optional[str] = None, + request_id: str | None = None, ): super().__init__(message, error_code, http_status, request_id) match = re.search(r"Found inactive addresses: ([^.]+)", message) diff --git a/postmark/models/bounces/manager.py b/postmark/models/bounces/manager.py index 5132eb6..dae9008 100644 --- a/postmark/models/bounces/manager.py +++ b/postmark/models/bounces/manager.py @@ -1,6 +1,6 @@ import logging +from collections.abc import AsyncGenerator from datetime import datetime -from typing import AsyncGenerator, Optional from postmark.models.page import Page from postmark.utils.pagination import paginate @@ -33,14 +33,14 @@ async def list( self, count: int = 100, offset: int = 0, - type: Optional[BounceType] = None, - inactive: Optional[bool] = None, - email_filter: Optional[str] = None, - tag: Optional[str] = None, - message_id: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, - message_stream: Optional[str] = None, + type: BounceType | None = None, + inactive: bool | None = None, + email_filter: str | None = None, + tag: str | None = None, + message_id: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, + message_stream: str | None = None, ) -> Page[Bounce]: """ List bounces for the server. @@ -90,14 +90,14 @@ async def stream( self, batch_size: int = 500, max_bounces: int = 1000, - type: Optional[BounceType] = None, - inactive: Optional[bool] = None, - email_filter: Optional[str] = None, - tag: Optional[str] = None, - message_id: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, - message_stream: Optional[str] = None, + type: BounceType | None = None, + inactive: bool | None = None, + email_filter: str | None = None, + tag: str | None = None, + message_id: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, + message_stream: str | None = None, ) -> AsyncGenerator[Bounce, None]: """Yield bounces with automatic pagination.""" async for bounce in paginate( diff --git a/postmark/models/bounces/schemas.py b/postmark/models/bounces/schemas.py index 321d0d2..bdd876a 100644 --- a/postmark/models/bounces/schemas.py +++ b/postmark/models/bounces/schemas.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List, Optional from pydantic import BaseModel, ConfigDict, EmailStr, Field @@ -13,7 +12,7 @@ class Bounce(BaseModel): type: BounceType = Field(alias="Type") type_code: int = Field(alias="TypeCode") name: str = Field(alias="Name") - tag: Optional[str] = Field(None, alias="Tag") + tag: str | None = Field(None, alias="Tag") message_id: str = Field(alias="MessageID") server_id: int = Field(alias="ServerID") message_stream: str = Field(alias="MessageStream") @@ -26,7 +25,7 @@ class Bounce(BaseModel): inactive: bool = Field(alias="Inactive") can_activate: bool = Field(alias="CanActivate") subject: str = Field(alias="Subject") - content: Optional[str] = Field(None, alias="Content") + content: str | None = Field(None, alias="Content") model_config = ConfigDict(populate_by_name=True) @@ -46,7 +45,7 @@ class DeliveryStats(BaseModel): """Response from ``GET /deliverystats``""" inactive_mails: int = Field(alias="InactiveMails") - bounces: List[BounceTypeCount] = Field(alias="Bounces") + bounces: list[BounceTypeCount] = Field(alias="Bounces") model_config = ConfigDict(populate_by_name=True) @@ -55,7 +54,7 @@ class BouncesListResponse(BaseModel): """Response from ``GET /bounces``""" total_count: int = Field(alias="TotalCount") - bounces: List[Bounce] = Field(alias="Bounces") + bounces: list[Bounce] = Field(alias="Bounces") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/domains/manager.py b/postmark/models/domains/manager.py index a9ca97e..4cc03ec 100644 --- a/postmark/models/domains/manager.py +++ b/postmark/models/domains/manager.py @@ -1,5 +1,3 @@ -from typing import Optional - from postmark.models.page import Page from postmark.utils.types import HTTPClient @@ -42,7 +40,7 @@ async def get(self, domain_id: int) -> Domain: async def create( self, name: str, - return_path_domain: Optional[str] = None, + return_path_domain: str | None = None, ) -> Domain: """ Create a new domain on the account. @@ -62,7 +60,7 @@ async def create( async def edit( self, domain_id: int, - return_path_domain: Optional[str] = None, + return_path_domain: str | None = None, ) -> Domain: """ Update a domain. diff --git a/postmark/models/domains/schemas.py b/postmark/models/domains/schemas.py index 56e30f2..f63e095 100644 --- a/postmark/models/domains/schemas.py +++ b/postmark/models/domains/schemas.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel, ConfigDict, Field @@ -22,25 +20,25 @@ class Domain(BaseModel): id: int = Field(alias="ID") name: str = Field(alias="Name") spf_verified: bool = Field(False, alias="SPFVerified") # deprecated by Postmark - spf_host: Optional[str] = Field(None, alias="SPFHost") - spf_text_value: Optional[str] = Field(None, alias="SPFTextValue") + spf_host: str | None = Field(None, alias="SPFHost") + spf_text_value: str | None = Field(None, alias="SPFTextValue") dkim_verified: bool = Field(alias="DKIMVerified") weak_dkim: bool = Field(alias="WeakDKIM") - dkim_host: Optional[str] = Field(None, alias="DKIMHost") - dkim_text_value: Optional[str] = Field(None, alias="DKIMTextValue") - dkim_pending_host: Optional[str] = Field(None, alias="DKIMPendingHost") - dkim_pending_text_value: Optional[str] = Field(None, alias="DKIMPendingTextValue") - dkim_revoked_host: Optional[str] = Field(None, alias="DKIMRevokedHost") - dkim_revoked_text_value: Optional[str] = Field(None, alias="DKIMRevokedTextValue") - safe_to_remove_revoked_key_from_dns: Optional[bool] = Field( + dkim_host: str | None = Field(None, alias="DKIMHost") + dkim_text_value: str | None = Field(None, alias="DKIMTextValue") + dkim_pending_host: str | None = Field(None, alias="DKIMPendingHost") + dkim_pending_text_value: str | None = Field(None, alias="DKIMPendingTextValue") + dkim_revoked_host: str | None = Field(None, alias="DKIMRevokedHost") + dkim_revoked_text_value: str | None = Field(None, alias="DKIMRevokedTextValue") + safe_to_remove_revoked_key_from_dns: bool | None = Field( None, alias="SafeToRemoveRevokedKeyFromDNS" ) - dkim_update_status: Optional[str] = Field(None, alias="DKIMUpdateStatus") - return_path_domain: Optional[str] = Field(None, alias="ReturnPathDomain") - return_path_domain_verified: Optional[bool] = Field( + dkim_update_status: str | None = Field(None, alias="DKIMUpdateStatus") + return_path_domain: str | None = Field(None, alias="ReturnPathDomain") + return_path_domain_verified: bool | None = Field( None, alias="ReturnPathDomainVerified" ) - return_path_domain_cname_value: Optional[str] = Field( + return_path_domain_cname_value: str | None = Field( None, alias="ReturnPathDomainCNAMEValue" ) @@ -51,7 +49,7 @@ class DomainsListResponse(BaseModel): """Response from ``GET /domains``.""" total_count: int = Field(alias="TotalCount") - domains: List[DomainListItem] = Field(alias="Domains") + domains: list[DomainListItem] = Field(alias="Domains") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/inbound/manager.py b/postmark/models/inbound/manager.py index 81bb97e..5275cba 100644 --- a/postmark/models/inbound/manager.py +++ b/postmark/models/inbound/manager.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Any, Dict, Optional +from typing import Any from postmark.models.page import Page from postmark.utils.types import HTTPClient @@ -15,14 +15,14 @@ async def list( self, count: int = 100, offset: int = 0, - recipient: Optional[str] = None, - from_email: Optional[str] = None, - tag: Optional[str] = None, - subject: Optional[str] = None, - mailbox_hash: Optional[str] = None, - status: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, + recipient: str | None = None, + from_email: str | None = None, + tag: str | None = None, + subject: str | None = None, + mailbox_hash: str | None = None, + status: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, ) -> Page[InboundMessage]: """List inbound messages.""" if count > 500: @@ -30,7 +30,7 @@ async def list( if count + offset > 10000: raise ValueError("Count + Offset cannot exceed 10,000") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} if recipient is not None: params["recipient"] = recipient diff --git a/postmark/models/inbound/schemas.py b/postmark/models/inbound/schemas.py index 9021777..9a4bf28 100644 --- a/postmark/models/inbound/schemas.py +++ b/postmark/models/inbound/schemas.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel, ConfigDict, Field from postmark.models.outbound.schemas import EmailAddress, Header @@ -7,7 +5,7 @@ class InboundAttachment(BaseModel): name: str = Field(alias="Name") - content_id: Optional[str] = Field(None, alias="ContentID") + content_id: str | None = Field(None, alias="ContentID") content_type: str = Field(alias="ContentType") content_length: int = Field(alias="ContentLength") @@ -22,17 +20,17 @@ class InboundMessage(BaseModel): from_name: str = Field(alias="FromName") from_full: EmailAddress = Field(alias="FromFull") to: str = Field(alias="To") - to_full: List[EmailAddress] = Field(alias="ToFull") - cc: Optional[str] = Field(None, alias="Cc") - cc_full: List[EmailAddress] = Field(default_factory=list, alias="CcFull") - reply_to: Optional[str] = Field(None, alias="ReplyTo") + to_full: list[EmailAddress] = Field(alias="ToFull") + cc: str | None = Field(None, alias="Cc") + cc_full: list[EmailAddress] = Field(default_factory=list, alias="CcFull") + reply_to: str | None = Field(None, alias="ReplyTo") original_recipient: str = Field(alias="OriginalRecipient") subject: str = Field(alias="Subject") date: str = Field(alias="Date") - mailbox_hash: Optional[str] = Field(None, alias="MailboxHash") - tag: Optional[str] = Field(None, alias="Tag") + mailbox_hash: str | None = Field(None, alias="MailboxHash") + tag: str | None = Field(None, alias="Tag") status: str = Field(alias="Status") - attachments: List[InboundAttachment] = Field( + attachments: list[InboundAttachment] = Field( default_factory=list, alias="Attachments" ) @@ -42,10 +40,10 @@ class InboundMessage(BaseModel): class InboundMessageDetails(InboundMessage): """Response from ``GET /messages/inbound/{messageid}/details``.""" - text_body: Optional[str] = Field(None, alias="TextBody") - html_body: Optional[str] = Field(None, alias="HtmlBody") - blocked_reason: Optional[str] = Field(None, alias="BlockedReason") - headers: List[Header] = Field(default_factory=list, alias="Headers") + text_body: str | None = Field(None, alias="TextBody") + html_body: str | None = Field(None, alias="HtmlBody") + blocked_reason: str | None = Field(None, alias="BlockedReason") + headers: list[Header] = Field(default_factory=list, alias="Headers") class InboundActionResponse(BaseModel): diff --git a/postmark/models/inbound_rules/schemas.py b/postmark/models/inbound_rules/schemas.py index 32aef30..572731b 100644 --- a/postmark/models/inbound_rules/schemas.py +++ b/postmark/models/inbound_rules/schemas.py @@ -1,5 +1,3 @@ -from typing import List - from pydantic import BaseModel, ConfigDict, Field @@ -16,7 +14,7 @@ class InboundRulesListResponse(BaseModel): """Response from ``GET /triggers/inboundrules``.""" total_count: int = Field(alias="TotalCount") - inbound_rules: List[InboundRule] = Field(alias="InboundRules") + inbound_rules: list[InboundRule] = Field(alias="InboundRules") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/outbound/manager.py b/postmark/models/outbound/manager.py index 04475b5..a88bb34 100644 --- a/postmark/models/outbound/manager.py +++ b/postmark/models/outbound/manager.py @@ -1,8 +1,9 @@ from __future__ import annotations import logging +from collections.abc import AsyncGenerator from datetime import datetime -from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any from pydantic import ValidationError @@ -14,6 +15,8 @@ if TYPE_CHECKING: from postmark.models.templates.schemas import TemplateEmail +import builtins + from .schemas import ( BulkEmail, BulkSendResponse, @@ -30,7 +33,7 @@ logger = logging.getLogger(__name__) -def _parse_email(message: Union[Email, Dict[str, Any]]) -> Email: +def _parse_email(message: Email | dict[str, Any]) -> Email: """ Coerce a dict to an Email model using snake_case field names, or raise InvalidEmailException @@ -43,7 +46,7 @@ def _parse_email(message: Union[Email, Dict[str, Any]]) -> Email: raise InvalidEmailException(e.errors()) from e -def _parse_template_email(msg: Union[TemplateEmail, Dict[str, Any]]) -> TemplateEmail: +def _parse_template_email(msg: TemplateEmail | dict[str, Any]) -> TemplateEmail: from postmark.models.templates.schemas import TemplateEmail if isinstance(msg, TemplateEmail): @@ -54,7 +57,7 @@ def _parse_template_email(msg: Union[TemplateEmail, Dict[str, Any]]) -> Template raise InvalidEmailException(e.errors()) from e -def _parse_bulk_email(message: Union[BulkEmail, Dict[str, Any]]) -> BulkEmail: +def _parse_bulk_email(message: BulkEmail | dict[str, Any]) -> BulkEmail: """ Coerce a dict to a BulkEmail model, raising InvalidEmailException on validation failure. Passes through a BulkEmail instance unchanged. @@ -75,7 +78,7 @@ def __init__(self, client: HTTPClient): # Single send # ------------------------------------------------------------------------- - async def send(self, message: Union[Email, Dict[str, Any]]) -> SendResponse: + async def send(self, message: Email | dict[str, Any]) -> SendResponse: """Send a single email.""" email_payload = _parse_email(message) @@ -91,8 +94,8 @@ async def send(self, message: Union[Email, Dict[str, Any]]) -> SendResponse: # ------------------------------------------------------------------------- async def send_batch( - self, messages: List[Union[Email, Dict[str, Any]]] - ) -> List[SendResponse]: + self, messages: builtins.list[Email | dict[str, Any]] + ) -> builtins.list[SendResponse]: """ Send up to 500 different emails in a single request. Use this when each recipient needs a **completely different** message. @@ -128,9 +131,7 @@ async def send_batch( # Bulk send — same message, many recipients, one request # ------------------------------------------------------------------------- - async def send_bulk( - self, message: Union[BulkEmail, Dict[str, Any]] - ) -> BulkSendResponse: + async def send_bulk(self, message: BulkEmail | dict[str, Any]) -> BulkSendResponse: """ Send the **same message** to multiple recipients in a single request. """ @@ -162,7 +163,7 @@ async def get_bulk_status(self, bulk_id: str) -> BulkSendStatus: # ------------------------------------------------------------------------- async def send_with_template( - self, message: Union[TemplateEmail, Dict[str, Any]] + self, message: TemplateEmail | dict[str, Any] ) -> SendResponse: """Send an email using a template.""" email = _parse_template_email(message) @@ -174,8 +175,8 @@ async def send_with_template( return SendResponse(**response.json()) async def send_batch_with_template( - self, messages: List[Union[TemplateEmail, Dict[str, Any]]] - ) -> List[SendResponse]: + self, messages: builtins.list[TemplateEmail | dict[str, Any]] + ) -> builtins.list[SendResponse]: """Send up to 500 template emails in a single batch request.""" if len(messages) > 500: raise ValueError("Batch size cannot exceed 500 messages") @@ -209,15 +210,15 @@ async def list( self, count: int = 100, offset: int = 0, - recipient: Optional[str] = None, - from_email: Optional[str] = None, - tag: Optional[str] = None, - status: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, - subject: Optional[str] = None, - message_stream: Optional[str] = None, - metadata: Optional[Dict[str, str]] = None, + recipient: str | None = None, + from_email: str | None = None, + tag: str | None = None, + status: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, + subject: str | None = None, + message_stream: str | None = None, + metadata: dict[str, str] | None = None, ) -> Page[Message]: """List sent messages.""" if count > 500: @@ -225,7 +226,7 @@ async def list( if count + offset > 10000: raise ValueError("Count + Offset cannot exceed 10,000 messages") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} if recipient is not None: params["recipient"] = recipient @@ -262,15 +263,15 @@ async def stream( self, batch_size: int = 500, max_messages: int = 1000, - recipient: Optional[str] = None, - from_email: Optional[str] = None, - tag: Optional[str] = None, - status: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, - subject: Optional[str] = None, - message_stream: Optional[str] = None, - metadata: Optional[Dict[str, str]] = None, + recipient: str | None = None, + from_email: str | None = None, + tag: str | None = None, + status: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, + subject: str | None = None, + message_stream: str | None = None, + metadata: dict[str, str] | None = None, ) -> AsyncGenerator[Message, None]: """Stream messages with automatic pagination.""" async for msg in paginate( @@ -307,21 +308,21 @@ async def list_opens( self, count: int = 100, offset: int = 0, - recipient: Optional[str] = None, - tag: Optional[str] = None, - client_name: Optional[str] = None, - client_company: Optional[str] = None, - client_family: Optional[str] = None, - os_name: Optional[str] = None, - os_family: Optional[str] = None, - os_company: Optional[str] = None, - platform: Optional[str] = None, - country: Optional[str] = None, - region: Optional[str] = None, - city: Optional[str] = None, - message_stream: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, + recipient: str | None = None, + tag: str | None = None, + client_name: str | None = None, + client_company: str | None = None, + client_family: str | None = None, + os_name: str | None = None, + os_family: str | None = None, + os_company: str | None = None, + platform: str | None = None, + country: str | None = None, + region: str | None = None, + city: str | None = None, + message_stream: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, ) -> Page[OpenEvent]: """List open tracking events across all messages.""" if count > 500: @@ -329,7 +330,7 @@ async def list_opens( if count + offset > 10000: raise ValueError("Count + Offset cannot exceed 10,000") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} if recipient is not None: params["recipient"] = recipient @@ -378,7 +379,7 @@ async def list_message_opens( if count + offset > 10000: raise ValueError("Count + Offset cannot exceed 10,000") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} response = await self.client.get( f"/messages/outbound/opens/{message_id}", params=params ) @@ -396,21 +397,21 @@ async def list_clicks( self, count: int = 100, offset: int = 0, - recipient: Optional[str] = None, - tag: Optional[str] = None, - client_name: Optional[str] = None, - client_company: Optional[str] = None, - client_family: Optional[str] = None, - os_name: Optional[str] = None, - os_family: Optional[str] = None, - os_company: Optional[str] = None, - platform: Optional[str] = None, - country: Optional[str] = None, - region: Optional[str] = None, - city: Optional[str] = None, - message_stream: Optional[str] = None, - from_date: Optional[datetime] = None, - to_date: Optional[datetime] = None, + recipient: str | None = None, + tag: str | None = None, + client_name: str | None = None, + client_company: str | None = None, + client_family: str | None = None, + os_name: str | None = None, + os_family: str | None = None, + os_company: str | None = None, + platform: str | None = None, + country: str | None = None, + region: str | None = None, + city: str | None = None, + message_stream: str | None = None, + from_date: datetime | None = None, + to_date: datetime | None = None, ) -> Page[ClickEvent]: """List click tracking events across all messages.""" if count > 500: @@ -418,7 +419,7 @@ async def list_clicks( if count + offset > 10000: raise ValueError("Count + Offset cannot exceed 10,000") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} if recipient is not None: params["recipient"] = recipient @@ -467,7 +468,7 @@ async def list_message_clicks( if count + offset > 10000: raise ValueError("Count + Offset cannot exceed 10,000") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} response = await self.client.get( f"/messages/outbound/clicks/{message_id}", params=params ) diff --git a/postmark/models/outbound/schemas.py b/postmark/models/outbound/schemas.py index c1637e6..bd35b25 100644 --- a/postmark/models/outbound/schemas.py +++ b/postmark/models/outbound/schemas.py @@ -1,6 +1,6 @@ import logging from datetime import datetime -from typing import Annotated, Any, Dict, List, Optional, Union +from typing import Annotated, Any from pydantic import ( BaseModel, @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) FormattedEmailStr = Annotated[str, BeforeValidator(validate_formatted_email)] -EmailList = Annotated[List[str], BeforeValidator(validate_email_list)] +EmailList = Annotated[list[str], BeforeValidator(validate_email_list)] # --------------------------------------------------------------------------- @@ -26,15 +26,15 @@ class EmailAddress(BaseModel): email: EmailStr = Field(alias="Email") - name: Optional[str] = Field(None, alias="Name") + name: str | None = Field(None, alias="Name") class Attachment(BaseModel): name: str = Field(alias="Name") content: str = Field(alias="Content") content_type: str = Field(alias="ContentType") - content_id: Optional[str] = Field(None, alias="ContentID") - content_length: Optional[int] = Field(None, alias="ContentLength") + content_id: str | None = Field(None, alias="ContentID") + content_length: int | None = Field(None, alias="ContentLength") model_config = ConfigDict(populate_by_name=True) @@ -47,26 +47,26 @@ class Header(BaseModel): class ClientInfo(BaseModel): - name: Optional[str] = Field(None, alias="Name") - company: Optional[str] = Field(None, alias="Company") - family: Optional[str] = Field(None, alias="Family") + name: str | None = Field(None, alias="Name") + company: str | None = Field(None, alias="Company") + family: str | None = Field(None, alias="Family") class OSInfo(BaseModel): - name: Optional[str] = Field(None, alias="Name") - company: Optional[str] = Field(None, alias="Company") - family: Optional[str] = Field(None, alias="Family") + name: str | None = Field(None, alias="Name") + company: str | None = Field(None, alias="Company") + family: str | None = Field(None, alias="Family") class GeoInfo(BaseModel): - country_iso_code: Optional[str] = Field(None, alias="CountryISOCode") - country: Optional[str] = Field(None, alias="Country") - region_iso_code: Optional[str] = Field(None, alias="RegionISOCode") - region: Optional[str] = Field(None, alias="Region") - city: Optional[str] = Field(None, alias="City") - zip: Optional[str] = Field(None, alias="Zip") - coords: Optional[str] = Field(None, alias="Coords") - ip: Optional[str] = Field(None, alias="IP") + country_iso_code: str | None = Field(None, alias="CountryISOCode") + country: str | None = Field(None, alias="Country") + region_iso_code: str | None = Field(None, alias="RegionISOCode") + region: str | None = Field(None, alias="Region") + city: str | None = Field(None, alias="City") + zip: str | None = Field(None, alias="Zip") + coords: str | None = Field(None, alias="Coords") + ip: str | None = Field(None, alias="IP") # --------------------------------------------------------------------------- @@ -75,22 +75,22 @@ class GeoInfo(BaseModel): class MessageEventDetails(BaseModel): - delivery_message: Optional[str] = Field(None, alias="DeliveryMessage") - destination_server: Optional[str] = Field(None, alias="DestinationServer") - destination_ip: Optional[str] = Field(None, alias="DestinationIP") - summary: Optional[str] = Field(None, alias="Summary") - bounce_id: Optional[str] = Field(None, alias="BounceID") - origin: Optional[str] = Field(None, alias="Origin") - suppress_sending: Optional[bool] = Field(None, alias="SuppressSending") - link: Optional[str] = Field(None, alias="Link") - click_location: Optional[str] = Field(None, alias="ClickLocation") + delivery_message: str | None = Field(None, alias="DeliveryMessage") + destination_server: str | None = Field(None, alias="DestinationServer") + destination_ip: str | None = Field(None, alias="DestinationIP") + summary: str | None = Field(None, alias="Summary") + bounce_id: str | None = Field(None, alias="BounceID") + origin: str | None = Field(None, alias="Origin") + suppress_sending: bool | None = Field(None, alias="SuppressSending") + link: str | None = Field(None, alias="Link") + click_location: str | None = Field(None, alias="ClickLocation") class MessageEvent(BaseModel): recipient: EmailStr = Field(alias="Recipient") type: MessageEventType = Field(alias="Type") received_at: datetime = Field(alias="ReceivedAt") - details: Optional[MessageEventDetails] = Field(None, alias="Details") + details: MessageEventDetails | None = Field(None, alias="Details") class SendResponse(BaseModel): @@ -111,51 +111,51 @@ def success(self) -> bool: class Email(BaseModel): sender: str = Field(alias="From") to: str = Field(alias="To") - cc: Optional[str] = Field(None, alias="Cc") - bcc: Optional[str] = Field(None, alias="Bcc") - subject: Optional[str] = Field(None, alias="Subject") - tag: Optional[str] = Field(None, alias="Tag") - html_body: Optional[str] = Field(None, alias="HtmlBody") - text_body: Optional[str] = Field(None, alias="TextBody") - reply_to: Optional[str] = Field(None, alias="ReplyTo") - headers: List[Header] = Field(default_factory=list, alias="Headers") - track_opens: Optional[bool] = Field(None, alias="TrackOpens") - track_links: Optional[TrackLinksOption] = Field(None, alias="TrackLinks") - attachments: List[Attachment] = Field(default_factory=list, alias="Attachments") - metadata: Dict[str, str] = Field(default_factory=dict, alias="Metadata") - message_stream: Optional[str] = Field(None, alias="MessageStream") + cc: str | None = Field(None, alias="Cc") + bcc: str | None = Field(None, alias="Bcc") + subject: str | None = Field(None, alias="Subject") + tag: str | None = Field(None, alias="Tag") + html_body: str | None = Field(None, alias="HtmlBody") + text_body: str | None = Field(None, alias="TextBody") + reply_to: str | None = Field(None, alias="ReplyTo") + headers: list[Header] = Field(default_factory=list, alias="Headers") + track_opens: bool | None = Field(None, alias="TrackOpens") + track_links: TrackLinksOption | None = Field(None, alias="TrackLinks") + attachments: list[Attachment] = Field(default_factory=list, alias="Attachments") + metadata: dict[str, str] = Field(default_factory=dict, alias="Metadata") + message_stream: str | None = Field(None, alias="MessageStream") model_config = ConfigDict(populate_by_name=True) class Message(BaseModel): - tag: Optional[str] = Field(None, alias="Tag") + tag: str | None = Field(None, alias="Tag") message_id: str = Field(alias="MessageID") message_stream: str = Field(alias="MessageStream") - to: List[EmailAddress] = Field(alias="To") - cc: List[EmailAddress] = Field(default_factory=list, alias="Cc") - bcc: List[EmailAddress] = Field(default_factory=list, alias="Bcc") + to: list[EmailAddress] = Field(alias="To") + cc: list[EmailAddress] = Field(default_factory=list, alias="Cc") + bcc: list[EmailAddress] = Field(default_factory=list, alias="Bcc") recipients: EmailList = Field(alias="Recipients") received_at: datetime = Field(alias="ReceivedAt") sender: FormattedEmailStr = Field(alias="From") subject: str = Field(alias="Subject") - attachments: List[Union[str, Attachment]] = Field( + attachments: list[str | Attachment] = Field( default_factory=list, alias="Attachments" ) status: MessageStatus = Field(alias="Status") track_opens: bool = Field(alias="TrackOpens") track_links: TrackLinksOption = Field(alias="TrackLinks") - metadata: Dict[str, Any] = Field(default_factory=dict, alias="Metadata") + metadata: dict[str, Any] = Field(default_factory=dict, alias="Metadata") sandboxed: bool = Field(alias="Sandboxed") model_config = ConfigDict(populate_by_name=True) class MessageDetails(Message): - text_body: Optional[str] = Field(None, alias="TextBody") - html_body: Optional[str] = Field(None, alias="HtmlBody") - body: Optional[str] = Field(None, alias="Body") - message_events: List[MessageEvent] = Field( + text_body: str | None = Field(None, alias="TextBody") + html_body: str | None = Field(None, alias="HtmlBody") + body: str | None = Field(None, alias="Body") + message_events: list[MessageEvent] = Field( default_factory=list, alias="MessageEvents" ) @@ -177,11 +177,11 @@ class OpenEvent(BaseModel): """Single record from ``GET /messages/outbound/opens`` endpoints.""" record_type: str = Field(alias="RecordType") - user_agent: Optional[str] = Field(None, alias="UserAgent") + user_agent: str | None = Field(None, alias="UserAgent") message_id: str = Field(alias="MessageID") message_stream: str = Field(alias="MessageStream") received_at: datetime = Field(alias="ReceivedAt") - tag: Optional[str] = Field(None, alias="Tag") + tag: str | None = Field(None, alias="Tag") recipient: str = Field(alias="Recipient") client: ClientInfo = Field(alias="Client") os: OSInfo = Field(alias="OS") @@ -195,13 +195,13 @@ class ClickEvent(BaseModel): """Single record from ``GET /messages/outbound/clicks`` endpoints.""" record_type: str = Field(alias="RecordType") - user_agent: Optional[str] = Field(None, alias="UserAgent") + user_agent: str | None = Field(None, alias="UserAgent") message_id: str = Field(alias="MessageID") message_stream: str = Field(alias="MessageStream") received_at: datetime = Field(alias="ReceivedAt") - tag: Optional[str] = Field(None, alias="Tag") + tag: str | None = Field(None, alias="Tag") recipient: str = Field(alias="Recipient") - click_location: Optional[str] = Field(None, alias="ClickLocation") + click_location: str | None = Field(None, alias="ClickLocation") original_link: str = Field(alias="OriginalLink") client: ClientInfo = Field(alias="Client") os: OSInfo = Field(alias="OS") @@ -222,11 +222,11 @@ class BulkRecipient(BaseModel): """ to: str = Field(alias="To") - cc: Optional[str] = Field(None, alias="Cc") - bcc: Optional[str] = Field(None, alias="Bcc") - template_model: Optional[Dict[str, Any]] = Field(None, alias="TemplateModel") - metadata: Optional[Dict[str, str]] = Field(None, alias="Metadata") - headers: List[Header] = Field(default_factory=list, alias="Headers") + cc: str | None = Field(None, alias="Cc") + bcc: str | None = Field(None, alias="Bcc") + template_model: dict[str, Any] | None = Field(None, alias="TemplateModel") + metadata: dict[str, str] | None = Field(None, alias="Metadata") + headers: list[Header] = Field(default_factory=list, alias="Headers") model_config = ConfigDict(populate_by_name=True) @@ -237,21 +237,21 @@ class BulkEmail(BaseModel): """ sender: str = Field(alias="From") - messages: List[BulkRecipient] = Field(alias="Messages") - reply_to: Optional[str] = Field(None, alias="ReplyTo") - subject: Optional[str] = Field(None, alias="Subject") - html_body: Optional[str] = Field(None, alias="HtmlBody") - text_body: Optional[str] = Field(None, alias="TextBody") - template_id: Optional[int] = Field(None, alias="TemplateId") - template_alias: Optional[str] = Field(None, alias="TemplateAlias") - inline_css: Optional[bool] = Field(None, alias="InlineCss") - tag: Optional[str] = Field(None, alias="Tag") - metadata: Optional[Dict[str, str]] = Field(None, alias="Metadata") - message_stream: Optional[str] = Field(None, alias="MessageStream") - track_opens: Optional[bool] = Field(None, alias="TrackOpens") - track_links: Optional[TrackLinksOption] = Field(None, alias="TrackLinks") - attachments: List[Attachment] = Field(default_factory=list, alias="Attachments") - headers: List[Header] = Field(default_factory=list, alias="Headers") + messages: list[BulkRecipient] = Field(alias="Messages") + reply_to: str | None = Field(None, alias="ReplyTo") + subject: str | None = Field(None, alias="Subject") + html_body: str | None = Field(None, alias="HtmlBody") + text_body: str | None = Field(None, alias="TextBody") + template_id: int | None = Field(None, alias="TemplateId") + template_alias: str | None = Field(None, alias="TemplateAlias") + inline_css: bool | None = Field(None, alias="InlineCss") + tag: str | None = Field(None, alias="Tag") + metadata: dict[str, str] | None = Field(None, alias="Metadata") + message_stream: str | None = Field(None, alias="MessageStream") + track_opens: bool | None = Field(None, alias="TrackOpens") + track_links: TrackLinksOption | None = Field(None, alias="TrackLinks") + attachments: list[Attachment] = Field(default_factory=list, alias="Attachments") + headers: list[Header] = Field(default_factory=list, alias="Headers") model_config = ConfigDict(populate_by_name=True) @@ -278,6 +278,6 @@ class BulkSendStatus(BaseModel): total_messages: int = Field(alias="TotalMessages") percentage_completed: float = Field(alias="PercentageCompleted") status: BulkJobStatus = Field(alias="Status") - subject: Optional[str] = Field(None, alias="Subject") + subject: str | None = Field(None, alias="Subject") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/page.py b/postmark/models/page.py index d8b0bd6..e1242e9 100644 --- a/postmark/models/page.py +++ b/postmark/models/page.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Generic, List, TypeVar +from typing import Generic, TypeVar T = TypeVar("T") @@ -14,5 +14,5 @@ class Page(Generic[T]): total: The total number of records matching the query (across all pages). """ - items: List[T] + items: list[T] total: int diff --git a/postmark/models/servers/account_manager.py b/postmark/models/servers/account_manager.py index bf20f4a..da68c38 100644 --- a/postmark/models/servers/account_manager.py +++ b/postmark/models/servers/account_manager.py @@ -1,5 +1,3 @@ -from typing import Optional - from postmark.models.page import Page from postmark.utils.types import HTTPClient @@ -21,22 +19,22 @@ async def get(self, server_id: int) -> Server: async def create( self, name: str, - color: Optional[ServerColor] = None, - smtp_api_activated: Optional[bool] = None, - raw_email_enabled: Optional[bool] = None, - delivery_type: Optional[DeliveryType] = None, - inbound_hook_url: Optional[str] = None, - bounce_hook_url: Optional[str] = None, - open_hook_url: Optional[str] = None, - delivery_hook_url: Optional[str] = None, - click_hook_url: Optional[str] = None, - post_first_open_only: Optional[bool] = None, - inbound_domain: Optional[str] = None, - inbound_spam_threshold: Optional[int] = None, - track_opens: Optional[bool] = None, - track_links: Optional[TrackLinks] = None, - include_bounce_content_in_hook: Optional[bool] = None, - enable_smtp_api_error_hooks: Optional[bool] = None, + color: ServerColor | None = None, + smtp_api_activated: bool | None = None, + raw_email_enabled: bool | None = None, + delivery_type: DeliveryType | None = None, + inbound_hook_url: str | None = None, + bounce_hook_url: str | None = None, + open_hook_url: str | None = None, + delivery_hook_url: str | None = None, + click_hook_url: str | None = None, + post_first_open_only: bool | None = None, + inbound_domain: str | None = None, + inbound_spam_threshold: int | None = None, + track_opens: bool | None = None, + track_links: TrackLinks | None = None, + include_bounce_content_in_hook: bool | None = None, + enable_smtp_api_error_hooks: bool | None = None, ) -> Server: """ Create a new server on the account. @@ -101,22 +99,22 @@ async def create( async def edit( self, server_id: int, - name: Optional[str] = None, - color: Optional[ServerColor] = None, - smtp_api_activated: Optional[bool] = None, - raw_email_enabled: Optional[bool] = None, - inbound_hook_url: Optional[str] = None, - bounce_hook_url: Optional[str] = None, - open_hook_url: Optional[str] = None, - delivery_hook_url: Optional[str] = None, - click_hook_url: Optional[str] = None, - post_first_open_only: Optional[bool] = None, - inbound_domain: Optional[str] = None, - inbound_spam_threshold: Optional[int] = None, - track_opens: Optional[bool] = None, - track_links: Optional[TrackLinks] = None, - include_bounce_content_in_hook: Optional[bool] = None, - enable_smtp_api_error_hooks: Optional[bool] = None, + name: str | None = None, + color: ServerColor | None = None, + smtp_api_activated: bool | None = None, + raw_email_enabled: bool | None = None, + inbound_hook_url: str | None = None, + bounce_hook_url: str | None = None, + open_hook_url: str | None = None, + delivery_hook_url: str | None = None, + click_hook_url: str | None = None, + post_first_open_only: bool | None = None, + inbound_domain: str | None = None, + inbound_spam_threshold: int | None = None, + track_opens: bool | None = None, + track_links: TrackLinks | None = None, + include_bounce_content_in_hook: bool | None = None, + enable_smtp_api_error_hooks: bool | None = None, ) -> Server: """ Update configuration for a server. @@ -185,7 +183,7 @@ async def list( self, count: int = 100, offset: int = 0, - name: Optional[str] = None, + name: str | None = None, ) -> Page[Server]: """ List servers on the account. diff --git a/postmark/models/servers/manager.py b/postmark/models/servers/manager.py index 1e7e7ac..75c27a6 100644 --- a/postmark/models/servers/manager.py +++ b/postmark/models/servers/manager.py @@ -1,5 +1,3 @@ -from typing import Optional - from postmark.utils.types import HTTPClient from .enums import ServerColor, TrackLinks @@ -19,22 +17,22 @@ async def get(self) -> Server: async def edit( self, - name: Optional[str] = None, - color: Optional[ServerColor] = None, - raw_email_enabled: Optional[bool] = None, - smtp_api_activated: Optional[bool] = None, - inbound_hook_url: Optional[str] = None, - bounce_hook_url: Optional[str] = None, - open_hook_url: Optional[str] = None, - delivery_hook_url: Optional[str] = None, - click_hook_url: Optional[str] = None, - post_first_open_only: Optional[bool] = None, - track_opens: Optional[bool] = None, - track_links: Optional[TrackLinks] = None, - inbound_domain: Optional[str] = None, - inbound_spam_threshold: Optional[int] = None, - include_bounce_content_in_hook: Optional[bool] = None, - enable_smtp_api_error_hooks: Optional[bool] = None, + name: str | None = None, + color: ServerColor | None = None, + raw_email_enabled: bool | None = None, + smtp_api_activated: bool | None = None, + inbound_hook_url: str | None = None, + bounce_hook_url: str | None = None, + open_hook_url: str | None = None, + delivery_hook_url: str | None = None, + click_hook_url: str | None = None, + post_first_open_only: bool | None = None, + track_opens: bool | None = None, + track_links: TrackLinks | None = None, + inbound_domain: str | None = None, + inbound_spam_threshold: int | None = None, + include_bounce_content_in_hook: bool | None = None, + enable_smtp_api_error_hooks: bool | None = None, ) -> Server: """ Update configuration for the current server. diff --git a/postmark/models/servers/schemas.py b/postmark/models/servers/schemas.py index 26bb6d8..555a21a 100644 --- a/postmark/models/servers/schemas.py +++ b/postmark/models/servers/schemas.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel, ConfigDict, Field from .enums import DeliveryType, ServerColor, TrackLinks @@ -10,25 +8,25 @@ class Server(BaseModel): id: int = Field(alias="ID") name: str = Field(alias="Name") - api_tokens: List[str] = Field(alias="ApiTokens") + api_tokens: list[str] = Field(alias="ApiTokens") color: ServerColor = Field(alias="Color") smtp_api_activated: bool = Field(alias="SmtpApiActivated") raw_email_enabled: bool = Field(alias="RawEmailEnabled") delivery_type: DeliveryType = Field(alias="DeliveryType") server_link: str = Field(alias="ServerLink") inbound_address: str = Field(alias="InboundAddress") - inbound_hook_url: Optional[str] = Field(None, alias="InboundHookUrl") - bounce_hook_url: Optional[str] = Field(None, alias="BounceHookUrl") - open_hook_url: Optional[str] = Field(None, alias="OpenHookUrl") - delivery_hook_url: Optional[str] = Field(None, alias="DeliveryHookUrl") + inbound_hook_url: str | None = Field(None, alias="InboundHookUrl") + bounce_hook_url: str | None = Field(None, alias="BounceHookUrl") + open_hook_url: str | None = Field(None, alias="OpenHookUrl") + delivery_hook_url: str | None = Field(None, alias="DeliveryHookUrl") post_first_open_only: bool = Field(alias="PostFirstOpenOnly") - inbound_domain: Optional[str] = Field(None, alias="InboundDomain") + inbound_domain: str | None = Field(None, alias="InboundDomain") inbound_hash: str = Field(alias="InboundHash") inbound_spam_threshold: int = Field(alias="InboundSpamThreshold") track_opens: bool = Field(alias="TrackOpens") track_links: TrackLinks = Field(alias="TrackLinks") include_bounce_content_in_hook: bool = Field(alias="IncludeBounceContentInHook") - click_hook_url: Optional[str] = Field(None, alias="ClickHookUrl") + click_hook_url: str | None = Field(None, alias="ClickHookUrl") enable_smtp_api_error_hooks: bool = Field(alias="EnableSmtpApiErrorHooks") model_config = ConfigDict(populate_by_name=True) @@ -38,7 +36,7 @@ class ServersListResponse(BaseModel): """Response from ``GET /servers``.""" total_count: int = Field(alias="TotalCount") - servers: List[Server] = Field(alias="Servers") + servers: list[Server] = Field(alias="Servers") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/signatures/manager.py b/postmark/models/signatures/manager.py index 8aaa75d..439c57d 100644 --- a/postmark/models/signatures/manager.py +++ b/postmark/models/signatures/manager.py @@ -1,5 +1,3 @@ -from typing import Optional - from postmark.models.page import Page from postmark.utils.types import HTTPClient @@ -44,9 +42,9 @@ async def create( self, sender: str, name: str, - reply_to: Optional[str] = None, - return_path_domain: Optional[str] = None, - confirmation_personal_note: Optional[str] = None, + reply_to: str | None = None, + return_path_domain: str | None = None, + confirmation_personal_note: str | None = None, ) -> SenderSignature: """ Create a new sender signature. @@ -75,9 +73,9 @@ async def edit( self, signature_id: int, name: str, - reply_to: Optional[str] = None, - return_path_domain: Optional[str] = None, - confirmation_personal_note: Optional[str] = None, + reply_to: str | None = None, + return_path_domain: str | None = None, + confirmation_personal_note: str | None = None, ) -> SenderSignature: """ Update a sender signature. diff --git a/postmark/models/signatures/schemas.py b/postmark/models/signatures/schemas.py index ebe27ea..28fc2cc 100644 --- a/postmark/models/signatures/schemas.py +++ b/postmark/models/signatures/schemas.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel, ConfigDict, Field @@ -26,28 +24,28 @@ class SenderSignature(BaseModel): name: str = Field(alias="Name") confirmed: bool = Field(alias="Confirmed") spf_verified: bool = Field(False, alias="SPFVerified") # deprecated by Postmark - spf_host: Optional[str] = Field(None, alias="SPFHost") - spf_text_value: Optional[str] = Field(None, alias="SPFTextValue") + spf_host: str | None = Field(None, alias="SPFHost") + spf_text_value: str | None = Field(None, alias="SPFTextValue") dkim_verified: bool = Field(alias="DKIMVerified") weak_dkim: bool = Field(alias="WeakDKIM") - dkim_host: Optional[str] = Field(None, alias="DKIMHost") - dkim_text_value: Optional[str] = Field(None, alias="DKIMTextValue") - dkim_pending_host: Optional[str] = Field(None, alias="DKIMPendingHost") - dkim_pending_text_value: Optional[str] = Field(None, alias="DKIMPendingTextValue") - dkim_revoked_host: Optional[str] = Field(None, alias="DKIMRevokedHost") - dkim_revoked_text_value: Optional[str] = Field(None, alias="DKIMRevokedTextValue") - safe_to_remove_revoked_key_from_dns: Optional[bool] = Field( + dkim_host: str | None = Field(None, alias="DKIMHost") + dkim_text_value: str | None = Field(None, alias="DKIMTextValue") + dkim_pending_host: str | None = Field(None, alias="DKIMPendingHost") + dkim_pending_text_value: str | None = Field(None, alias="DKIMPendingTextValue") + dkim_revoked_host: str | None = Field(None, alias="DKIMRevokedHost") + dkim_revoked_text_value: str | None = Field(None, alias="DKIMRevokedTextValue") + safe_to_remove_revoked_key_from_dns: bool | None = Field( None, alias="SafeToRemoveRevokedKeyFromDNS" ) - dkim_update_status: Optional[str] = Field(None, alias="DKIMUpdateStatus") - return_path_domain: Optional[str] = Field(None, alias="ReturnPathDomain") - return_path_domain_verified: Optional[bool] = Field( + dkim_update_status: str | None = Field(None, alias="DKIMUpdateStatus") + return_path_domain: str | None = Field(None, alias="ReturnPathDomain") + return_path_domain_verified: bool | None = Field( None, alias="ReturnPathDomainVerified" ) - return_path_domain_cname_value: Optional[str] = Field( + return_path_domain_cname_value: str | None = Field( None, alias="ReturnPathDomainCNAMEValue" ) - confirmation_personal_note: Optional[str] = Field( + confirmation_personal_note: str | None = Field( None, alias="ConfirmationPersonalNote" ) @@ -58,7 +56,7 @@ class SenderSignaturesListResponse(BaseModel): """Response from ``GET /senders``.""" total_count: int = Field(alias="TotalCount") - sender_signatures: List[SenderSignatureListItem] = Field(alias="SenderSignatures") + sender_signatures: list[SenderSignatureListItem] = Field(alias="SenderSignatures") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/stats/manager.py b/postmark/models/stats/manager.py index daac8e6..aab43bb 100644 --- a/postmark/models/stats/manager.py +++ b/postmark/models/stats/manager.py @@ -1,5 +1,4 @@ from datetime import date -from typing import Optional from postmark.utils.types import HTTPClient @@ -26,10 +25,10 @@ def __init__(self, client: HTTPClient): def _params( self, - tag: Optional[str], - from_date: Optional[date], - to_date: Optional[date], - message_stream: Optional[str], + tag: str | None, + from_date: date | None, + to_date: date | None, + message_stream: str | None, ) -> dict: params: dict = {} if tag is not None: @@ -44,10 +43,10 @@ def _params( async def overview( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> OutboundOverview: """Return aggregate outbound statistics.""" response = await self.client.get( @@ -58,10 +57,10 @@ async def overview( async def sent_counts( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> SentCounts: """Return daily sent counts.""" response = await self.client.get( @@ -72,10 +71,10 @@ async def sent_counts( async def bounce_counts( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> BounceCounts: """Return daily bounce counts broken down by type.""" response = await self.client.get( @@ -86,10 +85,10 @@ async def bounce_counts( async def spam_counts( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> SpamComplaints: """Return daily spam complaint counts.""" response = await self.client.get( @@ -100,10 +99,10 @@ async def spam_counts( async def tracked_counts( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> TrackedCounts: """Return daily tracked email counts.""" response = await self.client.get( @@ -114,10 +113,10 @@ async def tracked_counts( async def open_counts( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> OpenCounts: """Return daily email open counts.""" response = await self.client.get( @@ -128,10 +127,10 @@ async def open_counts( async def platform_usage( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> PlatformUsage: """Return daily email open counts broken down by platform.""" response = await self.client.get( @@ -142,10 +141,10 @@ async def platform_usage( async def email_client_usage( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> EmailClientUsage: """Return email open counts broken down by email client.""" response = await self.client.get( @@ -156,10 +155,10 @@ async def email_client_usage( async def click_counts( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> ClickCounts: """Return daily click counts.""" response = await self.client.get( @@ -170,10 +169,10 @@ async def click_counts( async def browser_usage( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> BrowserUsage: """Return click counts broken down by browser family.""" response = await self.client.get( @@ -184,10 +183,10 @@ async def browser_usage( async def browser_platform_usage( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> BrowserPlatformUsage: """Return click counts broken down by browser platform.""" response = await self.client.get( @@ -198,10 +197,10 @@ async def browser_platform_usage( async def click_location( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> ClickLocation: """Return click counts broken down by link location (HTML vs text).""" response = await self.client.get( @@ -212,10 +211,10 @@ async def click_location( async def read_times( self, - tag: Optional[str] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - message_stream: Optional[str] = None, + tag: str | None = None, + from_date: date | None = None, + to_date: date | None = None, + message_stream: str | None = None, ) -> ReadTimes: """Return email open read-time distribution.""" response = await self.client.get( diff --git a/postmark/models/stats/schemas.py b/postmark/models/stats/schemas.py index 79a9581..7d07458 100644 --- a/postmark/models/stats/schemas.py +++ b/postmark/models/stats/schemas.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List +from typing import Any from pydantic import BaseModel, ConfigDict, Field @@ -36,7 +36,7 @@ class SentDay(BaseModel): class SentCounts(BaseModel): """Response from ``GET /stats/outbound/sends``.""" - days: List[SentDay] = Field(alias="Days") + days: list[SentDay] = Field(alias="Days") sent: int = Field(0, alias="Sent") model_config = ConfigDict(populate_by_name=True) @@ -55,7 +55,7 @@ class BounceDay(BaseModel): class BounceCounts(BaseModel): """Response from ``GET /stats/outbound/bounces``.""" - days: List[BounceDay] = Field(alias="Days") + days: list[BounceDay] = Field(alias="Days") hard_bounce: int = Field(0, alias="HardBounce") smtp_api_error: int = Field(0, alias="SMTPApiError") soft_bounce: int = Field(0, alias="SoftBounce") @@ -74,7 +74,7 @@ class SpamComplaintDay(BaseModel): class SpamComplaints(BaseModel): """Response from ``GET /stats/outbound/spam``.""" - days: List[SpamComplaintDay] = Field(alias="Days") + days: list[SpamComplaintDay] = Field(alias="Days") spam_complaint: int = Field(0, alias="SpamComplaint") model_config = ConfigDict(populate_by_name=True) @@ -90,7 +90,7 @@ class TrackedDay(BaseModel): class TrackedCounts(BaseModel): """Response from ``GET /stats/outbound/tracked``.""" - days: List[TrackedDay] = Field(alias="Days") + days: list[TrackedDay] = Field(alias="Days") tracked: int = Field(0, alias="Tracked") model_config = ConfigDict(populate_by_name=True) @@ -107,7 +107,7 @@ class OpenDay(BaseModel): class OpenCounts(BaseModel): """Response from ``GET /stats/outbound/opens``.""" - days: List[OpenDay] = Field(alias="Days") + days: list[OpenDay] = Field(alias="Days") opens: int = Field(0, alias="Opens") unique: int = Field(0, alias="Unique") @@ -127,7 +127,7 @@ class PlatformDay(BaseModel): class PlatformUsage(BaseModel): """Response from ``GET /stats/outbound/opens/platforms``.""" - days: List[PlatformDay] = Field(alias="Days") + days: list[PlatformDay] = Field(alias="Days") desktop: int = Field(0, alias="Desktop") mobile: int = Field(0, alias="Mobile") unknown: int = Field(0, alias="Unknown") @@ -144,7 +144,7 @@ class EmailClientUsage(BaseModel): ``model_extra`` and individual day dicts via ``days``. """ - days: List[Dict[str, Any]] = Field(alias="Days") + days: list[dict[str, Any]] = Field(alias="Days") model_config = ConfigDict(populate_by_name=True, extra="allow") @@ -160,7 +160,7 @@ class ClickDay(BaseModel): class ClickCounts(BaseModel): """Response from ``GET /stats/outbound/clicks``.""" - days: List[ClickDay] = Field(alias="Days") + days: list[ClickDay] = Field(alias="Days") clicks: int = Field(0, alias="Clicks") unique: int = Field(0, alias="Unique") @@ -174,7 +174,7 @@ class BrowserUsage(BaseModel): and within each day object. Access totals via ``model_extra``. """ - days: List[Dict[str, Any]] = Field(alias="Days") + days: list[dict[str, Any]] = Field(alias="Days") model_config = ConfigDict(populate_by_name=True, extra="allow") @@ -191,7 +191,7 @@ class BrowserPlatformDay(BaseModel): class BrowserPlatformUsage(BaseModel): """Response from ``GET /stats/outbound/clicks/platforms``.""" - days: List[BrowserPlatformDay] = Field(alias="Days") + days: list[BrowserPlatformDay] = Field(alias="Days") desktop: int = Field(0, alias="Desktop") mobile: int = Field(0, alias="Mobile") unknown: int = Field(0, alias="Unknown") @@ -210,7 +210,7 @@ class ClickLocationDay(BaseModel): class ClickLocation(BaseModel): """Response from ``GET /stats/outbound/clicks/location``.""" - days: List[ClickLocationDay] = Field(alias="Days") + days: list[ClickLocationDay] = Field(alias="Days") html: int = Field(0, alias="HTML") text: int = Field(0, alias="Text") @@ -224,6 +224,6 @@ class ReadTimes(BaseModel): day object alongside ``Date``. Access totals via ``model_extra``. """ - days: List[Dict[str, Any]] = Field(alias="Days") + days: list[dict[str, Any]] = Field(alias="Days") model_config = ConfigDict(populate_by_name=True, extra="allow") diff --git a/postmark/models/streams/manager.py b/postmark/models/streams/manager.py index db31083..6f2ea22 100644 --- a/postmark/models/streams/manager.py +++ b/postmark/models/streams/manager.py @@ -1,5 +1,3 @@ -from typing import Optional - from postmark.models.page import Page from postmark.utils.types import HTTPClient @@ -17,7 +15,7 @@ def __init__(self, client: HTTPClient): async def list( self, - message_stream_type: Optional[MessageStreamType] = None, + message_stream_type: MessageStreamType | None = None, include_archived: bool = False, ) -> Page[MessageStream]: """ @@ -47,8 +45,8 @@ async def create( id: str, name: str, message_stream_type: MessageStreamType, - description: Optional[str] = None, - unsubscribe_handling_type: Optional[UnsubscribeHandlingType] = None, + description: str | None = None, + unsubscribe_handling_type: UnsubscribeHandlingType | None = None, ) -> MessageStream: """ Create a new message stream. @@ -79,9 +77,9 @@ async def create( async def edit( self, stream_id: str, - name: Optional[str] = None, - description: Optional[str] = None, - unsubscribe_handling_type: Optional[UnsubscribeHandlingType] = None, + name: str | None = None, + description: str | None = None, + unsubscribe_handling_type: UnsubscribeHandlingType | None = None, ) -> MessageStream: """ Update a message stream. diff --git a/postmark/models/streams/schemas.py b/postmark/models/streams/schemas.py index 446d76b..d1f5000 100644 --- a/postmark/models/streams/schemas.py +++ b/postmark/models/streams/schemas.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import List, Optional from pydantic import BaseModel, ConfigDict, Field @@ -20,12 +19,12 @@ class MessageStream(BaseModel): id: str = Field(alias="ID") server_id: int = Field(alias="ServerID") name: str = Field(alias="Name") - description: Optional[str] = Field(None, alias="Description") + description: str | None = Field(None, alias="Description") message_stream_type: MessageStreamType = Field(alias="MessageStreamType") created_at: datetime = Field(alias="CreatedAt") - updated_at: Optional[datetime] = Field(None, alias="UpdatedAt") - archived_at: Optional[datetime] = Field(None, alias="ArchivedAt") - expected_purge_date: Optional[datetime] = Field(None, alias="ExpectedPurgeDate") + updated_at: datetime | None = Field(None, alias="UpdatedAt") + archived_at: datetime | None = Field(None, alias="ArchivedAt") + expected_purge_date: datetime | None = Field(None, alias="ExpectedPurgeDate") subscription_management_configuration: SubscriptionManagementConfiguration = Field( alias="SubscriptionManagementConfiguration" ) @@ -37,7 +36,7 @@ class MessageStreamListResponse(BaseModel): """Response from ``GET /message-streams``.""" total_count: int = Field(alias="TotalCount") - message_streams: List[MessageStream] = Field(alias="MessageStreams") + message_streams: list[MessageStream] = Field(alias="MessageStreams") model_config = ConfigDict(populate_by_name=True) @@ -47,6 +46,6 @@ class ArchiveMessageStreamResponse(BaseModel): id: str = Field(alias="ID") server_id: int = Field(alias="ServerID") - expected_purge_date: Optional[datetime] = Field(None, alias="ExpectedPurgeDate") + expected_purge_date: datetime | None = Field(None, alias="ExpectedPurgeDate") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/suppressions/manager.py b/postmark/models/suppressions/manager.py index 048b3c8..69160c0 100644 --- a/postmark/models/suppressions/manager.py +++ b/postmark/models/suppressions/manager.py @@ -1,5 +1,4 @@ from datetime import date -from typing import List, Optional from postmark.utils.types import HTTPClient @@ -14,12 +13,12 @@ def __init__(self, client: HTTPClient): async def dump( self, stream_id: str, - suppression_reason: Optional[SuppressionReason] = None, - origin: Optional[SuppressionOrigin] = None, - from_date: Optional[date] = None, - to_date: Optional[date] = None, - email_address: Optional[str] = None, - ) -> List[SuppressionEntry]: + suppression_reason: SuppressionReason | None = None, + origin: SuppressionOrigin | None = None, + from_date: date | None = None, + to_date: date | None = None, + email_address: str | None = None, + ) -> list[SuppressionEntry]: """ Return all suppressions for a message stream. @@ -49,8 +48,8 @@ async def dump( return [SuppressionEntry(**s) for s in response.json()["Suppressions"]] async def create( - self, stream_id: str, email_addresses: List[str] - ) -> List[SuppressionResult]: + self, stream_id: str, email_addresses: list[str] + ) -> list[SuppressionResult]: """ Suppress one or more email addresses on a message stream (max 50 per call). @@ -67,8 +66,8 @@ async def create( return [SuppressionResult(**s) for s in response.json()["Suppressions"]] async def delete( - self, stream_id: str, email_addresses: List[str] - ) -> List[SuppressionResult]: + self, stream_id: str, email_addresses: list[str] + ) -> list[SuppressionResult]: """ Remove suppressions for one or more email addresses (max 50 per call). diff --git a/postmark/models/suppressions/schemas.py b/postmark/models/suppressions/schemas.py index 61e249d..eceda99 100644 --- a/postmark/models/suppressions/schemas.py +++ b/postmark/models/suppressions/schemas.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Optional from pydantic import BaseModel, ConfigDict, Field @@ -22,6 +21,6 @@ class SuppressionResult(BaseModel): email_address: str = Field(alias="EmailAddress") status: str = Field(alias="Status") - message: Optional[str] = Field(None, alias="Message") + message: str | None = Field(None, alias="Message") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/templates/account_manager.py b/postmark/models/templates/account_manager.py index 3363877..0b1d514 100644 --- a/postmark/models/templates/account_manager.py +++ b/postmark/models/templates/account_manager.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Union +from typing import Any from pydantic import ValidationError @@ -10,7 +10,7 @@ class AccountTemplateManager: def __init__(self, client: HTTPClient): self.client = client - async def push(self, request: Union[Dict[str, Any], Any]) -> Any: + async def push(self, request: dict[str, Any] | Any) -> Any: """Push templates from one server to another.""" from .schemas import PushTemplatesRequest, PushTemplatesResponse diff --git a/postmark/models/templates/manager.py b/postmark/models/templates/manager.py index 4c9e1ec..95de104 100644 --- a/postmark/models/templates/manager.py +++ b/postmark/models/templates/manager.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Dict, Optional, Union +from typing import Any from pydantic import ValidationError @@ -36,13 +36,13 @@ class TemplateManager: def __init__(self, client: HTTPClient): self.client = client - async def get(self, template_id_or_alias: Union[int, str]) -> Template: + async def get(self, template_id_or_alias: int | str) -> Template: """Get a template by ID or alias.""" response = await self.client.get(f"/templates/{template_id_or_alias}") return Template(**response.json()) async def create( - self, request: Union[CreateTemplateRequest, Dict[str, Any]] + self, request: CreateTemplateRequest | dict[str, Any] ) -> UpsertTemplateResponse: """Create a new template.""" req = _parse_request(CreateTemplateRequest, request) @@ -54,8 +54,8 @@ async def create( async def edit( self, - template_id_or_alias: Union[int, str], - request: Union[EditTemplateRequest, Dict[str, Any]], + template_id_or_alias: int | str, + request: EditTemplateRequest | dict[str, Any], ) -> UpsertTemplateResponse: """Edit an existing template.""" req = _parse_request(EditTemplateRequest, request) @@ -69,7 +69,7 @@ async def list( self, count: int = 100, offset: int = 0, - template_type: Optional[TemplateTypeFilter] = None, + template_type: TemplateTypeFilter | None = None, ) -> Page[TemplateSummary]: """List templates with optional type filter.""" if count > 500: @@ -77,7 +77,7 @@ async def list( if count + offset > 10000: raise ValueError("count + offset cannot exceed 10,000 templates") - params: Dict[str, Any] = {"count": count, "offset": offset} + params: dict[str, Any] = {"count": count, "offset": offset} if template_type is not None: params["templateType"] = template_type.value @@ -85,15 +85,13 @@ async def list( data = TemplateListResponse(**response.json()) return Page(items=data.templates, total=data.total_count) - async def delete( - self, template_id_or_alias: Union[int, str] - ) -> DeleteTemplateResponse: + async def delete(self, template_id_or_alias: int | str) -> DeleteTemplateResponse: """Delete a template by ID or alias.""" response = await self.client.delete(f"/templates/{template_id_or_alias}") return DeleteTemplateResponse(**response.json()) async def validate( - self, request: Union[ValidateTemplateRequest, Dict[str, Any]] + self, request: ValidateTemplateRequest | dict[str, Any] ) -> ValidateTemplateResponse: """Validate template content.""" req = _parse_request(ValidateTemplateRequest, request) diff --git a/postmark/models/templates/schemas.py b/postmark/models/templates/schemas.py index 7e111dd..4f44dbe 100644 --- a/postmark/models/templates/schemas.py +++ b/postmark/models/templates/schemas.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional +from typing import Any from pydantic import BaseModel, ConfigDict, Field @@ -30,22 +30,22 @@ class TemplateEmail(BaseModel): - template_id: Optional[int] = Field(None, alias="TemplateId") - template_alias: Optional[str] = Field(None, alias="TemplateAlias") - template_model: Dict[str, Any] = Field(default_factory=dict, alias="TemplateModel") + template_id: int | None = Field(None, alias="TemplateId") + template_alias: str | None = Field(None, alias="TemplateAlias") + template_model: dict[str, Any] = Field(default_factory=dict, alias="TemplateModel") sender: str = Field(alias="From") to: str = Field(alias="To") - cc: Optional[str] = Field(None, alias="Cc") - bcc: Optional[str] = Field(None, alias="Bcc") - reply_to: Optional[str] = Field(None, alias="ReplyTo") - tag: Optional[str] = Field(None, alias="Tag") - inline_css: Optional[bool] = Field(None, alias="InlineCss") - headers: List[Header] = Field(default_factory=list, alias="Headers") - track_opens: Optional[bool] = Field(None, alias="TrackOpens") - track_links: Optional[TrackLinksOption] = Field(None, alias="TrackLinks") - attachments: List[Attachment] = Field(default_factory=list, alias="Attachments") - metadata: Dict[str, str] = Field(default_factory=dict, alias="Metadata") - message_stream: Optional[str] = Field(None, alias="MessageStream") + cc: str | None = Field(None, alias="Cc") + bcc: str | None = Field(None, alias="Bcc") + reply_to: str | None = Field(None, alias="ReplyTo") + tag: str | None = Field(None, alias="Tag") + inline_css: bool | None = Field(None, alias="InlineCss") + headers: list[Header] = Field(default_factory=list, alias="Headers") + track_opens: bool | None = Field(None, alias="TrackOpens") + track_links: TrackLinksOption | None = Field(None, alias="TrackLinks") + attachments: list[Attachment] = Field(default_factory=list, alias="Attachments") + metadata: dict[str, str] = Field(default_factory=dict, alias="Metadata") + message_stream: str | None = Field(None, alias="MessageStream") model_config = ConfigDict(populate_by_name=True) @@ -54,9 +54,9 @@ class TemplateSummary(BaseModel): active: bool = Field(alias="Active") template_id: int = Field(alias="TemplateId") name: str = Field(alias="Name") - alias: Optional[str] = Field(None, alias="Alias") + alias: str | None = Field(None, alias="Alias") template_type: TemplateType = Field(alias="TemplateType") - layout_template: Optional[str] = Field(None, alias="LayoutTemplate") + layout_template: str | None = Field(None, alias="LayoutTemplate") model_config = ConfigDict(populate_by_name=True) @@ -64,44 +64,44 @@ class TemplateSummary(BaseModel): class Template(BaseModel): template_id: int = Field(alias="TemplateId") name: str = Field(alias="Name") - subject: Optional[str] = Field(None, alias="Subject") - html_body: Optional[str] = Field(None, alias="HtmlBody") - text_body: Optional[str] = Field(None, alias="TextBody") + subject: str | None = Field(None, alias="Subject") + html_body: str | None = Field(None, alias="HtmlBody") + text_body: str | None = Field(None, alias="TextBody") associated_server_id: int = Field(alias="AssociatedServerId") active: bool = Field(alias="Active") - alias: Optional[str] = Field(None, alias="Alias") + alias: str | None = Field(None, alias="Alias") template_type: TemplateType = Field(alias="TemplateType") - layout_template: Optional[str] = Field(None, alias="LayoutTemplate") + layout_template: str | None = Field(None, alias="LayoutTemplate") model_config = ConfigDict(populate_by_name=True) class TemplateListResponse(BaseModel): total_count: int = Field(alias="TotalCount") - templates: List[TemplateSummary] = Field(alias="Templates") + templates: list[TemplateSummary] = Field(alias="Templates") model_config = ConfigDict(populate_by_name=True) class CreateTemplateRequest(BaseModel): name: str = Field(alias="Name") - alias: Optional[str] = Field(None, alias="Alias") - subject: Optional[str] = Field(None, alias="Subject") - html_body: Optional[str] = Field(None, alias="HtmlBody") - text_body: Optional[str] = Field(None, alias="TextBody") - template_type: Optional[TemplateType] = Field(None, alias="TemplateType") - layout_template: Optional[str] = Field(None, alias="LayoutTemplate") + alias: str | None = Field(None, alias="Alias") + subject: str | None = Field(None, alias="Subject") + html_body: str | None = Field(None, alias="HtmlBody") + text_body: str | None = Field(None, alias="TextBody") + template_type: TemplateType | None = Field(None, alias="TemplateType") + layout_template: str | None = Field(None, alias="LayoutTemplate") model_config = ConfigDict(populate_by_name=True) class EditTemplateRequest(BaseModel): - name: Optional[str] = Field(None, alias="Name") - alias: Optional[str] = Field(None, alias="Alias") - subject: Optional[str] = Field(None, alias="Subject") - html_body: Optional[str] = Field(None, alias="HtmlBody") - text_body: Optional[str] = Field(None, alias="TextBody") - layout_template: Optional[str] = Field(None, alias="LayoutTemplate") + name: str | None = Field(None, alias="Name") + alias: str | None = Field(None, alias="Alias") + subject: str | None = Field(None, alias="Subject") + html_body: str | None = Field(None, alias="HtmlBody") + text_body: str | None = Field(None, alias="TextBody") + layout_template: str | None = Field(None, alias="LayoutTemplate") model_config = ConfigDict(populate_by_name=True) @@ -110,9 +110,9 @@ class UpsertTemplateResponse(BaseModel): template_id: int = Field(alias="TemplateId") name: str = Field(alias="Name") active: bool = Field(alias="Active") - alias: Optional[str] = Field(None, alias="Alias") + alias: str | None = Field(None, alias="Alias") template_type: TemplateType = Field(alias="TemplateType") - layout_template: Optional[str] = Field(None, alias="LayoutTemplate") + layout_template: str | None = Field(None, alias="LayoutTemplate") model_config = ConfigDict(populate_by_name=True) @@ -126,40 +126,40 @@ class DeleteTemplateResponse(BaseModel): class ValidationError(BaseModel): message: str = Field(alias="Message") - line: Optional[int] = Field(None, alias="Line") - character_position: Optional[int] = Field(None, alias="CharacterPosition") + line: int | None = Field(None, alias="Line") + character_position: int | None = Field(None, alias="CharacterPosition") model_config = ConfigDict(populate_by_name=True) class TemplateContentValidation(BaseModel): content_is_valid: bool = Field(alias="ContentIsValid") - validation_errors: List[ValidationError] = Field(alias="ValidationErrors") - rendered_content: Optional[str] = Field(None, alias="RenderedContent") + validation_errors: list[ValidationError] = Field(alias="ValidationErrors") + rendered_content: str | None = Field(None, alias="RenderedContent") model_config = ConfigDict(populate_by_name=True) class ValidateTemplateRequest(BaseModel): - subject: Optional[str] = Field(None, alias="Subject") - html_body: Optional[str] = Field(None, alias="HtmlBody") - text_body: Optional[str] = Field(None, alias="TextBody") - test_render_model: Optional[Dict[str, Any]] = Field(None, alias="TestRenderModel") - inline_css_for_html_test_render: Optional[bool] = Field( + subject: str | None = Field(None, alias="Subject") + html_body: str | None = Field(None, alias="HtmlBody") + text_body: str | None = Field(None, alias="TextBody") + test_render_model: dict[str, Any] | None = Field(None, alias="TestRenderModel") + inline_css_for_html_test_render: bool | None = Field( None, alias="InlineCssForHtmlTestRender" ) - template_type: Optional[TemplateType] = Field(None, alias="TemplateType") - layout_template: Optional[str] = Field(None, alias="LayoutTemplate") + template_type: TemplateType | None = Field(None, alias="TemplateType") + layout_template: str | None = Field(None, alias="LayoutTemplate") model_config = ConfigDict(populate_by_name=True) class ValidateTemplateResponse(BaseModel): all_content_is_valid: bool = Field(alias="AllContentIsValid") - html_body: Optional[TemplateContentValidation] = Field(None, alias="HtmlBody") - text_body: Optional[TemplateContentValidation] = Field(None, alias="TextBody") - subject: Optional[TemplateContentValidation] = Field(None, alias="Subject") - suggested_template_model: Optional[Dict[str, Any]] = Field( + html_body: TemplateContentValidation | None = Field(None, alias="HtmlBody") + text_body: TemplateContentValidation | None = Field(None, alias="TextBody") + subject: TemplateContentValidation | None = Field(None, alias="Subject") + suggested_template_model: dict[str, Any] | None = Field( None, alias="SuggestedTemplateModel" ) @@ -176,8 +176,8 @@ class PushTemplatesRequest(BaseModel): class PushedTemplate(BaseModel): action: TemplateAction = Field(alias="Action") - template_id: Optional[int] = Field(None, alias="TemplateId") - alias: Optional[str] = Field(None, alias="Alias") + template_id: int | None = Field(None, alias="TemplateId") + alias: str | None = Field(None, alias="Alias") name: str = Field(alias="Name") template_type: TemplateType = Field(alias="TemplateType") @@ -186,6 +186,6 @@ class PushedTemplate(BaseModel): class PushTemplatesResponse(BaseModel): total_count: int = Field(alias="TotalCount") - templates: List[PushedTemplate] = Field(alias="Templates") + templates: list[PushedTemplate] = Field(alias="Templates") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/models/webhooks/manager.py b/postmark/models/webhooks/manager.py index 05acda9..46489b8 100644 --- a/postmark/models/webhooks/manager.py +++ b/postmark/models/webhooks/manager.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +import builtins from postmark.utils.types import HTTPClient @@ -9,7 +9,7 @@ class WebhookManager: def __init__(self, client: HTTPClient): self.client = client - async def list(self, message_stream: Optional[str] = None) -> List[Webhook]: + async def list(self, message_stream: str | None = None) -> list[Webhook]: """List all webhooks on the server, optionally filtered by message stream.""" params: dict = {} if message_stream is not None: @@ -26,10 +26,10 @@ async def get(self, webhook_id: int) -> Webhook: async def create( self, url: str, - message_stream: Optional[str] = None, - http_auth: Optional[Dict] = None, - http_headers: Optional[List[Dict]] = None, - triggers: Optional[Dict] = None, + message_stream: str | None = None, + http_auth: dict | None = None, + http_headers: builtins.list[dict] | None = None, + triggers: dict | None = None, ) -> Webhook: """ Create a webhook. @@ -61,10 +61,10 @@ async def create( async def edit( self, webhook_id: int, - url: Optional[str] = None, - http_auth: Optional[Dict] = None, - http_headers: Optional[List[Dict]] = None, - triggers: Optional[Dict] = None, + url: str | None = None, + http_auth: dict | None = None, + http_headers: builtins.list[dict] | None = None, + triggers: dict | None = None, ) -> Webhook: """ Update a webhook. diff --git a/postmark/models/webhooks/schemas.py b/postmark/models/webhooks/schemas.py index d521ca5..04dc624 100644 --- a/postmark/models/webhooks/schemas.py +++ b/postmark/models/webhooks/schemas.py @@ -1,5 +1,3 @@ -from typing import List, Optional - from pydantic import BaseModel, ConfigDict, Field @@ -73,8 +71,8 @@ class Webhook(BaseModel): id: int = Field(alias="ID") url: str = Field(alias="Url") message_stream: str = Field(alias="MessageStream") - http_auth: Optional[HttpAuth] = Field(None, alias="HttpAuth") - http_headers: Optional[List[HttpHeader]] = Field(None, alias="HttpHeaders") + http_auth: HttpAuth | None = Field(None, alias="HttpAuth") + http_headers: list[HttpHeader] | None = Field(None, alias="HttpHeaders") triggers: WebhookTriggers = Field(alias="Triggers") model_config = ConfigDict(populate_by_name=True) @@ -83,7 +81,7 @@ class Webhook(BaseModel): class WebhooksListResponse(BaseModel): """Response from ``GET /webhooks``.""" - webhooks: List[Webhook] = Field(alias="Webhooks") + webhooks: list[Webhook] = Field(alias="Webhooks") model_config = ConfigDict(populate_by_name=True) diff --git a/postmark/sync.py b/postmark/sync.py index accd42d..cb2352e 100644 --- a/postmark/sync.py +++ b/postmark/sync.py @@ -23,7 +23,6 @@ import inspect import os import threading -from typing import Optional from postmark.clients.account_client import AccountClient as _AsyncAccountClient from postmark.clients.server_client import ServerClient as _AsyncServerClient @@ -133,7 +132,7 @@ def __init__( server_token: str, retries: int = 3, timeout: float = 5.0, - base_url: Optional[str] = None, + base_url: str | None = None, ): self._async = _AsyncServerClient( server_token, retries=retries, timeout=timeout, base_url=base_url @@ -183,7 +182,7 @@ def __init__( account_token: str, retries: int = 3, timeout: float = 30.0, - base_url: Optional[str] = None, + base_url: str | None = None, ): self._async = _AsyncAccountClient( account_token, retries=retries, timeout=timeout, base_url=base_url diff --git a/postmark/utils/message_utils.py b/postmark/utils/message_utils.py index 31de855..02e32bb 100644 --- a/postmark/utils/message_utils.py +++ b/postmark/utils/message_utils.py @@ -1,6 +1,5 @@ import logging import re -from typing import List from pydantic import ( EmailStr, @@ -43,7 +42,7 @@ def validate_formatted_email(v: str) -> str: return v -def validate_email_list(v: List[str]) -> List[str]: +def validate_email_list(v: list[str]) -> list[str]: """Validate that all items in a list are valid email addresses""" for email in v: try: diff --git a/postmark/utils/pagination.py b/postmark/utils/pagination.py index 86c62e8..05ee390 100644 --- a/postmark/utils/pagination.py +++ b/postmark/utils/pagination.py @@ -1,4 +1,5 @@ -from typing import AsyncGenerator, Awaitable, Callable, TypeVar +from collections.abc import AsyncGenerator, Awaitable, Callable +from typing import TypeVar from postmark.models.page import Page diff --git a/postmark/utils/server_utils.py b/postmark/utils/server_utils.py index 9cc554e..1547e52 100644 --- a/postmark/utils/server_utils.py +++ b/postmark/utils/server_utils.py @@ -1,10 +1,10 @@ import json -from typing import Any, Optional +from typing import Any import httpx -def _coerce_postmark_error_code(raw: Any) -> Optional[int]: +def _coerce_postmark_error_code(raw: Any) -> int | None: """Normalize Postmark ``ErrorCode`` from JSON (int or numeric string) to ``int`` or ``None``.""" if raw is None: return None @@ -25,7 +25,7 @@ def _coerce_postmark_error_code(raw: Any) -> Optional[int]: return None -def parse_error_response(response: httpx.Response) -> tuple[str, Optional[int]]: +def parse_error_response(response: httpx.Response) -> tuple[str, int | None]: """Parse error details from Postmark API response.""" try: error_data = response.json() diff --git a/postmark/utils/types.py b/postmark/utils/types.py index eb13fab..e1b5cc1 100644 --- a/postmark/utils/types.py +++ b/postmark/utils/types.py @@ -1,28 +1,28 @@ # postmark/utils/types.py -from typing import Any, Dict, List, Optional, Protocol, Union +from typing import Any, Protocol import httpx class HTTPClient(Protocol): async def get( - self, endpoint: str, params: Optional[Dict[str, Any]] = None + self, endpoint: str, params: dict[str, Any] | None = None ) -> httpx.Response: ... async def post( self, endpoint: str, - json: Union[Dict[str, Any], List[Dict[str, Any]], None] = None, + json: dict[str, Any] | list[dict[str, Any]] | None = None, ) -> httpx.Response: ... async def put( - self, endpoint: str, json: Optional[Dict[str, Any]] = None + self, endpoint: str, json: dict[str, Any] | None = None ) -> httpx.Response: ... async def patch( - self, endpoint: str, json: Optional[Dict[str, Any]] = None + self, endpoint: str, json: dict[str, Any] | None = None ) -> httpx.Response: ... async def delete( - self, endpoint: str, params: Optional[Dict[str, Any]] = None + self, endpoint: str, params: dict[str, Any] | None = None ) -> httpx.Response: ... diff --git a/pyproject.toml b/pyproject.toml index f177afd..bb4641b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,9 +51,10 @@ python-dotenv = "^1.2.2" [tool.ruff] line-length = 88 +target-version = "py310" [tool.ruff.lint] -select = ["E", "F", "I", "N"] +select = ["E", "F", "I", "N", "UP"] ignore = ["N818", "E501"] # allow "Exception" suffix on exception classes [tool.pytest.ini_options]