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

feat: checks #412

Merged
merged 17 commits into from
Nov 30, 2022
2 changes: 1 addition & 1 deletion .drone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ steps:
- cd ../tests
- coverage run --source dessia_common ci_tests.py
- coverage json
- python coverage.py
- coverage report
- python coverage.py

- name: develop
image: python:3.8
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ suggestion-mode=yes

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
unsafe-load-any-extension=yes


[MESSAGES CONTROL]
Expand Down
8 changes: 3 additions & 5 deletions code_pylint.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@
'consider-using-f-string': 1,
'arguments-differ': 2,
'no-member': 3,
'too-many-locals': 10, # Reduce by dropping vectored objects
'too-many-locals': 12, # Reduce by dropping vectored objects
'too-many-branches': 13,
'wrong-import-order': 1,
'unused-argument': 6,
'unused-argument': 7,
'cyclic-import': 14,
'no-self-use': 6,
'trailing-whitespace': 11,
'empty-docstring': 1,
'missing-module-docstring': 1,
'too-many-arguments': 22,
'too-few-public-methods': 4,
'too-few-public-methods': 19, # Not really important
'unnecessary-comprehension': 1,
'no-value-for-parameter': 2,
'too-many-return-statements': 10,
Expand All @@ -60,8 +60,6 @@
'use-maxsplit-arg': 1,
'duplicate-code': 1,
'too-many-lines': 1
# No tolerance errors

}

print('pylint version: ', __version__)
Expand Down
84 changes: 84 additions & 0 deletions dessia_common/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Nov 8 19:39:07 2022

@author: steven
"""

import sys
from typing import Dict, Any
from dessia_common.typings import JsonSerializable
from dessia_common.utils.serialization import dict_to_object, serialize_dict_with_pointers, serialize_dict


class SerializableObject:
"""
Serialization capabilities of Dessia Object
"""
_non_serializable_attributes = []

def base_dict(self):
"""
A base dict for to_dict: put name, object class and version in a dict
"""
package_name = self.__module__.split('.', maxsplit=1)[0]
if package_name in sys.modules:
package = sys.modules[package_name]
if hasattr(package, '__version__'):
package_version = package.__version__
else:
package_version = None
else:
package_version = None

object_class = self.__module__ + '.' + self.__class__.__name__
dict_ = {'object_class': object_class}
if package_version:
dict_['package_version'] = package_version
return dict_

def _serializable_dict(self):
"""
Returns a dict of attribute_name, values (still python, not serialized)
Keys are filtered with non serializable attributes controls
"""

dict_ = {k: v for k, v in self.__dict__.items()
if k not in self._non_serializable_attributes and not k.startswith('_')}
return dict_

def to_dict(self, use_pointers: bool = True, memo=None, path: str = '#') -> JsonSerializable:
"""
Generic to_dict method
"""
if memo is None:
memo = {}

# Default to dict
serialized_dict = self.base_dict()
dict_ = self._serializable_dict()
if use_pointers:
serialized_dict.update(serialize_dict_with_pointers(dict_, memo, path)[0])
else:
serialized_dict.update(serialize_dict(dict_))

return serialized_dict

@classmethod
def dict_to_object(cls, dict_: JsonSerializable, force_generic: bool = False, global_dict=None,
pointers_memo: Dict[str, Any] = None, path: str = '#') -> 'SerializableObject':
"""
Generic dict_to_object method
"""
if 'object_class' in dict_:
obj = dict_to_object(dict_=dict_, force_generic=force_generic, global_dict=global_dict,
pointers_memo=pointers_memo, path=path)
return obj

if cls is not SerializableObject:
obj = dict_to_object(dict_=dict_, class_=cls, force_generic=force_generic, global_dict=global_dict,
pointers_memo=pointers_memo, path=path)
return obj

raise NotImplementedError('No object_class in dict')
114 changes: 114 additions & 0 deletions dessia_common/checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
General checks & checklists
"""

from dessia_common.core import SerializableObject


LEVEL_TO_INT = {'debug': 0, 'info': 1, 'warning': 2, 'error': 3}


class PassedCheck(SerializableObject):
level = 'info'

def __init__(self, message: str):
self.message = message

def __repr__(self):
return f'[{self.level}]: {self.__class__.__name__} {self.message}'


class CheckWarning(PassedCheck):
level = 'warning'


class FailedCheck(PassedCheck):
level = 'error'


class BadType(FailedCheck):
pass


class GeometricInconsistance(FailedCheck):
pass


class CheckList(SerializableObject):
def __init__(self, checks):
self.checks = checks

def __repr__(self):
rep = f'Check list containing {len(self.checks)} checks:\n'
for check_idx, check in enumerate(self.checks):
rep += f'Check {check_idx+1}: {check}\n'
return rep

def __add__(self, other_checklist):
return self.__class__(self.checks + other_checklist.checks)

def checks_above_level(self, level='error'):
checks = []
for check in self.checks:
if LEVEL_TO_INT[check.level] >= LEVEL_TO_INT[level]:
checks.append(check)
return checks

def raise_if_above_level(self, level='error'):
for check in self.checks_above_level(level=level):
raise ValueError(f'Check: {check} is above level "{level}"')


def is_int(value, level='error'):
if not isinstance(value, int):
return CheckList([FailedCheck(f'Value {value} is not an int')])
if level == 'info':
return CheckList([PassedCheck(f'value {value} is an int')])
return CheckList([])


def is_float(value, level='error'):
"""

:param value: DESCRIPTION
:type value: TYPE
:param level: DESCRIPTION, defaults to 'error'
:type level: TYPE, optional
:return: DESCRIPTION
:rtype: TYPE

"""
if not isinstance(value, float):
return CheckList([FailedCheck(f'Value {value} is not a float')])
if level == 'info':
return CheckList([PassedCheck(f'value {value} is a float')])
return CheckList([])


def is_str(value, level='error'):
if not isinstance(value, str):
return CheckList([FailedCheck(f'Value {value} is not a str')])
if level == 'info':
return CheckList([PassedCheck(f'value {value} is a str')])
return CheckList([])


def type_check(value, expected_type, level='error'):
"""
This is experimental!
"""
if expected_type is int:
return is_int(value)
if expected_type is float:
return is_float(value)
if expected_type is str:
return is_str(value)

if not isinstance(value, expected_type):
return CheckList([FailedCheck(f'Value {value} is not of type {expected_type}')])
if level == 'info':
return CheckList([PassedCheck(f'value {value} is of expected type {expected_type}')])

return CheckList([])
Loading