Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Control domain directory traversal explicitly in init() #391

Merged
merged 2 commits into from
Dec 6, 2023
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
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
Loading