Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Simulator API Support #74

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

Check our main [developer changelog](https://developer.paddle.com/?utm_source=dx&utm_medium=paddle-python-sdk) for information about changes to the Paddle Billing platform, the Paddle API, and other developer tools.

## [Unreleased]

### Added

- Added simulation API support [related changelog](https://developer.paddle.com/changelog/2024/webhook-simulator?utm_source=dx&utm_medium=paddle-python-sdk)
- `Client.simulations.create`
- `Client.simulations.update`
- `Client.simulations.get`
- `Client.simulations.list`
- `Client.simulation_runs.create`
- `Client.simulation_runs.get`
- `Client.simulation_runs.list`
- `Client.simulation_run_events.replay`
- `Client.simulation_run_events.get`
- `Client.simulation_run_events.list`
- `Client.simulation_types.list`

## 1.4.0 - 2024-12-19

### Added
Expand Down
32 changes: 10 additions & 22 deletions paddle_billing/Client.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from json import dumps as json_dumps, JSONEncoder
from json import dumps as json_dumps
from logging import Logger, getLogger
from requests import Response, RequestException, Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from urllib.parse import urljoin, urlencode
from uuid import uuid4
from dataclasses import fields, is_dataclass
from paddle_billing.Json import PayloadEncoder

from paddle_billing.Operation import Operation
from paddle_billing.FiltersUndefined import FiltersUndefined
from paddle_billing.Undefined import Undefined
from paddle_billing.HasParameters import HasParameters
from paddle_billing.Options import Options
from paddle_billing.ResponseParser import ResponseParser
Expand All @@ -33,28 +31,14 @@
from paddle_billing.Resources.PricingPreviews.PricingPreviewsClient import PricingPreviewsClient
from paddle_billing.Resources.Products.ProductsClient import ProductsClient
from paddle_billing.Resources.Reports.ReportsClient import ReportsClient
from paddle_billing.Resources.Simulations.SimulationsClient import SimulationsClient
from paddle_billing.Resources.SimulationRuns.SimulationRunsClient import SimulationRunsClient
from paddle_billing.Resources.SimulationRunEvents.SimulationRunEventsClient import SimulationRunEventsClient
from paddle_billing.Resources.SimulationTypes.SimulationTypesClient import SimulationTypesClient
from paddle_billing.Resources.Subscriptions.SubscriptionsClient import SubscriptionsClient
from paddle_billing.Resources.Transactions.TransactionsClient import TransactionsClient


class PayloadEncoder(JSONEncoder):
def default(self, z):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PayloadEncoder was becoming more complex, so have moved out into paddle_billing.Json

if isinstance(z, Undefined):
return None

if hasattr(z, "to_json") and callable(z.to_json):
return z.to_json()

if is_dataclass(z):
data = {}
for field in fields(z):
data[field.name] = getattr(z, field.name)

return FiltersUndefined.filter_undefined_values(data)

return super().default(z)


class Client:
"""
Client for making API requests using Python's requests library.
Expand Down Expand Up @@ -98,6 +82,10 @@ def __init__(
self.pricing_previews = PricingPreviewsClient(self)
self.products = ProductsClient(self)
self.reports = ReportsClient(self)
self.simulations = SimulationsClient(self)
self.simulation_runs = SimulationRunsClient(self)
self.simulation_run_events = SimulationRunEventsClient(self)
self.simulation_types = SimulationTypesClient(self)
self.subscriptions = SubscriptionsClient(self)
self.transactions = TransactionsClient(self)
self.ip_addresses = IPAddressesClient(self)
Expand Down
13 changes: 13 additions & 0 deletions paddle_billing/Entities/Collections/SimulationCollection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

from paddle_billing.Entities.Collections.Collection import Collection
from paddle_billing.Entities.Collections.Paginator import Paginator
from paddle_billing.Entities.Simulation import Simulation


class SimulationCollection(Collection[Simulation]):
@classmethod
def from_list(cls, items_data: list, paginator: Paginator | None = None) -> SimulationCollection:
items: list[Simulation] = [Simulation.from_dict(item) for item in items_data]

return SimulationCollection(items, paginator)
13 changes: 13 additions & 0 deletions paddle_billing/Entities/Collections/SimulationRunCollection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

from paddle_billing.Entities.Collections.Collection import Collection
from paddle_billing.Entities.Collections.Paginator import Paginator
from paddle_billing.Entities.SimulationRun import SimulationRun


class SimulationRunCollection(Collection[SimulationRun]):
@classmethod
def from_list(cls, items_data: list, paginator: Paginator | None = None) -> SimulationRunCollection:
items: list[SimulationRun] = [SimulationRun.from_dict(item) for item in items_data]

return SimulationRunCollection(items, paginator)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

from paddle_billing.Entities.Collections.Collection import Collection
from paddle_billing.Entities.Collections.Paginator import Paginator
from paddle_billing.Entities.SimulationRunEvent import SimulationRunEvent


class SimulationRunEventCollection(Collection[SimulationRunEvent]):
@classmethod
def from_list(cls, items_data: list, paginator: Paginator | None = None) -> SimulationRunEventCollection:
items: list[SimulationRunEvent] = [SimulationRunEvent.from_dict(item) for item in items_data]

return SimulationRunEventCollection(items, paginator)
13 changes: 13 additions & 0 deletions paddle_billing/Entities/Collections/SimulationTypeCollection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

from paddle_billing.Entities.Collections.Collection import Collection
from paddle_billing.Entities.Collections.Paginator import Paginator
from paddle_billing.Entities.SimulationType import SimulationType


class SimulationTypeCollection(Collection[SimulationType]):
@classmethod
def from_list(cls, items_data: list, paginator: Paginator | None = None) -> SimulationTypeCollection:
items: list[SimulationType] = [SimulationType.from_dict(item) for item in items_data]

return SimulationTypeCollection(items, paginator)
4 changes: 4 additions & 0 deletions paddle_billing/Entities/Collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
from paddle_billing.Entities.Collections.PriceCollection import PriceCollection
from paddle_billing.Entities.Collections.ProductCollection import ProductCollection
from paddle_billing.Entities.Collections.ReportCollection import ReportCollection
from paddle_billing.Entities.Collections.SimulationCollection import SimulationCollection
from paddle_billing.Entities.Collections.SimulationRunCollection import SimulationRunCollection
from paddle_billing.Entities.Collections.SimulationRunEventCollection import SimulationRunEventCollection
from paddle_billing.Entities.Collections.SimulationTypeCollection import SimulationTypeCollection
from paddle_billing.Entities.Collections.SubscriptionCollection import SubscriptionCollection
from paddle_billing.Entities.Collections.SubscriptionPreviewCollection import SubscriptionPreviewCollection
from paddle_billing.Entities.Collections.TransactionCollection import TransactionCollection
Expand Down
2 changes: 2 additions & 0 deletions paddle_billing/Entities/Events/EventTypeName.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class EventTypeName(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
InvoiceOverdue: "EventTypeName" = "invoice.overdue"
InvoicePaid: "EventTypeName" = "invoice.paid"
InvoiceScheduled: "EventTypeName" = "invoice.scheduled"
PaymentMethodDeleted: "EventTypeName" = "payment_method.deleted"
PaymentMethodSaved: "EventTypeName" = "payment_method.saved"
PayoutCreated: "EventTypeName" = "payout.created"
PayoutPaid: "EventTypeName" = "payout.paid"
PriceCreated: "EventTypeName" = "price.created"
Expand Down
46 changes: 46 additions & 0 deletions paddle_billing/Entities/Simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations
from abc import ABC
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Entities.Entity import Entity
from paddle_billing.Entities.Events import EventTypeName
from paddle_billing.Entities.Simulations import SimulationScenarioType, SimulationStatus

from paddle_billing.Notifications.Entities.Simulations.SimulationEntity import SimulationEntity
from paddle_billing.Notifications.Entities.UndefinedEntity import UndefinedEntity


@dataclass
class Simulation(Entity, ABC):
id: str
status: SimulationStatus
notification_setting_id: str
name: str
type: EventTypeName | SimulationScenarioType
payload: SimulationEntity | UndefinedEntity | None
last_run_at: datetime | None
created_at: datetime
updated_at: datetime

@staticmethod
def from_dict(data: dict) -> Simulation:
type = EventTypeName(data["type"])
if not type.is_known():
type = SimulationScenarioType(data["type"])

return Simulation(
id=data["id"],
status=SimulationStatus(data["status"]),
notification_setting_id=data["notification_setting_id"],
name=data["name"],
type=type,
payload=(
SimulationEntity.from_dict_for_event_type(data["payload"], type.value)
if isinstance(type, EventTypeName) and data.get("payload")
else None
),
last_run_at=datetime.fromisoformat(data["last_run_at"]) if data.get("last_run_at") else None,
created_at=datetime.fromisoformat(data["created_at"]),
updated_at=datetime.fromisoformat(data["updated_at"]),
)
35 changes: 35 additions & 0 deletions paddle_billing/Entities/SimulationRun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations
from abc import ABC
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Entities.Entity import Entity
from paddle_billing.Entities.Events import EventTypeName
from paddle_billing.Entities.Simulations import SimulationScenarioType
from paddle_billing.Entities.SimulationRuns import SimulationRunStatus
from paddle_billing.Entities.SimulationRunEvent import SimulationRunEvent


@dataclass
class SimulationRun(Entity, ABC):
id: str
status: SimulationRunStatus
type: EventTypeName | SimulationScenarioType
created_at: datetime
updated_at: datetime
events: list[SimulationRunEvent]

@staticmethod
def from_dict(data: dict) -> SimulationRun:
type = EventTypeName(data["type"])
if not type.is_known():
type = SimulationScenarioType(data["type"])

return SimulationRun(
id=data["id"],
status=SimulationRunStatus(data["status"]),
type=type,
created_at=datetime.fromisoformat(data["created_at"]),
updated_at=datetime.fromisoformat(data["updated_at"]),
events=[SimulationRunEvent.from_dict(event) for event in data.get("events", [])],
)
40 changes: 40 additions & 0 deletions paddle_billing/Entities/SimulationRunEvent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from __future__ import annotations
from abc import ABC
from dataclasses import dataclass
from datetime import datetime

from paddle_billing.Entities.Entity import Entity
from paddle_billing.Entities.Events import EventTypeName
from paddle_billing.Entities.SimulationRunEvents import (
SimulationRunEventStatus,
SimulationRunEventRequest,
SimulationRunEventResponse,
)

from paddle_billing.Notifications.Entities.Entity import Entity as NotificationEntity
from paddle_billing.Notifications.Entities.UndefinedEntity import UndefinedEntity


@dataclass
class SimulationRunEvent(Entity, ABC):
id: str
status: SimulationRunEventStatus
event_type: EventTypeName
payload: NotificationEntity | UndefinedEntity
request: SimulationRunEventRequest | None
response: SimulationRunEventResponse | None
created_at: datetime
updated_at: datetime

@staticmethod
def from_dict(data: dict) -> SimulationRunEvent:
return SimulationRunEvent(
id=data["id"],
status=SimulationRunEventStatus(data["status"]),
event_type=EventTypeName(data["event_type"]),
payload=NotificationEntity.from_dict_for_event_type(data["payload"], data["event_type"]),
request=SimulationRunEventRequest.from_dict(data["request"]) if data.get("request") else None,
response=SimulationRunEventResponse.from_dict(data["response"]) if data.get("response") else None,
created_at=datetime.fromisoformat(data["created_at"]),
updated_at=datetime.fromisoformat(data["updated_at"]),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from __future__ import annotations
from dataclasses import dataclass

from paddle_billing.Entities.Entity import Entity


@dataclass
class SimulationRunEventRequest(Entity):
body: str

@staticmethod
def from_dict(data: dict) -> SimulationRunEventRequest:
return SimulationRunEventRequest(
body=data["body"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations
from dataclasses import dataclass

from paddle_billing.Entities.Entity import Entity


@dataclass
class SimulationRunEventResponse(Entity):
body: str
status_code: int

@staticmethod
def from_dict(data: dict) -> SimulationRunEventResponse:
return SimulationRunEventResponse(
body=data["body"],
status_code=data["status_code"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SimulationRunEventStatus(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
Aborted: "SimulationRunEventStatus" = "aborted"
Failed: "SimulationRunEventStatus" = "failed"
Success: "SimulationRunEventStatus" = "success"
Pending: "SimulationRunEventStatus" = "pending"
3 changes: 3 additions & 0 deletions paddle_billing/Entities/SimulationRunEvents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from paddle_billing.Entities.SimulationRunEvents.SimulationRunEventRequest import SimulationRunEventRequest
from paddle_billing.Entities.SimulationRunEvents.SimulationRunEventResponse import SimulationRunEventResponse
from paddle_billing.Entities.SimulationRunEvents.SimulationRunEventStatus import SimulationRunEventStatus
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SimulationRunStatus(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
Canceled: "SimulationRunStatus" = "canceled"
Completed: "SimulationRunStatus" = "completed"
Pending: "SimulationRunStatus" = "pending"
1 change: 1 addition & 0 deletions paddle_billing/Entities/SimulationRuns/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from paddle_billing.Entities.SimulationRuns.SimulationRunStatus import SimulationRunStatus
28 changes: 28 additions & 0 deletions paddle_billing/Entities/SimulationType.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations
from abc import ABC
from dataclasses import dataclass

from paddle_billing.Entities.Entity import Entity
from paddle_billing.Entities.Events import EventTypeName
from paddle_billing.Entities.Simulations import SimulationKind


@dataclass
class SimulationType(Entity, ABC):
name: str
label: str
description: str
group: str
type: SimulationKind
events: list[EventTypeName]

@staticmethod
def from_dict(data: dict) -> SimulationType:
return SimulationType(
name=data["name"],
label=data["label"],
description=data["description"],
group=data["group"],
type=SimulationKind(data["type"]),
events=[EventTypeName(event) for event in data.get("events", [])],
)
6 changes: 6 additions & 0 deletions paddle_billing/Entities/Simulations/SimulationKind.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SimulationKind(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
Scenario: "SimulationKind" = "scenario"
SingleEvent: "SimulationKind" = "single_event"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SimulationScenarioType(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
SubscriptionCreation: "SimulationScenarioType" = "subscription_creation"
SubscriptionRenewal: "SimulationScenarioType" = "subscription_renewal"
SubscriptionPause: "SimulationScenarioType" = "subscription_pause"
SubscriptionResume: "SimulationScenarioType" = "subscription_resume"
SubscriptionCancellation: "SimulationScenarioType" = "subscription_cancellation"
6 changes: 6 additions & 0 deletions paddle_billing/Entities/Simulations/SimulationStatus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from paddle_billing.PaddleStrEnum import PaddleStrEnum, PaddleStrEnumMeta


class SimulationStatus(PaddleStrEnum, metaclass=PaddleStrEnumMeta):
Active: "SimulationStatus" = "active"
Archived: "SimulationStatus" = "archived"
3 changes: 3 additions & 0 deletions paddle_billing/Entities/Simulations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from paddle_billing.Entities.Simulations.SimulationKind import SimulationKind
from paddle_billing.Entities.Simulations.SimulationScenarioType import SimulationScenarioType
from paddle_billing.Entities.Simulations.SimulationStatus import SimulationStatus
Loading
Loading