Skip to content

Domain

The central registry object. Create one per bounded context to register all domain elements, manage configuration, and coordinate infrastructure adapters.

See Compose a Domain for a practical guide.

The domain object is a one-stop gateway to:

  • Registering Domain Objects/Concepts
  • Querying/Retrieving Domain Artifacts like Entities, Services, etc.
  • Retrieve injected infrastructure adapters

Usually you create a Domain instance in your main module or in the __init__.py file of your package like this::

from protean import Domain
domain = Domain()

The Domain will automatically detect the root path of the calling module. You can also specify the root path explicitly::

domain = Domain(root_path="/path/to/domain")

The root path resolution follows this priority:

  1. Explicit root_path parameter if provided
  2. DOMAIN_ROOT_PATH environment variable if set
  3. Auto-detection of caller's file location
  4. Current working directory as last resort

:param root_path: the path to the folder containing the domain file (optional, will auto-detect if not provided) :param name: the name of the domain (optional, will use the module name if not provided) :param config: optional configuration dictionary :param identity_function: optional function to generate identities for domain objects

Source code in src/protean/domain/__init__.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
def __init__(
    self,
    root_path: str | None = None,
    name: str = "",
    config: Optional[dict[str, Any]] = None,
    identity_function: Optional[Callable[..., Any]] = None,
) -> None:
    self.root_path: str
    # Determine root_path based on resolution priority
    if root_path is None:
        # Try to get from environment variable
        env_root_path = os.environ.get("DOMAIN_ROOT_PATH")
        if env_root_path:
            self.root_path = env_root_path
        else:
            # Auto-detect
            self.root_path = self._guess_caller_path()
    else:
        self.root_path = root_path

    # Initialize the domain with the name of the module if not provided
    # Get the stack frame of the caller of the __init__ method
    caller_frame = inspect.stack()[1]
    # Get the module name from the globals of the frame where the object was instantiated
    self.name = name if name else caller_frame.frame.f_globals["__name__"]

    # Registry for all domain Objects
    self._domain_registry = _DomainRegistry()

    #: The configuration dictionary as ``Config``.  This behaves
    #: exactly like a regular dictionary but supports additional methods
    #: to load a config from files.
    self.config: Config2 = self.load_config(config)

    # The function to invoke to generate identity
    self._identity_function = identity_function

    self.providers: Providers = Providers(self)
    self.event_store: EventStore = EventStore(self)
    self.brokers: Brokers = Brokers(self)
    self.caches: Caches = Caches(self)
    self.email_providers: EmailProviders = EmailProviders(self)

    # Cache for holding Model to Entity/Aggregate associations
    # Structure mirrors Providers._repositories:
    # {
    #    'app.User': {
    #        'postgresql': UserPostgresModel,
    #        'sqlite': UserSQLiteModel,
    #        None: UserGenericModel,       # database=None → fallback for any provider
    #    }
    # }
    self._database_models: dict[str, dict[str | None, type[BaseDatabaseModel]]] = (
        defaultdict(dict)
    )
    self._constructed_models: dict[str, BaseDatabaseModel] = {}

    # Message enricher hooks — callables that add custom metadata to events/commands.
    # Event enrichers receive (event, aggregate) and return dict[str, Any].
    # Command enrichers receive (command,) and return dict[str, Any].
    # Results are merged into metadata.extensions.
    self._event_enrichers: List[Callable[..., Any]] = []
    self._command_enrichers: List[Callable[..., Any]] = []

    # Composed helpers — see handler_setup.py, validation.py, etc.
    self._command_processor = CommandProcessor(self)
    self._handler_configurator = HandlerConfigurator(self)
    self._infrastructure = InfrastructureManager(self)
    self._query_processor = QueryProcessor(self)
    self._resolver = ElementResolver(self)
    self._type_manager = TypeManager(self)
    self._validator = DomainValidator(self)

    #: A list of functions that are called when the domain context
    #: is destroyed.  This is the place to store code that cleans up and
    #: disconnects from databases, for example.
    self.teardown_domain_context_functions: List[Callable[..., Any]] = []

    # Placeholder array for resolving classes referenced by domain elements
    self._pending_class_resolutions: dict[str, Any] = defaultdict(list)

    # Event classes that have already emitted a raise-time deprecation
    # warning, so a deprecated event warns once per type, not per instance.
    self._deprecated_events_warned: set[type] = set()

    # Lazy-initialized idempotency store
    self._idempotency_store: IdempotencyStore | None = None

    # Lazy-initialized trace emitter for command processing observability
    self._trace_emitter: TraceEmitter | None = None

    # Lazy-initialized OpenTelemetry providers (set by init_telemetry)
    self._otel_tracer_provider: Any = None
    self._otel_meter_provider: Any = None
    self._otel_init_attempted = False

