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

Clean code #7

Merged
merged 5 commits into from
Apr 29, 2020
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
22 changes: 1 addition & 21 deletions sqlalchemy_api_handler/api_errors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import re


class ApiErrors(Exception):
def __init__(self, errors: dict = None):
self.errors = errors if errors else {}
Expand Down Expand Up @@ -41,24 +42,3 @@ def __str__(self):
return json.dumps(self.errors, indent=2)

status_code = None

class DateTimeCastError(ApiErrors):
pass

class DecimalCastError(ApiErrors):
pass

class EmptyFiltersError(ApiErrors):
pass

class ForbiddenError(ApiErrors):
pass

class ResourceGoneError(ApiErrors):
pass

class ResourceNotFoundError(ApiErrors):
pass

class UuidCastError(ApiErrors):
pass
16 changes: 6 additions & 10 deletions sqlalchemy_api_handler/bases/delete.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
from sqlalchemy_api_handler.bases.accessor import Accessor

class DeletedRecordException(Exception):
pass


class Delete(Accessor):

def delete(self):
Accessor.get_db().session.delete(self)
return self

@staticmethod
def delete_objects(*objects):
delete_objects = list(map(Delete.delete, objects))
return delete_objects
def delete(*entities):
db = Accessor.get_db()
for entity in entities:
db.session.delete(entity)

db.session.commit()
36 changes: 36 additions & 0 deletions sqlalchemy_api_handler/bases/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,39 @@ def errors(self):
and not isinstance(val, int):
api_errors.add_error(key, 'doit être un nombre')
return api_errors


class DateTimeCastError(ApiErrors):
pass


class DecimalCastError(ApiErrors):
pass


class EmptyFilterError(ApiErrors):
pass


class ForbiddenError(ApiErrors):
pass


class NotSoftDeletableMixinException(ApiErrors):
pass


class ResourceGoneError(ApiErrors):
pass


class SoftDeletedRecordException(ApiErrors):
pass


class ResourceNotFoundError(ApiErrors):
pass


class UuidCastError(ApiErrors):
pass
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,29 @@
Numeric, \
String
from sqlalchemy.dialects.postgresql import UUID

from typing import List, Any, Iterable, Set

from sqlalchemy_api_handler.api_errors import DateTimeCastError, \
DecimalCastError, \
UuidCastError, \
ResourceNotFoundError, \
EmptyFiltersError

from sqlalchemy_api_handler.bases.delete import Delete
from sqlalchemy_api_handler.bases.errors import DateTimeCastError, \
DecimalCastError, \
EmptyFilterError, \
ResourceNotFoundError, \
UuidCastError
from sqlalchemy_api_handler.bases.soft_delete import SoftDelete
from sqlalchemy_api_handler.utils.date import match_format
from sqlalchemy_api_handler.utils.human_ids import dehumanize, is_id_column


class Populate(


class Modify(
Delete,
SoftDelete
):
def __init__(self, **options):
self.populate_from_dict(options)
self.modify(options)

def populate_from_dict(self, datum: dict, skipped_keys: List[str] = []):
def modify(self, datum: dict, skipped_keys: List[str] = []):
self.check_not_soft_deleted()
columns = self.__mapper__.columns
columns_keys_to_populate = self._get_column_keys_to_populate(
Expand Down Expand Up @@ -71,12 +71,14 @@ def populate_from_dict(self, datum: dict, skipped_keys: List[str] = []):
)
setattr(self, key, value)

return self

@staticmethod
def _get_column_keys_to_populate(column_keys: Set[str], data: dict, skipped_keys: Iterable[str]) -> Set[str]:
requested_columns_to_update = set(data.keys())
requested_columns_to_modify = set(data.keys())
forbidden_columns = set(['id', 'deleted'] + skipped_keys)
allowed_columns_to_update = requested_columns_to_update - forbidden_columns
keys_to_populate = column_keys.intersection(allowed_columns_to_update)
allowed_columns_to_modify = requested_columns_to_modify - forbidden_columns
keys_to_populate = column_keys.intersection(allowed_columns_to_modify)
return keys_to_populate

@staticmethod
Expand All @@ -92,11 +94,11 @@ def _get_model_instance(value, model):
for (index, column) in enumerate(primary_key_columns)
]
model_instance = model.query.get(pks)
model_instance.populate_from_dict(value)
model_instance.modify(value)
return model_instance
return model(**value)
elif hasattr(value, '__iter__'):
return list(map(lambda obj: Populate._get_model_instance(obj, model), value))
return list(map(lambda obj: Modify._get_model_instance(obj, model), value))
return value

def _try_to_set_attribute_with_deserialized_datetime(self, col, key, value):
Expand Down Expand Up @@ -131,20 +133,11 @@ def _get_filter_dict(model, content, filter_keys):
filter_keys = [filter_keys]
return dict([(key, value) for key, value in content.items() if key in filter_keys])

