From 8cb8c194f2760c079eda34e37efb459a84f52187 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Tue, 25 May 2021 10:38:51 -0500 Subject: [PATCH 1/5] feat: TT-43 create decorators to add new custom attributes to response --- .gitignore | 6 +-- time_tracker_api/projects/projects_model.py | 24 +++++++-- .../projects/projects_namespace.py | 37 ++++++++++++++ utils/extend_model.py | 50 +++++++++++++++++++ 4 files changed, 108 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 80c67e2e..55f6bbfb 100644 --- a/.gitignore +++ b/.gitignore @@ -38,8 +38,4 @@ migration_status.csv .DS_Store # windows env variables -.env.bat -# mac / linux env variables -.env -#PowerShell env variables -.env.ps1 +env.* diff --git a/time_tracker_api/projects/projects_model.py b/time_tracker_api/projects/projects_model.py index 4b3b77ab..5ebe4d04 100644 --- a/time_tracker_api/projects/projects_model.py +++ b/time_tracker_api/projects/projects_model.py @@ -11,9 +11,16 @@ from time_tracker_api.customers.customers_model import ( create_dao as customers_create_dao, ) +from time_tracker_api.project_types.project_types_model import ( + create_dao as project_types_create_dao, +) from time_tracker_api.customers.customers_model import CustomerCosmosDBModel from utils.query_builder import CosmosDBQueryBuilder -from utils.extend_model import add_customer_name_to_projects +from utils.extend_model import ( + add_customer_name_to_projects, + add_custom_attribute_in_list, + add_custom_attribute +) class ProjectDao(CRUDDao): @@ -101,9 +108,18 @@ class ProjectCosmosDBDao(APICosmosDBDao, ProjectDao): def __init__(self, repository): CosmosDBDao.__init__(self, repository) - def get_all( - self, conditions: dict = None, project_ids: List = None, **kwargs - ) -> list: + @add_custom_attribute('customer', customers_create_dao()) + @add_custom_attribute('project_type', project_types_create_dao()) + def get(self, id) -> ProjectCosmosDBModel: + """ + Get one project an active client + :param (str) id: project's id + """ + return super().get(id) + + @add_custom_attribute_in_list('customer', customers_create_dao()) + @add_custom_attribute_in_list('project_type', project_types_create_dao()) + def get_all(self, conditions: dict = None, project_ids: List = None, **kwargs) -> list: """ Get all the projects an active client has :param (dict) conditions: Conditions for querying the database diff --git a/time_tracker_api/projects/projects_namespace.py b/time_tracker_api/projects/projects_namespace.py index 3757b878..82dbaf2b 100644 --- a/time_tracker_api/projects/projects_namespace.py +++ b/time_tracker_api/projects/projects_namespace.py @@ -70,7 +70,42 @@ }, ) +project_type_nested_field = ns.model('ProjectType', { + 'name': fields.String( + title='Name', + required=True, + max_length=50, + description='Name of the project type', + example=faker.random_element(["Customer", "Training", "Internal"]), + ), + 'description': NullableString( + title='Description', + required=False, + max_length=250, + description='Comments about the project type', + example=faker.paragraph(), + ) +}) + +customer_nested_field = ns.model('Customer', { + 'name': fields.String( + title='Name', + required=True, + max_length=50, + description='Name of the customer', + example=faker.company(), + ), + 'description': NullableString( + title='Description', + required=False, + max_length=250, + description='Description about the customer', + example=faker.paragraph(), + ) +}) + project_response_fields = { + # TODO: Remove this DEAD CODE 'customer_name': fields.String( required=True, title='Customer Name', @@ -78,6 +113,8 @@ description='Name of the customer of the project', example=faker.company(), ), + 'customer': fields.Nested(customer_nested_field), + 'project_type': fields.Nested(project_type_nested_field), } project_response_fields.update(common_fields) diff --git a/utils/extend_model.py b/utils/extend_model.py index b13faa44..ccf07d63 100644 --- a/utils/extend_model.py +++ b/utils/extend_model.py @@ -1,6 +1,56 @@ +from functools import wraps import re +def add_custom_attribute(attr, dao): + """ + Decorator to add an custom attribute in model, based on entity's id + :param (attr) attribute: name of the new attribute + :param (dao) dao: related entity to the model + """ + def decorator_for_single_item(func): + @wraps(func) + def wrapper(*args, **kwargs): + entity_model = func(*args, **kwargs) + attribute_id = f"{attr}_id" + value_id = entity_model.__dict__[attribute_id] + + try: + related_entity = dao.get(value_id) + setattr(entity_model, attr, related_entity) + except: + print(f"This item isn't related a {attribute_id}") + + return entity_model + return wrapper + return decorator_for_single_item + + +def add_custom_attribute_in_list(attr, dao): + """ + Decorator to add an custom attribute in model_list, based on entity's id + :param (attr) attribute: name of the new attribute + :param (dao) dao: related entity to the model + """ + def decorator_for_list_item(func): + @wraps(func) + def wrapper(*args, **kwargs): + entity_model_list = func(*args, **kwargs) + attribute_id = f"{attr}_id" + + related_entity_list = dao.get_all() + related_entities_ids_dict = {x.id: x for x in related_entity_list} + + for entity_model in entity_model_list: + value_id = entity_model.__dict__[attribute_id] + setattr(entity_model, attr, + related_entities_ids_dict.get(value_id)) + + return entity_model_list + return wrapper + return decorator_for_list_item + + def add_customer_name_to_projects(projects, customers): """ Add attribute customer_name in project model, based on customer_id of the From 4f4c6c9a5a5ae54e81585d0d092d0a677b265ae6 Mon Sep 17 00:00:00 2001 From: thegreatyamori Date: Tue, 25 May 2021 14:48:06 -0500 Subject: [PATCH 2/5] feat: TT-43 add unit test to extend_model --- tests/utils/extend_model_test.py | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/utils/extend_model_test.py diff --git a/tests/utils/extend_model_test.py b/tests/utils/extend_model_test.py new file mode 100644 index 00000000..f2125a30 --- /dev/null +++ b/tests/utils/extend_model_test.py @@ -0,0 +1,49 @@ +from time_tracker_api.customers.customers_model import CustomerCosmosDBModel +from time_tracker_api.projects.projects_model import ProjectCosmosDBModel +from unittest.mock import patch +from utils.extend_model import add_custom_attribute + + +@patch('time_tracker_api.project_types.project_types_model.create_dao') +@patch('time_tracker_api.customers.customers_model.create_dao') +def test_add_custom_attribute(customers_create_dao, projects_create_dao): + expected_result = { + "name": "Franklin, Mcdonald and Morrison", + "description": "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard.", + "customer_id": "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178", + "project_type_id": "208aadb7-1ec1-4a67-a0b0-e0308d27045b", + "technologies": "['python', 'restplus', 'openapi']", + "status": "active", + "customer_name": "Tucker Inc", + "customer": { + "name": None, + "description": None + }, + "project_type": { + "name": None, + "description": None + }, + "id": "768c924e-4501-457f-99c5-7198440d3c60", + "tenant_id": "e2953984-03e7-4730-be29-1753d24df3b0", + "deleted": "null" + } + + @add_custom_attribute('project_type', projects_create_dao()) + @add_custom_attribute('customer', customers_create_dao()) + @patch('time_tracker_api.projects.projects_model.ProjectCosmosDBModel') + def fn(project): + project.return_value.name = "Franklin, Mcdonald and Morrison", + project.return_value.description = "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard.", + project.return_value.customer_id = "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178", + project.return_value.project_type_id = "208aadb7-1ec1-4a67-a0b0-e0308d27045b", + project.return_value.technologies = "['python', 'restplus', 'openapi']", + project.return_value.status = "active", + project.return_value.customer_name = "Tucker Inc", + project.return_value.id = "768c924e-4501-457f-99c5-7198440d3c60", + project.return_value.tenant_id = "e2953984-03e7-4730-be29-1753d24df3b0", + project.return_value.deleted = None, + + return project.return_value + + assert 'customer' in fn().__dict__ + assert 'project_type' in fn().__dict__ From 96157098553cab2821b29cdb63e2e6ac93bc20f3 Mon Sep 17 00:00:00 2001 From: jcalarcon98 Date: Fri, 28 May 2021 17:03:26 -0500 Subject: [PATCH 3/5] fix: TT-43 Fixing decorators errors on tests --- tests/utils/extend_model_test.py | 46 +++++++++++---------- time_tracker_api/projects/projects_model.py | 14 ++++--- utils/extend_model.py | 19 ++++++--- 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/tests/utils/extend_model_test.py b/tests/utils/extend_model_test.py index f2125a30..1b18fa4a 100644 --- a/tests/utils/extend_model_test.py +++ b/tests/utils/extend_model_test.py @@ -15,33 +15,37 @@ def test_add_custom_attribute(customers_create_dao, projects_create_dao): "technologies": "['python', 'restplus', 'openapi']", "status": "active", "customer_name": "Tucker Inc", - "customer": { - "name": None, - "description": None - }, - "project_type": { - "name": None, - "description": None - }, + "customer": {"name": None, "description": None}, + "project_type": {"name": None, "description": None}, "id": "768c924e-4501-457f-99c5-7198440d3c60", "tenant_id": "e2953984-03e7-4730-be29-1753d24df3b0", - "deleted": "null" + "deleted": "null", } - @add_custom_attribute('project_type', projects_create_dao()) - @add_custom_attribute('customer', customers_create_dao()) + @add_custom_attribute('project_type', projects_create_dao) + @add_custom_attribute('customer', customers_create_dao) @patch('time_tracker_api.projects.projects_model.ProjectCosmosDBModel') def fn(project): - project.return_value.name = "Franklin, Mcdonald and Morrison", - project.return_value.description = "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard.", - project.return_value.customer_id = "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178", - project.return_value.project_type_id = "208aadb7-1ec1-4a67-a0b0-e0308d27045b", - project.return_value.technologies = "['python', 'restplus', 'openapi']", - project.return_value.status = "active", - project.return_value.customer_name = "Tucker Inc", - project.return_value.id = "768c924e-4501-457f-99c5-7198440d3c60", - project.return_value.tenant_id = "e2953984-03e7-4730-be29-1753d24df3b0", - project.return_value.deleted = None, + project.return_value.name = ("Franklin, Mcdonald and Morrison",) + project.return_value.description = ( + "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard.", + ) + project.return_value.customer_id = ( + "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178", + ) + project.return_value.project_type_id = ( + "208aadb7-1ec1-4a67-a0b0-e0308d27045b", + ) + project.return_value.technologies = ( + "['python', 'restplus', 'openapi']", + ) + project.return_value.status = ("active",) + project.return_value.customer_name = ("Tucker Inc",) + project.return_value.id = ("768c924e-4501-457f-99c5-7198440d3c60",) + project.return_value.tenant_id = ( + "e2953984-03e7-4730-be29-1753d24df3b0", + ) + project.return_value.deleted = (None,) return project.return_value diff --git a/time_tracker_api/projects/projects_model.py b/time_tracker_api/projects/projects_model.py index 5ebe4d04..4ee44375 100644 --- a/time_tracker_api/projects/projects_model.py +++ b/time_tracker_api/projects/projects_model.py @@ -19,7 +19,7 @@ from utils.extend_model import ( add_customer_name_to_projects, add_custom_attribute_in_list, - add_custom_attribute + add_custom_attribute, ) @@ -108,8 +108,8 @@ class ProjectCosmosDBDao(APICosmosDBDao, ProjectDao): def __init__(self, repository): CosmosDBDao.__init__(self, repository) - @add_custom_attribute('customer', customers_create_dao()) - @add_custom_attribute('project_type', project_types_create_dao()) + @add_custom_attribute('customer', customers_create_dao) + @add_custom_attribute('project_type', project_types_create_dao) def get(self, id) -> ProjectCosmosDBModel: """ Get one project an active client @@ -117,9 +117,11 @@ def get(self, id) -> ProjectCosmosDBModel: """ return super().get(id) - @add_custom_attribute_in_list('customer', customers_create_dao()) - @add_custom_attribute_in_list('project_type', project_types_create_dao()) - def get_all(self, conditions: dict = None, project_ids: List = None, **kwargs) -> list: + @add_custom_attribute_in_list('customer', customers_create_dao) + @add_custom_attribute_in_list('project_type', project_types_create_dao) + def get_all( + self, conditions: dict = None, project_ids: List = None, **kwargs + ) -> list: """ Get all the projects an active client has :param (dict) conditions: Conditions for querying the database diff --git a/utils/extend_model.py b/utils/extend_model.py index ccf07d63..a45f5772 100644 --- a/utils/extend_model.py +++ b/utils/extend_model.py @@ -8,21 +8,25 @@ def add_custom_attribute(attr, dao): :param (attr) attribute: name of the new attribute :param (dao) dao: related entity to the model """ + def decorator_for_single_item(func): @wraps(func) def wrapper(*args, **kwargs): + current_dao = dao() entity_model = func(*args, **kwargs) attribute_id = f"{attr}_id" - value_id = entity_model.__dict__[attribute_id] try: - related_entity = dao.get(value_id) + value_id = entity_model.__dict__[attribute_id] + related_entity = current_dao.get(value_id) setattr(entity_model, attr, related_entity) except: print(f"This item isn't related a {attribute_id}") return entity_model + return wrapper + return decorator_for_single_item @@ -32,22 +36,27 @@ def add_custom_attribute_in_list(attr, dao): :param (attr) attribute: name of the new attribute :param (dao) dao: related entity to the model """ + def decorator_for_list_item(func): @wraps(func) def wrapper(*args, **kwargs): + current_dao = dao() entity_model_list = func(*args, **kwargs) attribute_id = f"{attr}_id" - related_entity_list = dao.get_all() + related_entity_list = current_dao.get_all() related_entities_ids_dict = {x.id: x for x in related_entity_list} for entity_model in entity_model_list: value_id = entity_model.__dict__[attribute_id] - setattr(entity_model, attr, - related_entities_ids_dict.get(value_id)) + setattr( + entity_model, attr, related_entities_ids_dict.get(value_id) + ) return entity_model_list + return wrapper + return decorator_for_list_item From d3694ba50f81d985a076e2df35d5e4b423aab941 Mon Sep 17 00:00:00 2001 From: jcalarcon98 Date: Fri, 28 May 2021 19:01:38 -0500 Subject: [PATCH 4/5] fix: TT-43 Fixing code smells --- tests/utils/extend_model_test.py | 23 ++++------------------- utils/extend_model.py | 9 ++++----- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/utils/extend_model_test.py b/tests/utils/extend_model_test.py index 1b18fa4a..20393c7b 100644 --- a/tests/utils/extend_model_test.py +++ b/tests/utils/extend_model_test.py @@ -1,5 +1,3 @@ -from time_tracker_api.customers.customers_model import CustomerCosmosDBModel -from time_tracker_api.projects.projects_model import ProjectCosmosDBModel from unittest.mock import patch from utils.extend_model import add_custom_attribute @@ -7,21 +5,6 @@ @patch('time_tracker_api.project_types.project_types_model.create_dao') @patch('time_tracker_api.customers.customers_model.create_dao') def test_add_custom_attribute(customers_create_dao, projects_create_dao): - expected_result = { - "name": "Franklin, Mcdonald and Morrison", - "description": "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard.", - "customer_id": "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178", - "project_type_id": "208aadb7-1ec1-4a67-a0b0-e0308d27045b", - "technologies": "['python', 'restplus', 'openapi']", - "status": "active", - "customer_name": "Tucker Inc", - "customer": {"name": None, "description": None}, - "project_type": {"name": None, "description": None}, - "id": "768c924e-4501-457f-99c5-7198440d3c60", - "tenant_id": "e2953984-03e7-4730-be29-1753d24df3b0", - "deleted": "null", - } - @add_custom_attribute('project_type', projects_create_dao) @add_custom_attribute('customer', customers_create_dao) @patch('time_tracker_api.projects.projects_model.ProjectCosmosDBModel') @@ -49,5 +32,7 @@ def fn(project): return project.return_value - assert 'customer' in fn().__dict__ - assert 'project_type' in fn().__dict__ + project = fn().__dict__ + + assert 'customer' in project + assert 'project_type' in project diff --git a/utils/extend_model.py b/utils/extend_model.py index a45f5772..ce39d5b7 100644 --- a/utils/extend_model.py +++ b/utils/extend_model.py @@ -16,12 +16,11 @@ def wrapper(*args, **kwargs): entity_model = func(*args, **kwargs) attribute_id = f"{attr}_id" - try: + if entity_model and attribute_id in entity_model.__dict__: value_id = entity_model.__dict__[attribute_id] - related_entity = current_dao.get(value_id) - setattr(entity_model, attr, related_entity) - except: - print(f"This item isn't related a {attribute_id}") + if value_id: + related_entity = current_dao.get(value_id) + setattr(entity_model, attr, related_entity) return entity_model From d4aef5080c64921cfc8a4b92d101f382280a288a Mon Sep 17 00:00:00 2001 From: jcalarcon98 Date: Mon, 31 May 2021 10:40:34 -0500 Subject: [PATCH 5/5] fix: TT-43 Remove unnecesary commas --- tests/utils/extend_model_test.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/tests/utils/extend_model_test.py b/tests/utils/extend_model_test.py index 20393c7b..09a2733b 100644 --- a/tests/utils/extend_model_test.py +++ b/tests/utils/extend_model_test.py @@ -9,26 +9,20 @@ def test_add_custom_attribute(customers_create_dao, projects_create_dao): @add_custom_attribute('customer', customers_create_dao) @patch('time_tracker_api.projects.projects_model.ProjectCosmosDBModel') def fn(project): - project.return_value.name = ("Franklin, Mcdonald and Morrison",) - project.return_value.description = ( - "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard.", - ) + project.return_value.name = "Franklin, Mcdonald and Morrison" + project.return_value.description = "Include speech feeling court almost country smile economy. True quality mention key. Similar provide yard." project.return_value.customer_id = ( - "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178", + "9afbfa3a-9de4-4b90-a1b7-a53d2c17a178" ) project.return_value.project_type_id = ( - "208aadb7-1ec1-4a67-a0b0-e0308d27045b", - ) - project.return_value.technologies = ( - "['python', 'restplus', 'openapi']", - ) - project.return_value.status = ("active",) - project.return_value.customer_name = ("Tucker Inc",) - project.return_value.id = ("768c924e-4501-457f-99c5-7198440d3c60",) - project.return_value.tenant_id = ( - "e2953984-03e7-4730-be29-1753d24df3b0", + "208aadb7-1ec1-4a67-a0b0-e0308d27045b" ) - project.return_value.deleted = (None,) + project.return_value.technologies = "['python', 'restplus', 'openapi']" + project.return_value.status = "active" + project.return_value.customer_name = "Tucker Inc" + project.return_value.id = "768c924e-4501-457f-99c5-7198440d3c60" + project.return_value.tenant_id = "e2953984-03e7-4730-be29-1753d24df3b0" + project.return_value.deleted = None return project.return_value