has_outbox property

has_outbox: bool

Whether the outbox pattern is active.

Derived from server.default_subscription_type: outbox is enabled when subscription type is "stream". For backward compatibility, an explicit enable_outbox = true also activates the outbox.

camel_case_name cached property

camel_case_name: str

Return the CamelCase name of the domain.

The CamelCase name is the name of the domain with the first letter capitalized. Examples: - my_domain -> MyDomain - my_domain_1 -> MyDomain1 - my_domain_1_0 -> MyDomain10

normalized_name cached property

normalized_name: str

Return the normalized name of the domain.

The normalized name is the underscored version of the domain name. Examples: - MyDomain -> my_domain - My Domain -> my_domain - My-Domain -> my_domain - My Domain 1 -> my_domain_1 - My Domain 1.0 -> my_domain_1_0

init

init(traverse: bool = True) -> None

Parse the domain folder, and attach elements dynamically to the domain.

Protean parses all files in the domain file's folder, as well as under it, to load elements. So, all domain files are to be nested under the file contain the domain definition.

One can use the traverse flag to control this functionality, True by default.

When enabled, Protean is responsible for loading domain elements and ensuring all functionality is activated.

The developer is responsible for activating functionality manually when autoloading is disabled. Element activation can be done by importing them in central areas of domain execution, like Application Services.

For example, asynchronous aspects of a domain like its Subscribers and Event Handlers should be imported in their relevant Application Services and Aggregates.

This method bubbles up circular import issues, if present, in the domain code.

Source code in src/protean/domain/__init__.py
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
def init(self, traverse: bool = True) -> None:  # noqa: C901
    """Parse the domain folder, and attach elements dynamically to the domain.

    Protean parses all files in the domain file's folder, as well as under it,
    to load elements. So, all domain files are to be nested under the file contain
    the domain definition.

    One can use the `traverse` flag to control this functionality, `True` by default.

    When enabled, Protean is responsible for loading domain elements and ensuring
    all functionality is activated.

    The developer is responsible for activating functionality manually when
    autoloading is disabled. Element activation can be done by importing them
    in central areas of domain execution, like Application Services.

    For example, asynchronous aspects of a domain like its Subscribers and
    Event Handlers should be imported in their relevant Application Services
    and Aggregates.

    This method bubbles up circular import issues, if present, in the domain code.
    """
    self._auto_configure_logging()

    self._prepare(traverse=traverse)

    # Initialize adapters after loading domain
    self._initialize()

    # Initialize outbox DAOs for all providers
    if self.has_outbox:
        self._initialize_outbox()

    # Initialize consume-side idempotency markers when any projector opts in
    if self.has_idempotent_consumers:
        self._initialize_processed_messages()

domain_context

domain_context(**kwargs: Any) -> DomainContext

Create a DomainContext. Use as a with block to push the context, which will make current_domain point at this domain.

::

with domain.domain_context():
    init_db()
Source code in src/protean/domain/__init__.py
930
931
932
933
934
935
936
937
938
939
940
def domain_context(self, **kwargs: Any) -> DomainContext:
    """Create a ``DomainContext``. Use as a ``with``
    block to push the context, which will make ``current_domain``
    point at this domain.

    ::

        with domain.domain_context():
            init_db()
    """
    return DomainContext(self, **kwargs)

aggregate

