Skip to content

Commit

Permalink
Control domain directory traversal explicitly in init() (#391)
Browse files Browse the repository at this point in the history
* Control domain directory traversal explicitly in init()
* Add note on traverse flag in domain user doc
  • Loading branch information
subhashb authored Dec 6, 2023
1 parent 166ee83 commit 71afcfd
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 25 deletions.
3 changes: 0 additions & 3 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
class Config:
DEBUG = True

# Parse domain directory and autoload domain modules
AUTOLOAD_DOMAIN = True

# A secret key for this particular Protean installation. Used in secret-key
# hashing algorithms.
SECRET_KEY = os.environ.get(
Expand Down
22 changes: 14 additions & 8 deletions docs/user/composing-a-domain.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The Domain is the one-stop gateway to:
- Retrieve dynamically-constructed artifacts like repositories and models
- Access injected technology components at runtime

Initializing a Domain
Define a Domain
---------------------

Constructing the object graph is a two-step procedure. First, you initialize a domain object at a reasonable starting
Expand All @@ -30,24 +30,28 @@ in application configuration.

.. code-block:: python
from sample_app import domain
@domain.aggregate
class User:
name = String()
email = String(required=True)
Configuring a Domain
--------------------
Initializing the Domain
-----------------------

You can pass a config file to the domain, like so:
Finally, the domain is initialized by calling the ``init`` method. This method will construct the object graph and
inject dependencies into the domain elements.

.. code-block:: python
domain.config.from_pyfile(config_path)
domain.init()
By default, a protean domain is configured to use an in-memory repository. This is useful for testing and prototyping.

Refer to :doc:`config` to understand the different ways to configure the domain.
If you do not want Protean to traverse the directory structure to discover domain elements, you can pass the
``traverse`` flag as ``False`` to the ``init`` method.

You can optionally pass a config file to the domain before initializing it. Refer to :doc:`config` to understand the different ways to configure the domain.

Activating a Domain
-------------------
Expand Down Expand Up @@ -102,6 +106,8 @@ the ``app`` object.
@app.before_request
def set_context():
domain.init()
# Push up a Domain Context
# This should be done within Flask App
context = domain.domain_context()
Expand Down
29 changes: 22 additions & 7 deletions docs/user/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ Note that Protean uses SQLAlchemy to access the SQLITE database internally.
A database file ``quickstart.db`` will be created in the location you will be running your application from.

Initialize the domain
---------------------

Before you can use the domain, you need to initialize it. Initialize the domain by calling :meth:`protean.Domain.init` on the domain instance.

.. code-block:: python
domain.init(traverse=False)
Since all our code is in the same module, we can use ``traverse=False``. If you have your code spread across multiple modules, you can set ``traverse=True`` to traverse the entire module tree and load all the elements.

Configure Flask
---------------

Expand All @@ -81,20 +92,24 @@ We also register a function to run before Flask processes the very first request
app = Flask(__name__)
@app.before_first_request
@app.before_request
def set_context():
context = domain.domain_context()
context.push()
If you want to create the database tables automatically from the structure defined in the domain, you can:

.. code-block:: python
@app.before_first_request
def setup_db():
with domain.domain_context():
for provider in domain.providers_list():
for _, aggregate in domain.registry.aggregates.items():
domain.get_dao(aggregate.cls)
domain.repository_for(aggregate.cls)._dao
provider._metadata.create_all()
@app.before_request
def set_context():
context = domain.domain_context()
context.push()
.. |flask| raw:: html

<a href="https://flask.palletsprojects.com/" target="_blank">Flask</a>
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ click = ">=7.0"
cookiecutter = ">=1.7.0"
copier = ">=6.1.0"
inflection = ">=0.5.1"
marshmallow = ">=3.15.0" # FIXME Remove core dependency
python-dateutil = ">=2.8.2"
werkzeug = ">=2.0.0"

Expand All @@ -51,7 +52,6 @@ redis = {version = "~3.5.2", optional = true}
sqlalchemy = {version = "~1.4.50", optional = true}
psycopg2 = {version = ">=2.9.9", optional = true}
celery = { version = "~5.2.7", extras = ["redis"], optional = true}
marshmallow = {version = ">=3.15.0", optional = true}
flask = {version = ">=1.1.1", optional = true}
sendgrid = {version = ">=6.1.3", optional = true}
message-db-py = {version = ">=0.1.2", optional = true}
Expand All @@ -63,7 +63,6 @@ postgresql = ["sqlalchemy", "psycopg2"]
sqlite = ["sqlalchemy"]
celery = ["celery"]
message-db = ["message-db-py"]
marshmallow = ["marshmallow"]
flask = ["flask"]
sendgrid = ["sendgrid"]

Expand Down
8 changes: 3 additions & 5 deletions src/protean/domain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ class Domain(_PackageBoundObject):
"ENV": None,
"DEBUG": None,
"SECRET_KEY": None,
"AUTOLOAD_DOMAIN": True,
"IDENTITY_STRATEGY": IdentityStrategy.UUID.value,
"IDENTITY_TYPE": IdentityType.STRING.value,
"DATABASES": {
Expand Down Expand Up @@ -171,15 +170,14 @@ def __init__(
# FIXME Should all protean elements be subclassed from a base element?
self._pending_class_resolutions: dict[str, Any] = defaultdict(list)

def init(self): # noqa: C901
def init(self, traverse=True): # 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 `AUTOLOAD_DOMAIN` flag in Protean config, `True` by default,
to control this functionality.
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.
Expand All @@ -194,7 +192,7 @@ def init(self): # noqa: C901
This method bubbles up circular import issues, if present, in the domain code.
"""
if self.config["AUTOLOAD_DOMAIN"] is True:
if traverse is True:
# Standard Library Imports
import importlib.util
import inspect
Expand Down

0 comments on commit 71afcfd

Please sign in to comment.