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
321
322
323
324
325
326
327
328
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
424
425
426
427
428
429
430
def __init__(
    self,
    root_path: str | None = None,
    name: str = "",
    config: dict[str, Any] | None = None,
    identity_function: Callable[..., Any] | None = 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

    # Injectable source of "now" for deadline, lock, and retry-backoff time
    # comparisons. Defaults to real UTC; tests assign a stub clock to make
    # boundary behavior deterministic. See :class:`protean.utils.Clock`.
    self.clock: Clock = SystemClock()

    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]] = []
    # Aggregate pre-persist enrichers receive (aggregate,) and mutate it
    # in place to stamp cross-cutting lifecycle/audit fields on save.
    self._aggregate_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)

    # Partition-per-key routing map (ADR-0028): stream_category ->
    # partition-key field name. Built by ``HandlerConfigurator`` during
    # ``_prepare()`` from every handler that declares ``sequential_by``, and
    # read by the Unit of Work at commit to denormalize the key onto each
    # outbox row. Empty when no handler opts in.
    self._partition_keys: dict[str, str] = {}

    # 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
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
def init(self, 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.
    """
    self._auto_configure_logging()

    self._prepare(traverse=traverse)

    # Initialize adapters after loading domain
    self._initialize()

    # Gate sequential_by handlers on broker STREAM_PARTITIONING support
    # (ADR-0028); needs live brokers, so it runs after _initialize().
    self._handler_configurator.validate_sequential_by_capabilities()

    # 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
949
950
951
952
953
954
955
956
957
958
959
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" backlink-type="used-by" backlink-anchor="protean.domain.Domain.aggregate" optional hover>_T] | None = None, **kwargs: Any
) -> type[_T" backlink-type="returned-by" backlink-anchor="protean.domain.Domain.aggregate" optional hover>_T] | Callable[[type[_T" backlink-type="returned-by" backlink-anchor="protean.domain.Domain.aggregate" optional hover>_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
@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
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
@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
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
@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
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
@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
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
@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
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
@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" backlink-type="used-by" backlink-anchor="protean.domain.Domain.event_handler" optional hover>_T] | None = None, **kwargs: Any
) -> type[_T] | Callable[[type[_T]], type[_T]]
Source code in src/protean/domain/__init__.py
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
@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
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
@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
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
@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
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
@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
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
@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
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
@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
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
@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
1987
1988
1989
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
@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
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
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
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
@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: bool | None = None,
    idempotency_key: str | None = None,
    raise_on_duplicate: bool = False,
    priority: int | None = None,
    correlation_id: str | None = None,
    deadline: datetime | None = None,
    timeout: timedelta | None = None,
) -> Any | None

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
Any | None

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

Source code in src/protean/domain/__init__.py
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
def process(
    self,
    command: Any,
    asynchronous: bool | None = None,
    idempotency_key: str | None = None,
    raise_on_duplicate: bool = False,
    priority: int | None = None,
    correlation_id: str | None = None,
    deadline: datetime | None = None,
    timeout: timedelta | None = None,
) -> Any | None:
    """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,
    )