aggregate(_cls: type[_T]) -> type[_T]
aggregate(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
aggregate(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def aggregate(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.AGGREGATE,
        _cls=_cls,
        **kwargs,
    )

entity

entity(_cls: type[_T]) -> type[_T]
entity(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
entity(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def entity(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(DomainObjects.ENTITY, _cls=_cls, **kwargs)

value_object

value_object(_cls: type[_T]) -> type[_T]
value_object(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
value_object(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def value_object(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.VALUE_OBJECT,
        _cls=_cls,
        **kwargs,
    )

command

command(_cls: type[_T]) -> type[_T]
command(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
command(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def command(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.COMMAND,
        _cls=_cls,
        **kwargs,
    )

event

event(_cls: type[_T]) -> type[_T]
event(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
event(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def event(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.EVENT,
        _cls=_cls,
        **kwargs,
    )

command_handler

command_handler(_cls: type[_T]) -> type[_T]
command_handler(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
command_handler(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def command_handler(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(DomainObjects.COMMAND_HANDLER, _cls=_cls, **kwargs)

event_handler

event_handler(_cls: type[_T]) -> type[_T]
event_handler(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
event_handler(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def event_handler(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.EVENT_HANDLER,
        _cls=_cls,
        **kwargs,
    )

application_service

application_service(_cls: type[_T]) -> type[_T]
application_service(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
application_service(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def application_service(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.APPLICATION_SERVICE,
        _cls=_cls,
        **kwargs,
    )

domain_service

domain_service(_cls: type[_T]) -> type[_T]
domain_service(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
domain_service(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def domain_service(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.DOMAIN_SERVICE,
        _cls=_cls,
        **kwargs,
    )

repository

repository(_cls: type[_T]) -> type[_T]
repository(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
repository(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def repository(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(DomainObjects.REPOSITORY, _cls=_cls, **kwargs)

projection

projection(_cls: type[_T]) -> type[_T]
projection(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
projection(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def projection(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.PROJECTION,
        _cls=_cls,
        **kwargs,
    )

projector

projector(_cls: type[_T]) -> type[_T]
projector(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
projector(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def projector(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.PROJECTOR,
        _cls=_cls,
        **kwargs,
    )

subscriber

subscriber(_cls: type[_T]) -> type[_T]
subscriber(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
subscriber(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def subscriber(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.SUBSCRIBER,
        _cls=_cls,
        **kwargs,
    )

process_manager

process_manager(_cls: type[_T]) -> type[_T]
process_manager(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
process_manager(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def process_manager(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(
        DomainObjects.PROCESS_MANAGER,
        _cls=_cls,
        **kwargs,
    )

upcaster

upcaster(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]

Register an event upcaster with the domain.

Upcasters transform raw event payloads from an old schema version to a newer one. They are applied lazily during deserialization so that @apply handlers and event handlers always see the current schema.

PARAMETER DESCRIPTION
event_type

The event class this upcaster targets (current version).

TYPE: type

from_version

Source version number (e.g. 1).

TYPE: int

to_version

Target version number (e.g. 2).

TYPE: int

Example::

@domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
class UpcastOrderPlacedV1ToV2(BaseUpcaster):
    def upcast(self, data: dict) -> dict:
        data["currency"] = "USD"
        return data
Source code in src/protean/domain/__init__.py
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
def upcaster(
    self,
    _cls: type[_T] | None = None,
    **kwargs: Any,
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    """Register an event upcaster with the domain.

    Upcasters transform raw event payloads from an old schema version to
    a newer one.  They are applied lazily during deserialization so that
    ``@apply`` handlers and event handlers always see the current schema.

    Keyword Args:
        event_type (type): The event class this upcaster targets (current version).
        from_version (int): Source version number (e.g. ``1``).
        to_version (int): Target version number (e.g. ``2``).

    Example::

        @domain.upcaster(event_type=OrderPlaced, from_version=1, to_version=2)
        class UpcastOrderPlacedV1ToV2(BaseUpcaster):
            def upcast(self, data: dict) -> dict:
                data["currency"] = "USD"
                return data
    """
    return self._domain_element(
        DomainObjects.UPCASTER,
        _cls=_cls,
        **kwargs,
    )

database_model

database_model(_cls: type[_T]) -> type[_T]
database_model(
    _cls: None = ..., **kwargs: Any
) -> Callable[[type[_T]], type[_T]]
database_model(
    _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
@dataclass_transform(
    field_specifiers=(
        Auto,
        Boolean,
        Date,
        DateTime,
        DictField,
        Float,
        HasMany,
        HasOne,
        Identifier,
        Integer,
        ListField,
        Reference,
        Status,
        String,
        Text,
        ValueObject,
    )
)
def database_model(
    self, _cls: type[_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]:
    return self._domain_element(DomainObjects.DATABASE_MODEL, _cls=_cls, **kwargs)

process

process(
    command: Any,
    asynchronous: Optional[bool] = None,
    idempotency_key: Optional[str] = None,
    raise_on_duplicate: bool = False,
    priority: Optional[int] = None,
    correlation_id: Optional[str] = None,
    deadline: Optional[datetime] = None,
    timeout: Optional[timedelta] = None,
) -> Optional[Any]

Process command and return results based on specified preference.

By default, Protean does not return values after processing commands. This behavior can be overridden either by setting command_processing in config to "sync" or by specifying asynchronous=False when calling the domain's handle method.

PARAMETER DESCRIPTION
command

Command to process (instance of a @domain.command-decorated class)

TYPE: Any

asynchronous

Specifies if the command should be processed asynchronously. Defaults to True.

TYPE: Boolean DEFAULT: None

idempotency_key

Caller-provided key for command deduplication. When provided, enables submission-level dedup via the idempotency store.

TYPE: str DEFAULT: None

raise_on_duplicate

If True, raises DuplicateCommandError when a duplicate idempotency key is detected. If False (default), silently returns the cached result.

TYPE: bool DEFAULT: False

priority

Processing priority for events produced by this command.

TYPE: int DEFAULT: None

correlation_id

Correlation ID for distributed tracing.

TYPE: str DEFAULT: None

deadline

Absolute time after which the command must not be executed. Stored in metadata and propagated to downstream commands. Mutually exclusive with timeout.

TYPE: datetime DEFAULT: None

timeout

Relative deadline (now + timeout), converted to an absolute deadline at submission. Mutually exclusive with deadline.

TYPE: timedelta DEFAULT: None

RETURNS DESCRIPTION
Optional[Any]

Optional[Any]: Returns either the command handler's return value or nothing, based on preference.

Source code in src/protean/domain/__init__.py
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
def process(
    self,
    command: Any,
    asynchronous: Optional[bool] = None,
    idempotency_key: Optional[str] = None,
    raise_on_duplicate: bool = False,
    priority: Optional[int] = None,
    correlation_id: Optional[str] = None,
    deadline: Optional[datetime] = None,
    timeout: Optional[timedelta] = None,
) -> Optional[Any]:
    """Process command and return results based on specified preference.

    By default, Protean does not return values after processing commands. This behavior
    can be overridden either by setting command_processing in config to "sync" or by specifying
    ``asynchronous=False`` when calling the domain's ``handle`` method.

    Args:
        command: Command to process (instance of a ``@domain.command``-decorated class)
        asynchronous (Boolean, optional): Specifies if the command should be processed asynchronously.
            Defaults to True.
        idempotency_key (str, optional): Caller-provided key for command deduplication.
            When provided, enables submission-level dedup via the idempotency store.
        raise_on_duplicate (bool): If ``True``, raises ``DuplicateCommandError``
            when a duplicate idempotency key is detected. If ``False`` (default),
            silently returns the cached result.
        priority (int, optional): Processing priority for events produced by this command.
        correlation_id (str, optional): Correlation ID for distributed tracing.
        deadline (datetime, optional): Absolute time after which the command must not
            be executed. Stored in metadata and propagated to downstream commands.
            Mutually exclusive with ``timeout``.
        timeout (timedelta, optional): Relative deadline (``now + timeout``), converted
            to an absolute deadline at submission. Mutually exclusive with ``deadline``.

    Returns:
        Optional[Any]: Returns either the command handler's return value or nothing, based on preference.
    """
    return self._command_processor.process(
        command,
        asynchronous=asynchronous,
        idempotency_key=idempotency_key,
        raise_on_duplicate=raise_on_duplicate,
        priority=priority,
        correlation_id=correlation_id,
        deadline=deadline,
        timeout=timeout,
    )