diff --git a/tests/time_tracker_api/api_test.py b/tests/time_tracker_api/api_test.py index a9af25da..6f9ad353 100644 --- a/tests/time_tracker_api/api_test.py +++ b/tests/time_tracker_api/api_test.py @@ -22,3 +22,24 @@ def test_create_attributes_filter_with_valid_attribute_should_succeed(): assert filter is not None assert type(filter) is RequestParser + + +def test_remove_required_constraint(): + from time_tracker_api.api import remove_required_constraint + from flask_restplus import fields + from flask_restplus import Namespace + + ns = Namespace('todos', description='Namespace for testing') + sample_model = ns.model('Todo', { + 'id': fields.Integer(readonly=True, description='The task unique identifier'), + 'task': fields.String(required=True, description='The task details'), + 'done': fields.Boolean(required=False, description='Has it being done or not') + }) + + new_model = remove_required_constraint(sample_model) + + assert new_model is not sample_model + + for attrib in sample_model: + assert new_model[attrib].required is False, "No attribute should be required" + assert new_model[attrib] is not sample_model[attrib], "No attribute should be required" diff --git a/time_tracker_api/__init__.py b/time_tracker_api/__init__.py index 848a01ac..b23a3c6b 100644 --- a/time_tracker_api/__init__.py +++ b/time_tracker_api/__init__.py @@ -38,8 +38,8 @@ def init_app(app: Flask): from commons.data_access_layer.database import init_app as init_database init_database(app) - from time_tracker_api.api import api - api.init_app(app) + from time_tracker_api.api import init_app + init_app(app) if app.config.get('DEBUG'): app.logger.setLevel(logging.INFO) diff --git a/time_tracker_api/activities/activities_namespace.py b/time_tracker_api/activities/activities_namespace.py index ba783bc2..1dffc3dc 100644 --- a/time_tracker_api/activities/activities_namespace.py +++ b/time_tracker_api/activities/activities_namespace.py @@ -3,7 +3,7 @@ from flask_restplus._http import HTTPStatus from time_tracker_api.activities.activities_model import create_dao -from time_tracker_api.api import common_fields, api +from time_tracker_api.api import common_fields, api, remove_required_constraint faker = Faker() @@ -68,7 +68,7 @@ def get(self, id): return activity_dao.get(id) @ns.doc('update_activity') - @ns.expect(activity_input) + @ns.expect(remove_required_constraint(activity_input)) @ns.response(HTTPStatus.BAD_REQUEST, 'Invalid format or structure of the activity') @ns.response(HTTPStatus.CONFLICT, 'An activity already exists with this new data') @ns.marshal_with(activity) diff --git a/time_tracker_api/api.py b/time_tracker_api/api.py index 089a04d6..59b8b3cd 100644 --- a/time_tracker_api/api.py +++ b/time_tracker_api/api.py @@ -1,6 +1,6 @@ from azure.cosmos.exceptions import CosmosResourceExistsError, CosmosResourceNotFoundError, CosmosHttpResponseError from faker import Faker -from flask import current_app as app +from flask import current_app as app, Flask from flask_restplus import Api, fields, Model from flask_restplus import namespace from flask_restplus._http import HTTPStatus @@ -22,7 +22,14 @@ ) -# Filters +def remove_required_constraint(model: Model): + result = model.resolved + for attrib in result: + result[attrib].required = False + + return result + + def create_attributes_filter(ns: namespace, model: Model, filter_attrib_names: list) -> RequestParser: attribs_parser = ns.parser() model_attributes = model.resolved @@ -72,26 +79,30 @@ def __init__(self, *args, **kwargs): ), } -# APIs -from time_tracker_api.projects import projects_namespace -api.add_namespace(projects_namespace.ns) +def init_app(app: Flask): + api.init_app(app) + + from time_tracker_api.projects import projects_namespace + + api.add_namespace(projects_namespace.ns) + + from time_tracker_api.activities import activities_namespace -from time_tracker_api.activities import activities_namespace + api.add_namespace(activities_namespace.ns) -api.add_namespace(activities_namespace.ns) + from time_tracker_api.time_entries import time_entries_namespace -from time_tracker_api.time_entries import time_entries_namespace + api.add_namespace(time_entries_namespace.ns) -api.add_namespace(time_entries_namespace.ns) + from time_tracker_api.project_types import project_types_namespace -from time_tracker_api.project_types import project_types_namespace + api.add_namespace(project_types_namespace.ns) -api.add_namespace(project_types_namespace.ns) + from time_tracker_api.customers import customers_namespace -from time_tracker_api.customers import customers_namespace + api.add_namespace(customers_namespace.ns) -api.add_namespace(customers_namespace.ns) """ Error handlers diff --git a/time_tracker_api/customers/customers_namespace.py b/time_tracker_api/customers/customers_namespace.py index f455cb22..d38f4566 100644 --- a/time_tracker_api/customers/customers_namespace.py +++ b/time_tracker_api/customers/customers_namespace.py @@ -2,7 +2,7 @@ from flask_restplus import Resource, fields from flask_restplus._http import HTTPStatus -from time_tracker_api.api import common_fields, api +from time_tracker_api.api import common_fields, api, remove_required_constraint from time_tracker_api.customers.customers_model import create_dao faker = Faker() @@ -74,7 +74,7 @@ def get(self, id): @ns.response(HTTPStatus.BAD_REQUEST, 'Invalid format or structure ' 'of the attributes of the customer') @ns.response(HTTPStatus.CONFLICT, 'A customer already exists with this new data') - @ns.expect(customer_input) + @ns.expect(remove_required_constraint(customer_input)) @ns.marshal_with(customer) def put(self, id): """Update a customer""" diff --git a/time_tracker_api/project_types/project_types_namespace.py b/time_tracker_api/project_types/project_types_namespace.py index 8e1e759e..7d717b7f 100644 --- a/time_tracker_api/project_types/project_types_namespace.py +++ b/time_tracker_api/project_types/project_types_namespace.py @@ -2,7 +2,7 @@ from flask_restplus import Resource, fields from flask_restplus._http import HTTPStatus -from time_tracker_api.api import common_fields, create_attributes_filter, api, UUID +from time_tracker_api.api import common_fields, create_attributes_filter, api, UUID, remove_required_constraint from time_tracker_api.project_types.project_types_model import create_dao faker = Faker() @@ -94,7 +94,7 @@ def get(self, id): @ns.response(HTTPStatus.BAD_REQUEST, 'Invalid format or structure ' 'of the attributes of the project type') @ns.response(HTTPStatus.CONFLICT, 'A project type already exists with this new data') - @ns.expect(project_type_input) + @ns.expect(remove_required_constraint(project_type_input)) @ns.marshal_with(project_type) def put(self, id): """Update a project type""" diff --git a/time_tracker_api/projects/projects_namespace.py b/time_tracker_api/projects/projects_namespace.py index 634af17e..9c39e53e 100644 --- a/time_tracker_api/projects/projects_namespace.py +++ b/time_tracker_api/projects/projects_namespace.py @@ -2,7 +2,7 @@ from flask_restplus import Resource, fields from flask_restplus._http import HTTPStatus -from time_tracker_api.api import common_fields, create_attributes_filter, UUID, api +from time_tracker_api.api import common_fields, create_attributes_filter, UUID, api, remove_required_constraint from time_tracker_api.projects.projects_model import create_dao faker = Faker() @@ -94,7 +94,7 @@ def get(self, id): @ns.response(HTTPStatus.BAD_REQUEST, 'Invalid format or structure ' 'of the attributes of the project') @ns.response(HTTPStatus.CONFLICT, 'A project already exists with this new data') - @ns.expect(project_input) + @ns.expect(remove_required_constraint(project_input)) @ns.marshal_with(project) def put(self, id): """Update a project""" diff --git a/time_tracker_api/time_entries/time_entries_namespace.py b/time_tracker_api/time_entries/time_entries_namespace.py index 87ba7f46..9df422a4 100644 --- a/time_tracker_api/time_entries/time_entries_namespace.py +++ b/time_tracker_api/time_entries/time_entries_namespace.py @@ -6,7 +6,8 @@ from commons.data_access_layer.cosmos_db import current_datetime, datetime_str, current_datetime_str from commons.data_access_layer.database import COMMENTS_MAX_LENGTH -from time_tracker_api.api import common_fields, create_attributes_filter, api, UUID +from time_tracker_api.api import common_fields, create_attributes_filter, api, UUID, NullableString, \ + remove_required_constraint from time_tracker_api.time_entries.time_entries_model import create_dao faker = Faker() @@ -48,7 +49,7 @@ description='When the user ended doing this activity', example=current_datetime_str(), ), - 'uri': fields.String( + 'uri': NullableString( title='Uniform Resource identifier', description='Either identifier or locator of a resource in the Internet that helps to understand' ' what this time entry was about. For example, A Jira ticket, a Github issue, a Google document.', @@ -146,7 +147,7 @@ def get(self, id): 'of the attributes of the time entry') @ns.response(HTTPStatus.CONFLICT, 'A time entry already exists with this new data or there' ' is a bad reference for the project or activity') - @ns.expect(time_entry_input) + @ns.expect(remove_required_constraint(time_entry_input)) @ns.marshal_with(time_entry) def put(self, id): """Update a time entry"""