Skip to content
Merged
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,26 @@ params = (
regions = client.regions().list(params).data
```

## Date and Datetime Fields

The SDK distinguishes between date-only and datetime fields:

- **Datetime fields** are deserialized as `datetime.datetime` with `timezone.utc`:
- All `created_at` fields — present on most resources
- Expiry fields: `Did.expires_at`, `DidReservation.expire_at`, `Proof.expires_at`, `EncryptedFile.expire_at`
- **Date-only fields** (`Identity.birth_date`, `CapacityPool.renew_date`, `DidOrderItem.billed_from`/`billed_to`) remain as `string` in `"YYYY-MM-DD"` format.

```python
from datetime import timezone

did = client.dids().find("uuid").data
print(did.created_at) # datetime(2024, 1, 15, 10, 0, 0, tzinfo=timezone.utc)
print(did.expires_at) # None or datetime(...)

identity = client.identities().find("uuid").data
print(identity.birth_date) # "1990-05-20"
```

## Enums

The SDK provides enum classes aligned with the Java SDK (for example `CallbackMethod`, `IdentityType`, `OrderStatus`, `ExportType`, `CliFormat`, `OnCliMismatchAction`, `MediaEncryptionMode`, `TransportProtocol`, `Codec`, and more).
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/address.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, Repository


class Address(DidwwApiModel):
Expand All @@ -8,7 +8,7 @@ class Address(DidwwApiModel):
postal_code = SafeAttributeField("postal_code")
address = SafeAttributeField("address")
description = SafeAttributeField("description")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")
verified = SafeAttributeField("verified")

country = RelationField("country")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/address_verification.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from didww.enums import AddressVerificationStatus, CallbackMethod
from didww.resources.base import DidwwApiModel, SafeAttributeField, EnumAttributeField, RelationField, CreateOnlyRepository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, EnumAttributeField, RelationField, CreateOnlyRepository


class _SplitSemicolonField(SafeAttributeField):
Expand All @@ -21,7 +21,7 @@ class AddressVerification(DidwwApiModel):
service_description = SafeAttributeField("service_description")
reject_reasons = _SplitSemicolonField("reject_reasons")
reference = SafeAttributeField("reference")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")

address = RelationField("address")
dids = RelationField("dids")
Expand Down
19 changes: 19 additions & 0 deletions src/didww/resources/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import functools
import importlib
from datetime import datetime, timezone
from enum import Enum

from jsonapi_requests.orm.api import OrmApi
Expand Down Expand Up @@ -80,6 +81,24 @@ def __set__(self, instance, value):
instance.attributes[self.source] = value


class DatetimeAttributeField(SafeAttributeField):
"""AttributeField that parses ISO 8601 datetime strings to datetime objects.

No custom setter is defined because all current datetime fields are read-only
(e.g. created_at). The inherited setter stores values as-is. If a writable
datetime field is added in the future, a setter that converts datetime back
to an ISO 8601 string should be implemented here.
"""

def __get__(self, instance, type=None):
raw = super().__get__(instance, type)
if instance is None:
return self
if raw is None:
return None
return datetime.fromisoformat(raw.replace("Z", "+00:00")) # "Z" not supported by fromisoformat before Python 3.11


Comment on lines +94 to +101
class EnumAttributeField(SafeAttributeField):
"""AttributeField that serializes/deserializes Enum values."""

Expand Down
6 changes: 3 additions & 3 deletions src/didww/resources/did.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, ExclusiveRelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, ExclusiveRelationField, Repository

class Did(DidwwApiModel):
_writable_attrs = {
Expand All @@ -12,8 +12,8 @@ class Did(DidwwApiModel):
description = SafeAttributeField("description")
terminated = SafeAttributeField("terminated")
awaiting_registration = SafeAttributeField("awaiting_registration")
created_at = SafeAttributeField("created_at")
expires_at = SafeAttributeField("expires_at")
created_at = DatetimeAttributeField("created_at")
expires_at = DatetimeAttributeField("expires_at")
channels_included_count = SafeAttributeField("channels_included_count")
billing_cycles_count = SafeAttributeField("billing_cycles_count")
dedicated_channels_count = SafeAttributeField("dedicated_channels_count")
Expand Down
6 changes: 3 additions & 3 deletions src/didww/resources/did_reservation.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from didww.exceptions import DidwwApiError
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, Repository


class DidReservation(DidwwApiModel):
_writable_attrs = {"description"}

expire_at = SafeAttributeField("expire_at")
created_at = SafeAttributeField("created_at")
expire_at = DatetimeAttributeField("expire_at")
created_at = DatetimeAttributeField("created_at")
description = SafeAttributeField("description")

available_did = RelationField("available_did")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/encrypted_file.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, Repository


class EncryptedFile(DidwwApiModel):
description = SafeAttributeField("description")
expire_at = SafeAttributeField("expire_at")
expire_at = DatetimeAttributeField("expire_at")

Comment on lines 4 to 7
class Meta:
type = "encrypted_files"
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/export.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from didww.enums import ExportStatus, ExportType, CallbackMethod
from didww.resources.base import DidwwApiModel, SafeAttributeField, EnumAttributeField, CreateOnlyRepository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, EnumAttributeField, CreateOnlyRepository


class Export(DidwwApiModel):
_writable_attrs = {"filters", "export_type", "callback_url", "callback_method"}

status = EnumAttributeField("status", ExportStatus)
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")
url = SafeAttributeField("url")
callback_url = SafeAttributeField("callback_url")
callback_method = EnumAttributeField("callback_method", CallbackMethod)
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/identity.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from didww.enums import IdentityType
from didww.resources.base import DidwwApiModel, SafeAttributeField, EnumAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, EnumAttributeField, RelationField, Repository


class Identity(DidwwApiModel):
Expand All @@ -22,7 +22,7 @@ class Identity(DidwwApiModel):
identity_type = EnumAttributeField("identity_type", IdentityType)
contact_email = SafeAttributeField("contact_email")
external_reference_id = SafeAttributeField("external_reference_id")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")
verified = SafeAttributeField("verified")

country = RelationField("country")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/order.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from didww.enums import CallbackMethod, OrderStatus
from didww.resources.base import DidwwApiModel, SafeAttributeField, EnumAttributeField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, EnumAttributeField, Repository
from didww.resources.order_item.base import OrderItem

# Import to register types
Expand All @@ -15,7 +15,7 @@ class Order(DidwwApiModel):

amount = SafeAttributeField("amount")
status = EnumAttributeField("status", OrderStatus)
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")
description = SafeAttributeField("description")
reference = SafeAttributeField("reference")
callback_url = SafeAttributeField("callback_url")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/permanent_supporting_document.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, CreateOnlyRepository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, CreateOnlyRepository


class PermanentSupportingDocument(DidwwApiModel):
_writable_attrs = set()

created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")

identity = RelationField("identity")
template = RelationField("template")
Expand Down
6 changes: 3 additions & 3 deletions src/didww/resources/proof.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, CreateOnlyRepository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, CreateOnlyRepository


class Proof(DidwwApiModel):
_writable_attrs = set()

created_at = SafeAttributeField("created_at")
expires_at = SafeAttributeField("expires_at")
created_at = DatetimeAttributeField("created_at")
expires_at = DatetimeAttributeField("expires_at")

proof_type = RelationField("proof_type")
entity = RelationField("entity")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/shared_capacity_group.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, Repository


class SharedCapacityGroup(DidwwApiModel):
Expand All @@ -7,7 +7,7 @@ class SharedCapacityGroup(DidwwApiModel):
name = SafeAttributeField("name")
shared_channels_count = SafeAttributeField("shared_channels_count")
metered_channels_count = SafeAttributeField("metered_channels_count")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")

capacity_pool = RelationField("capacity_pool")
dids = RelationField("dids")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/voice_in_trunk.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from didww.enums import CliFormat
from didww.resources.base import DidwwApiModel, SafeAttributeField, EnumAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, EnumAttributeField, RelationField, Repository
from didww.resources.configuration.base import TrunkConfiguration

# Import to register types
Expand All @@ -21,7 +21,7 @@ class VoiceInTrunk(DidwwApiModel):
cli_prefix = SafeAttributeField("cli_prefix")
description = SafeAttributeField("description")
ringing_timeout = SafeAttributeField("ringing_timeout")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")

pop = RelationField("pop")
voice_in_trunk_group = RelationField("voice_in_trunk_group")
Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/voice_in_trunk_group.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from didww.resources.base import DidwwApiModel, SafeAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, RelationField, Repository


class VoiceInTrunkGroup(DidwwApiModel):
_writable_attrs = {"capacity_limit", "name"}

name = SafeAttributeField("name")
capacity_limit = SafeAttributeField("capacity_limit")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")

voice_in_trunks = RelationField("voice_in_trunks")

Expand Down
4 changes: 2 additions & 2 deletions src/didww/resources/voice_out_trunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
OnCliMismatchAction,
VoiceOutTrunkStatus,
)
from didww.resources.base import DidwwApiModel, SafeAttributeField, EnumAttributeField, RelationField, Repository
from didww.resources.base import DidwwApiModel, DatetimeAttributeField, SafeAttributeField, EnumAttributeField, RelationField, Repository


class VoiceOutTrunk(DidwwApiModel):
Expand Down Expand Up @@ -32,7 +32,7 @@ class VoiceOutTrunk(DidwwApiModel):
callback_url = SafeAttributeField("callback_url")
username = SafeAttributeField("username")
password = SafeAttributeField("password")
created_at = SafeAttributeField("created_at")
created_at = DatetimeAttributeField("created_at")

default_did = RelationField("default_did")
dids = RelationField("dids")
Expand Down
6 changes: 4 additions & 2 deletions tests/resources/test_address_verification.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import datetime, timezone

from tests.conftest import my_vcr
from didww.enums import AddressVerificationStatus, CallbackMethod, IdentityType
from didww.query_params import QueryParams
Expand Down Expand Up @@ -25,7 +27,7 @@ def test_find_address_verification(self, client):
assert av.id == "c8e004b0-87ec-4987-b4fb-ee89db099f0e"
assert av.status == AddressVerificationStatus.APPROVED
assert av.reference == "SHB-485120"
assert av.created_at == "2020-09-15T06:38:12.650Z"
assert av.created_at == datetime(2020, 9, 15, 6, 38, 12, 650000, tzinfo=timezone.utc)

@my_vcr.use_cassette("address_verifications/show_rejected.yaml")
def test_find_rejected_address_verification(self, client):
Expand All @@ -35,7 +37,7 @@ def test_find_rejected_address_verification(self, client):
assert av.status == AddressVerificationStatus.REJECTED
assert av.reject_reasons == ["Address cannot be validated", "Proof of address should be not older than of 6 months"]
assert av.reference == "ODW-879912"
assert av.created_at == "2020-10-28T08:29:29.960Z"
assert av.created_at == datetime(2020, 10, 28, 8, 29, 29, 960000, tzinfo=timezone.utc)

@my_vcr.use_cassette("address_verifications/show_with_includes.yaml")
def test_find_address_verification_with_includes(self, client):
Expand Down
Loading