@classmethod
def _update(model, object, content):
object.populate_from_dict(content)
return object

@classmethod
def _create(model, content):
return model(**content)

@classmethod
def find(model, content, filter_keys):
filters = model._get_filter_dict(content, filter_keys)
if not filters:
errors = EmptyFiltersError()
errors = EmptyFilterError()
filters = ", ".join(filter_keys) if isinstance(filter_keys, list) else filter_keys
errors.add_error('_get_filter_dict', 'None of filters found among: ' + filters)
raise errors
Expand All @@ -158,24 +151,24 @@ def find_or_create(model, content, filter_keys):
existing = model.find(content, filter_keys)
if existing:
return existing
return model._create(content)
return model(**content)

@classmethod
def find_and_update(model, content, filter_keys):
def find_and_modify(model, content, filter_keys):
existing = model.find(content, filter_keys)
if not existing:
errors = ResourceNotFoundError()
filters = model._get_filter_dict(content, filter_keys)
errors.add_error('find_and_update', 'No ressource found with {} '.format(json.dumps(filters)))
errors.add_error('find_and_modify', 'No ressource found with {} '.format(json.dumps(filters)))
raise errors
return model._update(existing, content)
return model.modify(existing, content)

@classmethod
def create_or_update(model, content, filter_keys):
def create_or_modify(model, content, filter_keys):
existing = model.find(content, filter_keys)
if existing:
return model._update(existing, content)
return model._create(content)
return model.modify(existing, content)
return model(**content)


def _dehumanize_if_needed(column, value: Any) -> Any:
Expand Down
23 changes: 12 additions & 11 deletions sqlalchemy_api_handler/bases/save.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@

from sqlalchemy_api_handler.api_errors import ApiErrors
from sqlalchemy_api_handler.bases.errors import Errors
from sqlalchemy_api_handler.bases.populate import Populate
from sqlalchemy_api_handler.bases.modify import Modify

class Save(Populate, Errors):

class Save(Modify, Errors):

@staticmethod
def save(*objects):
if not objects:
def save(*entities):
if not entities:
return None

db = Save.get_db()

# CUMULATE ERRORS IN ONE SINGLE API ERRORS DURING ADD TIME
api_errors = ApiErrors()
for obj in objects:
for entity in entities:
with db.session.no_autoflush:
obj_api_errors = obj.errors()
if obj_api_errors.errors.keys():
api_errors.errors.update(obj_api_errors.errors)
entity_api_errors = entity.errors()
if entity_api_errors.errors.keys():
api_errors.errors.update(entity_api_errors.errors)
else:
db.session.add(obj)
db.session.add(entity)

# CHECK BEFORE COMMIT
if api_errors.errors.keys():
Expand All @@ -39,8 +40,8 @@ def save(*objects):
db.session.rollback()
raise api_errors
except InternalError as ie:
for obj in objects:
api_errors.add_error(*obj.restize_internal_error(ie))
for entity in entities:
api_errors.add_error(*entity.restize_internal_error(ie))
db.session.rollback()
raise api_errors
except TypeError as te:
Expand Down
29 changes: 13 additions & 16 deletions sqlalchemy_api_handler/bases/soft_delete.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import inspect

class NotSoftDeletableMixinException(Exception):
pass


class SoftDeletedRecordException(Exception):
pass
from sqlalchemy_api_handler.bases.errors import NotSoftDeletableMixinException, \
SoftDeletedRecordException


class SoftDelete():
Expand All @@ -15,21 +11,22 @@ def __init__(self):

def check_not_soft_deleted(self):
if self.is_soft_deleted():
raise SoftDeletedRecordException
api_errors = SoftDeletedRecordException()
api_errors.add_error('check_not_soft_deleted', 'Entity already soft deleted')
raise api_errors

def is_soft_deleted(self):
classes = inspect.getmro(type(self))
if 'SoftDeletableMixin' in [cl.__name__ for cl in classes]:
return self.isSoftDeleted
return False

def soft_delete(self):
classes = inspect.getmro(type(self))
if 'SoftDeletableMixin' not in [cl.__name__ for cl in classes]:
raise NotSoftDeletableMixinException
self.isSoftDeleted = True
return self

@staticmethod
def soft_delete_objects(*objects):
return list(map(SoftDelete.soft_delete, objects))
def soft_delete(*entities):
for entity in entities:
classes = inspect.getmro(type(entity))
if 'SoftDeletableMixin' not in [cl.__name__ for cl in classes]:
api_errors = NotSoftDeletableMixinException()
api_errors.add_error('soft_delete', 'cannot soft delete this entity')
raise api_errors
entity.isSoftDeleted = True
Loading