Skip to content

Commit a4abf15

Browse files
authored
Auto discover Domain.root_path (#519)
Removes the need for users to pass `__file__` when instantiating a Domain, while still allowing an explicit override. Environments supported: - Standard Python execution (scripts, console entry-points) - Jupyter / IPython notebooks - REPL / interactive shell - Frozen / zipapp / PyInstaller builds Also: - Updated the Domain initialization across various documentation files and test cases to use the new parameter format. - Changed instances of `Domain(__file__, "name")` to `Domain(name="name")` for consistency and clarity. Fixes #514
1 parent 0a9b5bb commit a4abf15

File tree

142 files changed

+698
-196
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+698
-196
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Protean officially supports Python 3.11+.
2828
from protean import Domain
2929
from protean.fields import String, Text
3030

31-
domain = Domain(__file__, "Publishing")
31+
domain = Domain(name="Publishing")
3232

3333
@domain.aggregate
3434
class Post:

docs/guides/compose-a-domain/index.md

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,28 @@ In Protean, the Domain serves as the central composition root of your applicatio
66

77
The Domain acts as a composition root, bringing together all the essential components of your application. It provides a centralized location where all elements are registered, configured, and made available for use throughout your application. This centralization simplifies dependency management and promotes a more maintainable architecture.
88

9-
## Key Responsibilities
9+
### Key Responsibilities
1010

11-
### Element Management
11+
#### Element Management
1212

1313
The Domain maintains a registry of all domain elements, including entities, value objects, repositories, services, and other components. This registry serves as a catalog that makes these elements discoverable and accessible throughout your application.
1414

15-
### Configuration Storage
15+
#### Configuration Storage
1616

1717
Your Domain stores configuration settings that define how your application behaves. These settings can include database connection parameters, feature flags, environment-specific values, and other application-wide settings. Read more at [Configuration](../configuration.md).
1818

19-
### Adapter Activation
19+
#### Adapter Activation
2020

2121
The Domain manages the lifecycle of adapters, which are components that connect your application to external systems and services. This includes activating the appropriate adapters based on your configuration and ensuring they are properly initialized.
2222

23-
### Automatic Element Collection
23+
#### Automatic Element Collection
2424

2525
Protean's Domain leverages Python decorators to automatically collect and register domain elements. By simply applying the appropriate decorator to your classes, they are automatically discovered and registered with the Domain during initialization.
2626

2727
```python
2828
from protean import Domain
2929

30-
domain = Domain(__file__)
30+
domain = Domain()
3131
...
3232

3333
@domain.aggregage
@@ -37,11 +37,100 @@ class User:
3737

3838
This declarative approach reduces boilerplate code and makes your domain model more expressive and maintainable.
3939

40+
## Parameters
41+
42+
The `Domain` constructor accepts several parameters that control how the domain is initialized and configured:
43+
44+
### `root_path`
45+
46+
Optional. Defaults to `None`.
47+
48+
The path to the folder containing the domain file. This parameter is optional and follows a resolution priority:
49+
50+
1. Explicit `root_path` parameter if provided
51+
2. `DOMAIN_ROOT_PATH` environment variable if set
52+
3. Auto-detection of caller's file location
53+
4. Current working directory as last resort
54+
55+
The `root_path` is used for finding configuration files and traversing domain files.
56+
57+
```python
58+
# Explicit root path
59+
domain = Domain(root_path="/path/to/domain")
60+
61+
# Using environment variable
62+
# export DOMAIN_ROOT_PATH="/path/to/domain"
63+
domain = Domain() # Will use DOMAIN_ROOT_PATH
64+
65+
# Auto-detection (uses the directory of the file where Domain is instantiated)
66+
domain = Domain()
67+
```
68+
69+
This is handled even under various execution contexts:
70+
71+
- Standard Python scripts
72+
- Jupyter/IPython notebooks
73+
- REPL/interactive shell
74+
- Frozen/PyInstaller applications
75+
76+
### `name`
77+
78+
The name of the domain.
79+
80+
Optional and defaults to the module name where the domain is instantiated.
81+
82+
The domain name is used in various contexts, including event type construction and logging.
83+
84+
```python
85+
# Explicit name
86+
domain = Domain(name="ecommerce")
87+
88+
# Default name (uses module name)
89+
domain = Domain() # If in module 'my_app', name will be 'my_app'
90+
```
91+
92+
### `config`
93+
94+
An optional configuration dictionary that overrides the default configuration and any configuration loaded from files.
95+
96+
If not provided, configuration is loaded from `.domain.toml`, `domain.toml`, or `pyproject.toml` files in the domain folder or its parent directories.
97+
98+
```python
99+
# Explicit configuration
100+
domain = Domain(config={
101+
"identity_strategy": "UUID",
102+
"databases": {
103+
"default": {"provider": "postgres", "url": "postgresql://user:pass@localhost/db"}
104+
}
105+
})
106+
107+
# Default configuration (loads from TOML files if available)
108+
domain = Domain()
109+
```
110+
111+
Refer to [Configuration](../configuration.md) to understand configuration file structure and parameters.
112+
113+
### `identity_function`
114+
115+
An optional function to generate identities for domain objects. This parameter is required when the `identity_strategy` in configuration is set to `FUNCTION`.
116+
117+
```python
118+
# Custom identity function
119+
def generate_id():
120+
# Custom ID generation logic
121+
return "custom-id-" + str(random.randint(1000, 9999))
122+
123+
# Using custom identity function
124+
domain = Domain(
125+
config={"identity_strategy": "FUNCTION"},
126+
identity_function=generate_id
127+
)
128+
```
129+
40130
## In This Section
41131

42132
- [Register Elements](./register-elements.md) - Explore methods for registering domain elements
43133
- [Initialize Domain](./initialize-domain.md) - Understand how to set up and initialize a new domain
44134
- [Element Decorators](./element-decorators.md) - Learn about using decorators to automatically register domain elements
45135
- [Activate Domain](./activate-domain.md) - Understand how to activate a domain and its components
46136
- [When to Compose a Domain](./when-to-compose.md) - Learn about the appropriate timing and scenarios for composing a domain
47-

docs/guides/compose-a-domain/initialize-domain.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ The domain is initialized by calling the `init` method.
66
domain.init()
77
```
88

9+
## `Domain.init()`
10+
911
A call to `init` does the following:
1012

11-
## 1. Traverse the domain model
13+
### 1. Traverse the domain model
1214

1315
By default, Protean traverses the directory structure under the domain file
1416
to discover domain elements. You can control this behavior with the `traverse`
@@ -22,7 +24,7 @@ If you choose to not traverse, Protean will not be able to detect domain
2224
elements automatically. ***You are responsible for registering each element
2325
with the domain explicitly***.
2426

25-
## 2. Construct the object graph
27+
### 2. Construct the object graph
2628

2729
Protean constructs a graph of all elements registered with a domain and
2830
exposes them in a registry.
@@ -31,7 +33,7 @@ exposes them in a registry.
3133
{! docs_src/guides/composing-a-domain/016.py !}
3234
```
3335

34-
## 3. Initialize dependencies
36+
### 3. Initialize dependencies
3537

3638
Calling `domain.init()` establishes connectivity with the underlying infra,
3739
testing access, and making them available for use by the rest of the system.
@@ -52,7 +54,7 @@ makes it available for domain elements for further use.
5254
Refer to [Configuration handling](../configuration.md) to understand the different ways to configure
5355
the domain.
5456

55-
## 4. Validate Domain Model
57+
### 4. Validate Domain Model
5658

5759
In the final part of domain initialization, Protean performs additional setup
5860
tasks on domain elements and also conducts various checks to ensure the domain
@@ -61,17 +63,18 @@ model is specified correctly.
6163
Examples of checks include:
6264

6365
1. Resolving references that were specified as Strings, like:
66+
6467
```python
6568
@domain.entity(part_of="User")
6669
class Account:
6770
...
6871
```
6972

70-
2. Setting up Aggregate clusters and their shared settings. The object graph
73+
1. Setting up Aggregate clusters and their shared settings. The object graph
7174
constructed earlier is used to homogenize settings across all elements under
7275
an aggregate, like the stream category and database provider.
7376

74-
3. Constructing a map of command and event types to reference when processing
77+
1. Constructing a map of command and event types to reference when processing
7578
incoming messages later.
7679

77-
4. Various checks and validations to ensure the domain structure is valid.
80+
1. Various checks and validations to ensure the domain structure is valid.

docs/guides/configuration.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@
22

33
Protean's configuration is managed through the Domain object, which can be configured in multiple ways:
44

5-
1. **Direct Configuration**: Pass a configuration dictionary when initializing the Domain object:
5+
## Direct Configuration
6+
7+
Pass a configuration dictionary when initializing the Domain object:
8+
69
```python
710
domain = Domain(config={'debug': True, 'testing': True})
811
```
912

10-
2. **Configuration Files**: Place configuration in a TOML file in your project directory or up to two levels of parent directories. Protean searches for configuration files in the following order:
13+
## Configuration Files
14+
15+
Place configuration can be supplied in a TOML file in your project directory or up to two levels of parent directories.
16+
17+
Protean searches for configuration files in the following order:
18+
19+
1. `.domain.toml`
20+
1. `domain.toml`
21+
1. `pyproject.toml` (under the `[tool.protean]` section)
1122

12-
- `.domain.toml`
13-
- `domain.toml`
14-
- `pyproject.toml` (under the `[tool.protean]` section)
23+
### Generating a new configuration file
1524

1625
When initializing a new Protean application using the [`new`](./cli/new.md) command, a `domain.toml` configuration file is automatically generated with sensible defaults.
1726

@@ -88,7 +97,7 @@ foo = "quux"
8897

8998
Specifies if the application is running in debug mode.
9099

91-
***Do not enable debug mode when deploying in production.***
100+
*Do not enable debug mode when deploying in production.*
92101

93102
Default: `False`
94103

@@ -112,7 +121,7 @@ You can generate a secret key with the following command:
112121
c4bf0121035265bf44657217c33a7d041fe9e505961fc7da5d976aa0eaf5cf94
113122
```
114123

115-
***Do not reveal the secret key when posting questions or committing code.***
124+
*Do not reveal your secret key when posting questions or committing code.*
116125

117126
### `identity_strategy`
118127

@@ -272,7 +281,7 @@ FOO = "bar"
272281
```
273282

274283
```shell hl_lines="3-4 6-7"
275-
In [1]: domain = Domain(__file__)
284+
In [1]: domain = Domain()
276285

277286
In [2]: domain.config["custom"]["FOO"]
278287
Out[2]: 'bar'

docs_src/adapters/001.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from protean import Domain
22

3-
domain = Domain(__file__)
3+
domain = Domain()
44

55
# Non-existent database
66
domain.config["databases"]["default"] = {

docs_src/adapters/database/postgresql/001.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from protean.adapters.repository.sqlalchemy import SqlalchemyModel
55
from protean.fields import Integer, String
66

7-
domain = Domain(__file__)
7+
domain = Domain()
88
domain.config["databases"]["default"] = {
99
"provider": "postgresql",
1010
"database_uri": "postgresql://postgres:postgres@localhost:5432/postgres",

docs_src/guides/change_state_001.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from protean import Domain
22
from protean.fields import String
33

4-
domain = Domain(__file__)
4+
domain = Domain()
55

66

77
@domain.aggregate

docs_src/guides/change_state_002.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from protean import Domain
22
from protean.fields import Float, HasMany, String, Text
33

4-
domain = Domain(__file__)
4+
domain = Domain()
55

66

77
@domain.aggregate

docs_src/guides/change_state_003.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from protean import Domain
22
from protean.fields import Boolean, Identifier, String, Text
33

4-
domain = Domain(__file__)
4+
domain = Domain()
55

66

77
@domain.aggregate

docs_src/guides/change_state_004.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from protean import Domain
22
from protean.fields import Integer, String
33

4-
domain = Domain(__file__)
4+
domain = Domain()
55

66

77
@domain.aggregate

0 commit comments

Comments
 (0)