From 353b840952137307569483a06f17f3dc486a4cc2 Mon Sep 17 00:00:00 2001 From: Subhash Bhushan Date: Sun, 9 Jun 2024 07:16:02 -0700 Subject: [PATCH 1/3] Enable VSCode Testing Changes: - Avoid using `current_domain` where domain object is readily accessible - Rename `test_domains` directory to `domains` in `tests/support` - Add `tests/support` to ignore path in .vscode/settings file - Change files to throw OutOfContextError now that there is no import time access to OutOfContextError --- .gitignore | 3 - .vscode/launch.json | 147 ++++++++++++++++++ .vscode/settings.json | 8 + src/protean/__init__.py | 3 - src/protean/adapters/repository/sqlalchemy.py | 27 ++-- src/protean/core/event_sourced_repository.py | 4 +- src/protean/core/repository.py | 11 +- src/protean/domain/__init__.py | 5 +- src/protean/fields/basic.py | 15 +- tests/domain/test_domain_traversal.py | 6 +- tests/field/test_identifier.py | 6 +- tests/shared.py | 4 +- .../{test_domains => domains}/README.md | 0 .../{test_domains => domains}/test1/basic.py | 0 .../test10/domain.py | 0 .../test11/subdomain.py | 0 .../{test_domains => domains}/test12/foo12.py | 0 .../test13/content/post13.py | 2 +- .../test13/content/user13.py | 2 +- .../test13/publishing13.py | 0 .../test14/domain.toml | 0 .../test14/domain14.py | 0 .../test15/.domain.toml | 0 .../test15/README.md | 0 .../test15/domain.toml | 0 .../test15/domain15.py | 0 .../test16/README.md | 0 .../test16/domain.toml | 0 .../test16/domain16.py | 0 .../test16/pyproject.toml | 0 .../test17/README.md | 0 .../test17/domain17.py | 0 .../test17/pyproject.toml | 0 .../test18/domain.toml | 0 .../test18/domain18.py | 0 .../test19/domain19.py | 0 .../test2/src/folder.py | 0 .../test20/README.md | 0 .../test20/__init__.py | 0 .../test20/auth/__init__.py | 0 .../test20/auth/account20.py | 0 .../test20/content/__init__.py | 0 .../test20/content/post20.py | 0 .../test20/domain.toml | 0 .../test20/publishing20.py | 0 .../test21/README.md | 0 .../test21/__init__.py | 0 .../test21/auth/__init__.py | 0 .../test21/auth/account21.py | 0 .../test21/auth/domain.toml | 0 .../test21/content/__init__.py | 0 .../test21/content/post21.py | 0 .../test21/domain.toml | 0 .../test21/publishing21.py | 0 .../test22/README.md | 0 .../test22/src/domain.toml | 0 .../test22/src/publishing/domain22.py | 0 .../test23/README.md | 0 .../test23/domain.toml | 0 .../test23/src/publishing/domain23.py | 0 .../test24/README.md | 0 .../test24/domain24.py | 0 .../test24/pyproject.toml | 0 .../test3/__init__.py | 0 .../test3/nested/web.py | 0 .../test4/instance.py | 0 .../{test_domains => domains}/test5/dummy.py | 0 .../{test_domains => domains}/test6/post6.py | 2 +- .../test6/publishing6.py | 0 .../test7/__init__.py | 0 .../{test_domains => domains}/test7/post7.py | 2 +- .../test7/publishing7.py | 0 .../test8/sqlite_domain.py | 0 .../test9/publishing9.py | 0 74 files changed, 195 insertions(+), 52 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json rename tests/support/{test_domains => domains}/README.md (100%) rename tests/support/{test_domains => domains}/test1/basic.py (100%) rename tests/support/{test_domains => domains}/test10/domain.py (100%) rename tests/support/{test_domains => domains}/test11/subdomain.py (100%) rename tests/support/{test_domains => domains}/test12/foo12.py (100%) rename tests/support/{test_domains => domains}/test13/content/post13.py (84%) rename tests/support/{test_domains => domains}/test13/content/user13.py (68%) rename tests/support/{test_domains => domains}/test13/publishing13.py (100%) rename tests/support/{test_domains => domains}/test14/domain.toml (100%) rename tests/support/{test_domains => domains}/test14/domain14.py (100%) rename tests/support/{test_domains => domains}/test15/.domain.toml (100%) rename tests/support/{test_domains => domains}/test15/README.md (100%) rename tests/support/{test_domains => domains}/test15/domain.toml (100%) rename tests/support/{test_domains => domains}/test15/domain15.py (100%) rename tests/support/{test_domains => domains}/test16/README.md (100%) rename tests/support/{test_domains => domains}/test16/domain.toml (100%) rename tests/support/{test_domains => domains}/test16/domain16.py (100%) rename tests/support/{test_domains => domains}/test16/pyproject.toml (100%) rename tests/support/{test_domains => domains}/test17/README.md (100%) rename tests/support/{test_domains => domains}/test17/domain17.py (100%) rename tests/support/{test_domains => domains}/test17/pyproject.toml (100%) rename tests/support/{test_domains => domains}/test18/domain.toml (100%) rename tests/support/{test_domains => domains}/test18/domain18.py (100%) rename tests/support/{test_domains => domains}/test19/domain19.py (100%) rename tests/support/{test_domains => domains}/test2/src/folder.py (100%) rename tests/support/{test_domains => domains}/test20/README.md (100%) rename tests/support/{test_domains => domains}/test20/__init__.py (100%) rename tests/support/{test_domains => domains}/test20/auth/__init__.py (100%) rename tests/support/{test_domains => domains}/test20/auth/account20.py (100%) rename tests/support/{test_domains => domains}/test20/content/__init__.py (100%) rename tests/support/{test_domains => domains}/test20/content/post20.py (100%) rename tests/support/{test_domains => domains}/test20/domain.toml (100%) rename tests/support/{test_domains => domains}/test20/publishing20.py (100%) rename tests/support/{test_domains => domains}/test21/README.md (100%) rename tests/support/{test_domains => domains}/test21/__init__.py (100%) rename tests/support/{test_domains => domains}/test21/auth/__init__.py (100%) rename tests/support/{test_domains => domains}/test21/auth/account21.py (100%) rename tests/support/{test_domains => domains}/test21/auth/domain.toml (100%) rename tests/support/{test_domains => domains}/test21/content/__init__.py (100%) rename tests/support/{test_domains => domains}/test21/content/post21.py (100%) rename tests/support/{test_domains => domains}/test21/domain.toml (100%) rename tests/support/{test_domains => domains}/test21/publishing21.py (100%) rename tests/support/{test_domains => domains}/test22/README.md (100%) rename tests/support/{test_domains => domains}/test22/src/domain.toml (100%) rename tests/support/{test_domains => domains}/test22/src/publishing/domain22.py (100%) rename tests/support/{test_domains => domains}/test23/README.md (100%) rename tests/support/{test_domains => domains}/test23/domain.toml (100%) rename tests/support/{test_domains => domains}/test23/src/publishing/domain23.py (100%) rename tests/support/{test_domains => domains}/test24/README.md (100%) rename tests/support/{test_domains => domains}/test24/domain24.py (100%) rename tests/support/{test_domains => domains}/test24/pyproject.toml (100%) rename tests/support/{test_domains => domains}/test3/__init__.py (100%) rename tests/support/{test_domains => domains}/test3/nested/web.py (100%) rename tests/support/{test_domains => domains}/test4/instance.py (100%) rename tests/support/{test_domains => domains}/test5/dummy.py (100%) rename tests/support/{test_domains => domains}/test6/post6.py (84%) rename tests/support/{test_domains => domains}/test6/publishing6.py (100%) rename tests/support/{test_domains => domains}/test7/__init__.py (100%) rename tests/support/{test_domains => domains}/test7/post7.py (84%) rename tests/support/{test_domains => domains}/test7/publishing7.py (100%) rename tests/support/{test_domains => domains}/test8/sqlite_domain.py (100%) rename tests/support/{test_domains => domains}/test9/publishing9.py (100%) diff --git a/.gitignore b/.gitignore index d29cab01..b6e920d3 100644 --- a/.gitignore +++ b/.gitignore @@ -135,9 +135,6 @@ output/*/index.html .testmondata *.rdb -# VS Code -.vscode - # Sphinx docs-sphinx/.doctrees diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..fd910f23 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,147 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Protean CLI", + "type": "debugpy", + "request": "launch", + "module": "protean", + "console": "integratedTerminal", + "justMyCode": false, + "args": [ + "new", + "-o", + "/Users/subhashb/wspace/proteanhq/test-scaffolding", + "-d", + "author_name=Subhash Bhushan", + "-d", + "author_email=subhash@gmail.com", + ] + }, + { + "name": "Python: Debug Tests", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "purpose": ["debug-test"], + "console": "integratedTerminal", + "justMyCode": false + }, + { + "name": "Python: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": false + }, + { + "name": "Python: Pytest", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "justMyCode": false, + "args": [ + "tests/test_aggregates.py::TestAggregateIdentity::test_that_abstract_aggregates_do_not_have_id_field", + ] + }, + { + "name": "Python: Sqlite - Specific test case", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "justMyCode": false, + "args": [ + "tests/adapters/repository/sqlalchemy_repo/sqlite/test_provider.py::TestProviders::test_provider_raw", + "--sqlite" + ] + }, + { + "name": "Python: Postgres - Specific test case", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "justMyCode": false, + "args": [ + "tests/adapters/model/sqlalchemy_model/postgresql/test_array_datatype.py::test_array_data_type_association", + "--postgresql" + ] + }, + { + "name": "Python: Redis - Specific test case", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "tests/adapters/broker/redis_broker/tests.py::TestPublishingToRedis::test_event_message_structure", + "--redis" + ] + }, + { + "name": "Python: MessageDB - Specific test case", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "tests/adapters/event_store/message_db_event_store/tests.py::TestMessageDBEventStore::test_error_on_message_db_initialization", + "--message_db" + ] + }, + { + "name": "Python: Elasticsearch Generic", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "justMyCode": false, + "args": [ + "tests/adapters/repository/test_generic.py::TestConcurrency::test_expected_version_error_on_version_mismatch", + "--db=ELASTICSEARCH" + ] + }, + { + "name": "Python: Elasticsearch", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "justMyCode": false, + "args": [ + "tests/adapters/model/elasticsearch_model/tests.py::TestModelOptions::TestModelSettings::test_settings_override_in_custom_model", + "--elasticsearch" + ] + }, + { + "name": "Python: All Postgresql", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": ["--postgresql"] + }, + { + "name": "Generic Tests - Postgresql", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "tests/adapters/repository/test_generic.py::TestPersistenceViaRepository::test_that_aggregate_can_be_persisted_with_repository", + "-m", + "database", + "--db=POSTGRESQL", + "-sv" + ] + }, + { + "name": "Generic Tests - Elasticsearch", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": [ + "tests/adapters/repository/test_generic.py::TestPersistenceViaRepository::test_that_aggregate_can_be_persisted_with_repository", + "--db=elasticsearch", + "-sv" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a02e82a2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.testing.pytestArgs": [ + "tests", + "--ignore=tests/support" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} \ No newline at end of file diff --git a/src/protean/__init__.py b/src/protean/__init__.py index 386c0198..d3b2c5b6 100644 --- a/src/protean/__init__.py +++ b/src/protean/__init__.py @@ -19,7 +19,6 @@ from .core.value_object import BaseValueObject from .core.view import BaseView from .domain import Domain -from .globals import current_domain, current_uow from .server import Engine from .utils import get_version from .utils.mixins import handle @@ -47,8 +46,6 @@ "QuerySet", "UnitOfWork", "apply", - "current_domain", - "current_uow", "get_version", "handle", "invariant", diff --git a/src/protean/adapters/repository/sqlalchemy.py b/src/protean/adapters/repository/sqlalchemy.py index ae44b020..d0106f45 100644 --- a/src/protean/adapters/repository/sqlalchemy.py +++ b/src/protean/adapters/repository/sqlalchemy.py @@ -22,7 +22,6 @@ from protean.exceptions import ( ConfigurationError, ObjectNotFoundError, - OutOfContextError, ) from protean.fields import ( Auto, @@ -95,22 +94,20 @@ def _get_identity_type(): If `current_domain` is not yet available, it simply means that Protean is still being loaded. Default to `Identity.STRING` + + Raises: + OutOfContextError: If the method is called outside the context of a domain """ - try: - if current_domain.config["identity_type"] == IdentityType.INTEGER.value: - return sa_types.Integer - elif current_domain.config["identity_type"] == IdentityType.STRING.value: - return sa_types.String - elif current_domain.config["identity_type"] == IdentityType.UUID.value: - return GUID - else: - raise ConfigurationError( - f'Unknown Identity Type {current_domain.config["identity_type"]}' - ) - except OutOfContextError: - # This happens only when the module is being imported the first time. - # All further calls will have `current_domain` available. + if current_domain.config["identity_type"] == IdentityType.INTEGER.value: + return sa_types.Integer + elif current_domain.config["identity_type"] == IdentityType.STRING.value: return sa_types.String + elif current_domain.config["identity_type"] == IdentityType.UUID.value: + return GUID + else: + raise ConfigurationError( + f'Unknown Identity Type {current_domain.config["identity_type"]}' + ) def _default(value): diff --git a/src/protean/core/event_sourced_repository.py b/src/protean/core/event_sourced_repository.py index f4cbdd75..35a0e241 100644 --- a/src/protean/core/event_sourced_repository.py +++ b/src/protean/core/event_sourced_repository.py @@ -8,7 +8,7 @@ NotSupportedError, ) from protean.fields import Identifier -from protean.globals import current_domain, current_uow +from protean.globals import current_uow from protean.utils import DomainObjects, derive_element_class logger = logging.getLogger(__name__) @@ -54,7 +54,7 @@ def get(self, identifier: Identifier) -> BaseEventSourcedAggregate: if current_uow and identifier in current_uow._identity_map: return current_uow._identity_map[identifier] - aggregate = current_domain.event_store.store.load_aggregate( + aggregate = self._domain.event_store.store.load_aggregate( self.meta_.part_of, identifier ) diff --git a/src/protean/core/repository.py b/src/protean/core/repository.py index d7532e5d..7cd328b0 100644 --- a/src/protean/core/repository.py +++ b/src/protean/core/repository.py @@ -5,7 +5,6 @@ from protean.container import Element, OptionsMixin from protean.exceptions import IncorrectUsageError, NotSupportedError from protean.fields import HasMany, HasOne -from protean.globals import current_domain from protean.reflection import association_fields, has_association_fields from protean.utils import ( Database, @@ -147,23 +146,23 @@ def _sync_children(self, entity): # If the item was changed directly AND added via `add`, then # we give preference to the object in the cache if item not in entity._temp_cache[field_name]["updated"]: - current_domain.repository_for(field.to_cls)._dao.save(item) + self._domain.repository_for(field.to_cls)._dao.save(item) for _, item in entity._temp_cache[field_name]["removed"].items(): - current_domain.repository_for(field.to_cls)._dao.delete(item) + self._domain.repository_for(field.to_cls)._dao.delete(item) entity._temp_cache[field_name][ "removed" ] = {} # Empty contents of `removed` cache for _, item in entity._temp_cache[field_name]["updated"].items(): - current_domain.repository_for(field.to_cls)._dao.save(item) + self._domain.repository_for(field.to_cls)._dao.save(item) entity._temp_cache[field_name][ "updated" ] = {} # Empty contents of `updated` cache for _, item in entity._temp_cache[field_name]["added"].items(): item.state_.mark_new() - current_domain.repository_for(field.to_cls)._dao.save(item) + self._domain.repository_for(field.to_cls)._dao.save(item) entity._temp_cache[field_name][ "added" ] = {} # Empty contents of `added` cache @@ -173,7 +172,7 @@ def _sync_children(self, entity): # These are ones whose attributes have been changed directly # instead of being routed via `add`/`remove` item = getattr(entity, field_name) - to_cls_repo = current_domain.repository_for(field.to_cls) + to_cls_repo = self._domain.repository_for(field.to_cls) if item is not None and item.state_.is_changed: to_cls_repo._dao.save(item) # Or a new instance has been assigned diff --git a/src/protean/domain/__init__.py b/src/protean/domain/__init__.py index 1083f033..f1cd2807 100644 --- a/src/protean/domain/__init__.py +++ b/src/protean/domain/__init__.py @@ -22,7 +22,6 @@ from protean.domain.registry import _DomainRegistry from protean.exceptions import ConfigurationError, IncorrectUsageError from protean.fields import HasMany, HasOne, Reference, ValueObject -from protean.globals import current_domain from protean.reflection import declared_fields, has_fields from protean.utils import ( CommandProcessing, @@ -815,9 +814,9 @@ def publish(self, event: BaseEvent) -> None: self.brokers.publish(event) - if current_domain.config["event_processing"] == EventProcessing.SYNC.value: + if self.config["event_processing"] == EventProcessing.SYNC.value: # Consume events right-away - handler_classes = current_domain.handlers_for(event) + handler_classes = self.handlers_for(event) for handler_cls in handler_classes: handler_methods = ( handler_cls._handlers[fqn(event.__class__)] diff --git a/src/protean/fields/basic.py b/src/protean/fields/basic.py index 8c0823fd..e3907844 100644 --- a/src/protean/fields/basic.py +++ b/src/protean/fields/basic.py @@ -8,7 +8,7 @@ from dateutil.parser import parse as date_parser -from protean.exceptions import InvalidOperationError, OutOfContextError, ValidationError +from protean.exceptions import InvalidOperationError, ValidationError from protean.fields import Field, validators from protean.fields.embedded import ValueObject from protean.globals import current_domain @@ -374,13 +374,6 @@ def __init__(self, identity_type=None, **kwargs): ]: raise ValidationError({"identity_type": ["Identity type not supported"]}) - # Pick identity type from domain configuration if not provided - try: - if not identity_type: - identity_type = current_domain.config["identity_type"] - except OutOfContextError: # Domain not active - identity_type = IdentityType.STRING.value - self.identity_type = identity_type super().__init__(**kwargs) @@ -390,6 +383,12 @@ def _cast_to_type(self, value): if not (isinstance(value, (UUID, str, int))) or isinstance(value, bool): self.fail("invalid", value=value) + # Fixate on IdentityType if not done already + # This happens the first time an identifier field instance is used. + # We don't try to fix this in the constructor because the Domain may not be available at that time. + if self.identity_type is None: + self.identity_type = current_domain.config["identity_type"] + # Ensure that the value is of the right type if self.identity_type == IdentityType.UUID.value: if not isinstance(value, UUID): diff --git a/tests/domain/test_domain_traversal.py b/tests/domain/test_domain_traversal.py index a0f4e899..b6db3d07 100644 --- a/tests/domain/test_domain_traversal.py +++ b/tests/domain/test_domain_traversal.py @@ -13,14 +13,14 @@ class TestDomainTraversal: @pytest.mark.no_test_domain def test_loading_domain_without_init(self): - from tests.support.test_domains.test6 import publishing6 + from tests.support.domains.test6 import publishing6 assert publishing6.domain is not None assert len(publishing6.domain.registry.aggregates) == 0 @pytest.mark.no_test_domain def test_loading_domain_with_init(self): - from tests.support.test_domains.test7 import publishing7 + from tests.support.domains.test7 import publishing7 assert publishing7.domain is not None publishing7.domain.init() @@ -28,7 +28,7 @@ def test_loading_domain_with_init(self): @pytest.mark.no_test_domain def test_loading_nested_domain_with_init(self): - from tests.support.test_domains.test13 import publishing13 + from tests.support.domains.test13 import publishing13 assert publishing13.domain is not None publishing13.domain.init() diff --git a/tests/field/test_identifier.py b/tests/field/test_identifier.py index ac59f3d5..e6fd6a3d 100644 --- a/tests/field/test_identifier.py +++ b/tests/field/test_identifier.py @@ -115,8 +115,8 @@ def test_that_default_is_picked_from_domain_config(self): identifier = Identifier() # Can load UUIDs as Strings - assert identifier.identity_type == IdentityType.STRING.value assert identifier._load(str(uuid_val)) == str(uuid_val) + assert identifier.identity_type == IdentityType.STRING.value # Can load arbitrary strings as well assert identifier._load("42") == "42" @@ -125,14 +125,14 @@ def test_that_default_is_picked_from_domain_config(self): domain.config["identity_type"] = IdentityType.INTEGER.value with domain.domain_context(): identifier = Identifier() - assert identifier.identity_type == IdentityType.INTEGER.value assert identifier._load(42) == 42 + assert identifier.identity_type == IdentityType.INTEGER.value assert identifier.as_dict(42) == 42 domain.config["identity_type"] = IdentityType.UUID.value with domain.domain_context(): uuid_val = uuid4() identifier = Identifier() - assert identifier.identity_type == IdentityType.UUID.value assert identifier._load(uuid_val) == uuid_val + assert identifier.identity_type == IdentityType.UUID.value assert identifier.as_dict(uuid_val) == str(uuid_val) diff --git a/tests/shared.py b/tests/shared.py index a2183b05..9de7fffa 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -30,9 +30,9 @@ def change_working_directory_to(path): """Change working directory to a specific test directory and add it to the Python path so that the test can import. - The test directory is expected to be in `support/test_domains`. + The test directory is expected to be in `support/domains`. """ - test_path = (Path(__file__) / ".." / "support" / "test_domains" / path).resolve() + test_path = (Path(__file__) / ".." / "support" / "domains" / path).resolve() os.chdir(test_path) sys.path.insert(0, str(test_path)) diff --git a/tests/support/test_domains/README.md b/tests/support/domains/README.md similarity index 100% rename from tests/support/test_domains/README.md rename to tests/support/domains/README.md diff --git a/tests/support/test_domains/test1/basic.py b/tests/support/domains/test1/basic.py similarity index 100% rename from tests/support/test_domains/test1/basic.py rename to tests/support/domains/test1/basic.py diff --git a/tests/support/test_domains/test10/domain.py b/tests/support/domains/test10/domain.py similarity index 100% rename from tests/support/test_domains/test10/domain.py rename to tests/support/domains/test10/domain.py diff --git a/tests/support/test_domains/test11/subdomain.py b/tests/support/domains/test11/subdomain.py similarity index 100% rename from tests/support/test_domains/test11/subdomain.py rename to tests/support/domains/test11/subdomain.py diff --git a/tests/support/test_domains/test12/foo12.py b/tests/support/domains/test12/foo12.py similarity index 100% rename from tests/support/test_domains/test12/foo12.py rename to tests/support/domains/test12/foo12.py diff --git a/tests/support/test_domains/test13/content/post13.py b/tests/support/domains/test13/content/post13.py similarity index 84% rename from tests/support/test_domains/test13/content/post13.py rename to tests/support/domains/test13/content/post13.py index 7a8da84e..888c78cd 100644 --- a/tests/support/test_domains/test13/content/post13.py +++ b/tests/support/domains/test13/content/post13.py @@ -1,7 +1,7 @@ from datetime import datetime from protean.fields import DateTime, HasMany, Reference, String -from tests.support.test_domains.test13.publishing13 import domain +from tests.support.domains.test13.publishing13 import domain @domain.aggregate diff --git a/tests/support/test_domains/test13/content/user13.py b/tests/support/domains/test13/content/user13.py similarity index 68% rename from tests/support/test_domains/test13/content/user13.py rename to tests/support/domains/test13/content/user13.py index 92b5a9b3..dbc863cd 100644 --- a/tests/support/test_domains/test13/content/user13.py +++ b/tests/support/domains/test13/content/user13.py @@ -1,5 +1,5 @@ from protean.fields import String -from tests.support.test_domains.test13.publishing13 import domain +from tests.support.domains.test13.publishing13 import domain @domain.aggregate diff --git a/tests/support/test_domains/test13/publishing13.py b/tests/support/domains/test13/publishing13.py similarity index 100% rename from tests/support/test_domains/test13/publishing13.py rename to tests/support/domains/test13/publishing13.py diff --git a/tests/support/test_domains/test14/domain.toml b/tests/support/domains/test14/domain.toml similarity index 100% rename from tests/support/test_domains/test14/domain.toml rename to tests/support/domains/test14/domain.toml diff --git a/tests/support/test_domains/test14/domain14.py b/tests/support/domains/test14/domain14.py similarity index 100% rename from tests/support/test_domains/test14/domain14.py rename to tests/support/domains/test14/domain14.py diff --git a/tests/support/test_domains/test15/.domain.toml b/tests/support/domains/test15/.domain.toml similarity index 100% rename from tests/support/test_domains/test15/.domain.toml rename to tests/support/domains/test15/.domain.toml diff --git a/tests/support/test_domains/test15/README.md b/tests/support/domains/test15/README.md similarity index 100% rename from tests/support/test_domains/test15/README.md rename to tests/support/domains/test15/README.md diff --git a/tests/support/test_domains/test15/domain.toml b/tests/support/domains/test15/domain.toml similarity index 100% rename from tests/support/test_domains/test15/domain.toml rename to tests/support/domains/test15/domain.toml diff --git a/tests/support/test_domains/test15/domain15.py b/tests/support/domains/test15/domain15.py similarity index 100% rename from tests/support/test_domains/test15/domain15.py rename to tests/support/domains/test15/domain15.py diff --git a/tests/support/test_domains/test16/README.md b/tests/support/domains/test16/README.md similarity index 100% rename from tests/support/test_domains/test16/README.md rename to tests/support/domains/test16/README.md diff --git a/tests/support/test_domains/test16/domain.toml b/tests/support/domains/test16/domain.toml similarity index 100% rename from tests/support/test_domains/test16/domain.toml rename to tests/support/domains/test16/domain.toml diff --git a/tests/support/test_domains/test16/domain16.py b/tests/support/domains/test16/domain16.py similarity index 100% rename from tests/support/test_domains/test16/domain16.py rename to tests/support/domains/test16/domain16.py diff --git a/tests/support/test_domains/test16/pyproject.toml b/tests/support/domains/test16/pyproject.toml similarity index 100% rename from tests/support/test_domains/test16/pyproject.toml rename to tests/support/domains/test16/pyproject.toml diff --git a/tests/support/test_domains/test17/README.md b/tests/support/domains/test17/README.md similarity index 100% rename from tests/support/test_domains/test17/README.md rename to tests/support/domains/test17/README.md diff --git a/tests/support/test_domains/test17/domain17.py b/tests/support/domains/test17/domain17.py similarity index 100% rename from tests/support/test_domains/test17/domain17.py rename to tests/support/domains/test17/domain17.py diff --git a/tests/support/test_domains/test17/pyproject.toml b/tests/support/domains/test17/pyproject.toml similarity index 100% rename from tests/support/test_domains/test17/pyproject.toml rename to tests/support/domains/test17/pyproject.toml diff --git a/tests/support/test_domains/test18/domain.toml b/tests/support/domains/test18/domain.toml similarity index 100% rename from tests/support/test_domains/test18/domain.toml rename to tests/support/domains/test18/domain.toml diff --git a/tests/support/test_domains/test18/domain18.py b/tests/support/domains/test18/domain18.py similarity index 100% rename from tests/support/test_domains/test18/domain18.py rename to tests/support/domains/test18/domain18.py diff --git a/tests/support/test_domains/test19/domain19.py b/tests/support/domains/test19/domain19.py similarity index 100% rename from tests/support/test_domains/test19/domain19.py rename to tests/support/domains/test19/domain19.py diff --git a/tests/support/test_domains/test2/src/folder.py b/tests/support/domains/test2/src/folder.py similarity index 100% rename from tests/support/test_domains/test2/src/folder.py rename to tests/support/domains/test2/src/folder.py diff --git a/tests/support/test_domains/test20/README.md b/tests/support/domains/test20/README.md similarity index 100% rename from tests/support/test_domains/test20/README.md rename to tests/support/domains/test20/README.md diff --git a/tests/support/test_domains/test20/__init__.py b/tests/support/domains/test20/__init__.py similarity index 100% rename from tests/support/test_domains/test20/__init__.py rename to tests/support/domains/test20/__init__.py diff --git a/tests/support/test_domains/test20/auth/__init__.py b/tests/support/domains/test20/auth/__init__.py similarity index 100% rename from tests/support/test_domains/test20/auth/__init__.py rename to tests/support/domains/test20/auth/__init__.py diff --git a/tests/support/test_domains/test20/auth/account20.py b/tests/support/domains/test20/auth/account20.py similarity index 100% rename from tests/support/test_domains/test20/auth/account20.py rename to tests/support/domains/test20/auth/account20.py diff --git a/tests/support/test_domains/test20/content/__init__.py b/tests/support/domains/test20/content/__init__.py similarity index 100% rename from tests/support/test_domains/test20/content/__init__.py rename to tests/support/domains/test20/content/__init__.py diff --git a/tests/support/test_domains/test20/content/post20.py b/tests/support/domains/test20/content/post20.py similarity index 100% rename from tests/support/test_domains/test20/content/post20.py rename to tests/support/domains/test20/content/post20.py diff --git a/tests/support/test_domains/test20/domain.toml b/tests/support/domains/test20/domain.toml similarity index 100% rename from tests/support/test_domains/test20/domain.toml rename to tests/support/domains/test20/domain.toml diff --git a/tests/support/test_domains/test20/publishing20.py b/tests/support/domains/test20/publishing20.py similarity index 100% rename from tests/support/test_domains/test20/publishing20.py rename to tests/support/domains/test20/publishing20.py diff --git a/tests/support/test_domains/test21/README.md b/tests/support/domains/test21/README.md similarity index 100% rename from tests/support/test_domains/test21/README.md rename to tests/support/domains/test21/README.md diff --git a/tests/support/test_domains/test21/__init__.py b/tests/support/domains/test21/__init__.py similarity index 100% rename from tests/support/test_domains/test21/__init__.py rename to tests/support/domains/test21/__init__.py diff --git a/tests/support/test_domains/test21/auth/__init__.py b/tests/support/domains/test21/auth/__init__.py similarity index 100% rename from tests/support/test_domains/test21/auth/__init__.py rename to tests/support/domains/test21/auth/__init__.py diff --git a/tests/support/test_domains/test21/auth/account21.py b/tests/support/domains/test21/auth/account21.py similarity index 100% rename from tests/support/test_domains/test21/auth/account21.py rename to tests/support/domains/test21/auth/account21.py diff --git a/tests/support/test_domains/test21/auth/domain.toml b/tests/support/domains/test21/auth/domain.toml similarity index 100% rename from tests/support/test_domains/test21/auth/domain.toml rename to tests/support/domains/test21/auth/domain.toml diff --git a/tests/support/test_domains/test21/content/__init__.py b/tests/support/domains/test21/content/__init__.py similarity index 100% rename from tests/support/test_domains/test21/content/__init__.py rename to tests/support/domains/test21/content/__init__.py diff --git a/tests/support/test_domains/test21/content/post21.py b/tests/support/domains/test21/content/post21.py similarity index 100% rename from tests/support/test_domains/test21/content/post21.py rename to tests/support/domains/test21/content/post21.py diff --git a/tests/support/test_domains/test21/domain.toml b/tests/support/domains/test21/domain.toml similarity index 100% rename from tests/support/test_domains/test21/domain.toml rename to tests/support/domains/test21/domain.toml diff --git a/tests/support/test_domains/test21/publishing21.py b/tests/support/domains/test21/publishing21.py similarity index 100% rename from tests/support/test_domains/test21/publishing21.py rename to tests/support/domains/test21/publishing21.py diff --git a/tests/support/test_domains/test22/README.md b/tests/support/domains/test22/README.md similarity index 100% rename from tests/support/test_domains/test22/README.md rename to tests/support/domains/test22/README.md diff --git a/tests/support/test_domains/test22/src/domain.toml b/tests/support/domains/test22/src/domain.toml similarity index 100% rename from tests/support/test_domains/test22/src/domain.toml rename to tests/support/domains/test22/src/domain.toml diff --git a/tests/support/test_domains/test22/src/publishing/domain22.py b/tests/support/domains/test22/src/publishing/domain22.py similarity index 100% rename from tests/support/test_domains/test22/src/publishing/domain22.py rename to tests/support/domains/test22/src/publishing/domain22.py diff --git a/tests/support/test_domains/test23/README.md b/tests/support/domains/test23/README.md similarity index 100% rename from tests/support/test_domains/test23/README.md rename to tests/support/domains/test23/README.md diff --git a/tests/support/test_domains/test23/domain.toml b/tests/support/domains/test23/domain.toml similarity index 100% rename from tests/support/test_domains/test23/domain.toml rename to tests/support/domains/test23/domain.toml diff --git a/tests/support/test_domains/test23/src/publishing/domain23.py b/tests/support/domains/test23/src/publishing/domain23.py similarity index 100% rename from tests/support/test_domains/test23/src/publishing/domain23.py rename to tests/support/domains/test23/src/publishing/domain23.py diff --git a/tests/support/test_domains/test24/README.md b/tests/support/domains/test24/README.md similarity index 100% rename from tests/support/test_domains/test24/README.md rename to tests/support/domains/test24/README.md diff --git a/tests/support/test_domains/test24/domain24.py b/tests/support/domains/test24/domain24.py similarity index 100% rename from tests/support/test_domains/test24/domain24.py rename to tests/support/domains/test24/domain24.py diff --git a/tests/support/test_domains/test24/pyproject.toml b/tests/support/domains/test24/pyproject.toml similarity index 100% rename from tests/support/test_domains/test24/pyproject.toml rename to tests/support/domains/test24/pyproject.toml diff --git a/tests/support/test_domains/test3/__init__.py b/tests/support/domains/test3/__init__.py similarity index 100% rename from tests/support/test_domains/test3/__init__.py rename to tests/support/domains/test3/__init__.py diff --git a/tests/support/test_domains/test3/nested/web.py b/tests/support/domains/test3/nested/web.py similarity index 100% rename from tests/support/test_domains/test3/nested/web.py rename to tests/support/domains/test3/nested/web.py diff --git a/tests/support/test_domains/test4/instance.py b/tests/support/domains/test4/instance.py similarity index 100% rename from tests/support/test_domains/test4/instance.py rename to tests/support/domains/test4/instance.py diff --git a/tests/support/test_domains/test5/dummy.py b/tests/support/domains/test5/dummy.py similarity index 100% rename from tests/support/test_domains/test5/dummy.py rename to tests/support/domains/test5/dummy.py diff --git a/tests/support/test_domains/test6/post6.py b/tests/support/domains/test6/post6.py similarity index 84% rename from tests/support/test_domains/test6/post6.py rename to tests/support/domains/test6/post6.py index 3ac5a43d..64fa50d2 100644 --- a/tests/support/test_domains/test6/post6.py +++ b/tests/support/domains/test6/post6.py @@ -1,7 +1,7 @@ from datetime import datetime from protean.fields import DateTime, HasMany, Reference, String -from tests.support.test_domains.test6.publishing6 import domain +from tests.support.domains.test6.publishing6 import domain @domain.aggregate diff --git a/tests/support/test_domains/test6/publishing6.py b/tests/support/domains/test6/publishing6.py similarity index 100% rename from tests/support/test_domains/test6/publishing6.py rename to tests/support/domains/test6/publishing6.py diff --git a/tests/support/test_domains/test7/__init__.py b/tests/support/domains/test7/__init__.py similarity index 100% rename from tests/support/test_domains/test7/__init__.py rename to tests/support/domains/test7/__init__.py diff --git a/tests/support/test_domains/test7/post7.py b/tests/support/domains/test7/post7.py similarity index 84% rename from tests/support/test_domains/test7/post7.py rename to tests/support/domains/test7/post7.py index ffa08ce2..4c8af9f8 100644 --- a/tests/support/test_domains/test7/post7.py +++ b/tests/support/domains/test7/post7.py @@ -1,7 +1,7 @@ from datetime import datetime from protean.fields import DateTime, HasMany, Reference, String -from tests.support.test_domains.test7.publishing7 import domain +from tests.support.domains.test7.publishing7 import domain @domain.aggregate diff --git a/tests/support/test_domains/test7/publishing7.py b/tests/support/domains/test7/publishing7.py similarity index 100% rename from tests/support/test_domains/test7/publishing7.py rename to tests/support/domains/test7/publishing7.py diff --git a/tests/support/test_domains/test8/sqlite_domain.py b/tests/support/domains/test8/sqlite_domain.py similarity index 100% rename from tests/support/test_domains/test8/sqlite_domain.py rename to tests/support/domains/test8/sqlite_domain.py diff --git a/tests/support/test_domains/test9/publishing9.py b/tests/support/domains/test9/publishing9.py similarity index 100% rename from tests/support/test_domains/test9/publishing9.py rename to tests/support/domains/test9/publishing9.py From efdc1d1e04b6802c21c8aefd935680f5023d66bc Mon Sep 17 00:00:00 2001 From: Subhash Bhushan Date: Sun, 9 Jun 2024 08:15:03 -0700 Subject: [PATCH 2/3] Add additional tests for basic fields --- src/protean/fields/base.py | 7 +++-- src/protean/fields/basic.py | 18 +++++++++--- tests/field/test_boolean.py | 23 +++++++++++++++ tests/field/test_identifier.py | 4 +++ tests/field/test_method.py | 21 ++++++++++++++ tests/field/test_nested.py | 21 ++++++++++++++ tests/field/test_repr.py | 52 +++++++++++++++++++++++++++++++++- 7 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 tests/field/test_boolean.py create mode 100644 tests/field/test_method.py create mode 100644 tests/field/test_nested.py diff --git a/src/protean/fields/base.py b/src/protean/fields/base.py index eb752927..ddedb181 100644 --- a/src/protean/fields/base.py +++ b/src/protean/fields/base.py @@ -101,7 +101,7 @@ def __init__( messages.update(error_messages or {}) self.error_messages = messages - def _generic_param_values_for_repr(self): + def _generic_param_values_for_repr(self) -> list[str]: """Return the generic parameter values for the Field's repr""" values = [] if self.description: @@ -117,7 +117,10 @@ def _generic_param_values_for_repr(self): if callable(self.default): values.append(f"default={self.default.__name__}") else: - values.append(f"default='{self.default}'") + if isinstance(self.default, str): + values.append(f"default='{self.default}'") + else: + values.append(f"default={self.default}") return values def __repr__(self): diff --git a/src/protean/fields/basic.py b/src/protean/fields/basic.py index e3907844..de2615fb 100644 --- a/src/protean/fields/basic.py +++ b/src/protean/fields/basic.py @@ -130,8 +130,8 @@ def __repr__(self): # Generate repr values specific to this field values = self._generic_param_values_for_repr() if self.max_value: - values.append(f"max_value={self.max_length}") - if self.min_value: + values.append(f"max_value={self.max_value}") + if self.min_value or self.min_value == 0: values.append(f"min_value={self.min_value}") return f"{self.__class__.__name__}(" + ", ".join(values) + ")" @@ -173,8 +173,8 @@ def __repr__(self): # Generate repr values specific to this field values = self._generic_param_values_for_repr() if self.max_value: - values.append(f"max_value={self.max_length}") - if self.min_value: + values.append(f"max_value={self.max_value}") + if self.min_value or self.min_value == 0.0: values.append(f"min_value={self.min_value}") return f"{self.__class__.__name__}(" + ", ".join(values) + ")" @@ -462,6 +462,16 @@ def as_dict(self, value): """Return JSON-compatible value of self""" return value + def __repr__(self): + # Generate repr values specific to this field + values = [] + if self.schema_name: + values.append(f"'{self.schema_name}'") + + values.extend(self._generic_param_values_for_repr()) + + return f"{self.__class__.__name__}(" + ", ".join(values) + ")" + class Date(Field): """Concrete field implementation for the Date type.""" diff --git a/tests/field/test_boolean.py b/tests/field/test_boolean.py new file mode 100644 index 00000000..e55032be --- /dev/null +++ b/tests/field/test_boolean.py @@ -0,0 +1,23 @@ +import pytest + +from protean.fields.basic import Boolean + + +@pytest.mark.parametrize( + "value,expected", + [ + (True, True), + (False, False), + (1, True), + (0, False), + ("t", True), + ("f", False), + ("True", True), + ("False", False), + (1.0, True), + (0.0, False), + ], +) +def test_boolean_cast_to_type(value, expected): + field = Boolean() + assert field._cast_to_type(value) == expected diff --git a/tests/field/test_identifier.py b/tests/field/test_identifier.py index e6fd6a3d..697396c2 100644 --- a/tests/field/test_identifier.py +++ b/tests/field/test_identifier.py @@ -136,3 +136,7 @@ def test_that_default_is_picked_from_domain_config(self): assert identifier._load(uuid_val) == uuid_val assert identifier.identity_type == IdentityType.UUID.value assert identifier.as_dict(uuid_val) == str(uuid_val) + + def test_invalid_identity_type(self): + with pytest.raises(ValidationError): + Identifier(identity_type="invalid") diff --git a/tests/field/test_method.py b/tests/field/test_method.py new file mode 100644 index 00000000..4b40f366 --- /dev/null +++ b/tests/field/test_method.py @@ -0,0 +1,21 @@ +from datetime import datetime, timezone + +from protean.fields import Method + + +def utc_now(): + return datetime.now(timezone.utc) + + +def test_method_repr_and_str(): + method_obj1 = Method("fake_method") + method_obj2 = Method("fake_method", required=True) + method_obj4 = Method("fake_method", required=True, default=utc_now) + + assert repr(method_obj1) == str(method_obj1) == "Method()" + assert repr(method_obj2) == str(method_obj2) == "Method(required=True)" + assert ( + repr(method_obj4) + == str(method_obj4) + == "Method(required=True, default=utc_now)" + ) diff --git a/tests/field/test_nested.py b/tests/field/test_nested.py new file mode 100644 index 00000000..e6271e0c --- /dev/null +++ b/tests/field/test_nested.py @@ -0,0 +1,21 @@ +from protean.fields import Nested + + +def test_nested_field_repr_and_str(): + nested_obj1 = Nested("schema1") + nested_obj2 = Nested("schema1", many=True, required=True) + nested_obj3 = Nested("schema1", default={"name": "John Doe"}) + nested_obj4 = Nested("schema1", required=True, default={"name": "John Doe"}) + + assert repr(nested_obj1) == str(nested_obj1) == "Nested('schema1')" + assert repr(nested_obj2) == str(nested_obj2) == "Nested('schema1', required=True)" + assert ( + repr(nested_obj3) + == str(nested_obj3) + == "Nested('schema1', default={'name': 'John Doe'})" + ) + assert ( + repr(nested_obj4) + == str(nested_obj4) + == "Nested('schema1', required=True, default={'name': 'John Doe'})" + ) diff --git a/tests/field/test_repr.py b/tests/field/test_repr.py index 75b8398c..4a4005a4 100644 --- a/tests/field/test_repr.py +++ b/tests/field/test_repr.py @@ -1,4 +1,4 @@ -from protean.fields import String, Text +from protean.fields import Auto, Float, Integer, String, Text def test_identifier_in_repr(): @@ -85,3 +85,53 @@ def test_text_repr_and_str(): == str(text_obj4) == "Text(required=True, default='John Doe', sanitize=False)" ) + + +def test_integer_repr_and_str(): + int_obj1 = Integer(required=True) + int_obj2 = Integer(required=True, default=100) + int_obj3 = Integer(required=True, min_value=0, max_value=100) + int_obj4 = Integer(required=True, default=100, min_value=0, max_value=100) + + assert repr(int_obj1) == str(int_obj1) == "Integer(required=True)" + assert repr(int_obj2) == str(int_obj2) == "Integer(required=True, default=100)" + assert ( + repr(int_obj3) + == str(int_obj3) + == "Integer(required=True, max_value=100, min_value=0)" + ) + assert ( + repr(int_obj4) + == str(int_obj4) + == "Integer(required=True, default=100, max_value=100, min_value=0)" + ) + + +def test_float_repr_and_str(): + float_obj1 = Float(required=True) + float_obj2 = Float(required=True, default=100.0) + float_obj3 = Float(required=True, min_value=0.0, max_value=100.0) + float_obj4 = Float(required=True, default=100.0, min_value=0.0, max_value=100.0) + + assert repr(float_obj1) == str(float_obj1) == "Float(required=True)" + assert repr(float_obj2) == str(float_obj2) == "Float(required=True, default=100.0)" + assert ( + repr(float_obj3) + == str(float_obj3) + == "Float(required=True, max_value=100.0, min_value=0.0)" + ) + assert ( + repr(float_obj4) + == str(float_obj4) + == "Float(required=True, default=100.0, max_value=100.0, min_value=0.0)" + ) + + +def test_auto_repr_and_str(): + auto_obj1 = Auto() + auto_obj2 = Auto(required=True) + auto_obj3 = Auto(required=True, increment=True) + + assert repr(auto_obj1) == str(auto_obj1) == "Auto()" + assert repr(auto_obj2) == str(auto_obj2) == "Auto(required=True)" + assert repr(auto_obj3) == str(auto_obj3) == "Auto(required=True, increment=True)" From 7b28c13e98ddbc5c29274cef241e2f65c9b5dccf Mon Sep 17 00:00:00 2001 From: Subhash Bhushan Date: Sun, 9 Jun 2024 08:21:05 -0700 Subject: [PATCH 3/3] Fix broken test for Auto field --- tests/field/test_repr.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/field/test_repr.py b/tests/field/test_repr.py index 4a4005a4..358469b3 100644 --- a/tests/field/test_repr.py +++ b/tests/field/test_repr.py @@ -129,9 +129,7 @@ def test_float_repr_and_str(): def test_auto_repr_and_str(): auto_obj1 = Auto() - auto_obj2 = Auto(required=True) - auto_obj3 = Auto(required=True, increment=True) + auto_obj2 = Auto(required=True, increment=True) assert repr(auto_obj1) == str(auto_obj1) == "Auto()" - assert repr(auto_obj2) == str(auto_obj2) == "Auto(required=True)" - assert repr(auto_obj3) == str(auto_obj3) == "Auto(required=True, increment=True)" + assert repr(auto_obj2) == str(auto_obj2) == "Auto(increment=True)"