Skip to content

Commit

Permalink
const as UPPERCASE, logger for wanrings, documentation
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck committed Sep 27, 2023
1 parent e1adaf3 commit d5f59ef
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 34 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.venv']

# -- Options for HTML output -------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ See also:
getting-started
customising-structure
formatters
logging
support
changelog

Expand Down
42 changes: 42 additions & 0 deletions docs/logging.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Logging
====================================================

This library utilizes an own instance of `Logger`_, which you may access and add handlers to.

.. _logger: https://docs.python.org/3/library/logging.html#logger-objects


.. code-block:: python
:caption: Example: send all logs messages to the console
import sys
import logging
import serializable
my_log_handler = logging.StreamHandler(sys.stderr)
my_log_handler.setLevel(logging.DEBUG)
my_log_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
serializable.LOGGER.addHandler(my_log_handler)
print(serializable.LOGGER.name)
@serializable.serializable_class
class Chapter:
def __init__(self, *, number: int, title: str) -> None:
self._number = number
self._title = title
@property
def number(self) -> int:
return self._number
@property
def title(self) -> str:
return self._title
moby_dick_c1 = Chapter(number=1, title='Loomings')
print(moby_dick_c1.as_json())
57 changes: 27 additions & 30 deletions serializable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) Paul Horton. All Rights Reserved.

import enum
import functools
import inspect
import json
import logging
import os
import re
import typing # noqa: F401
import warnings
from copy import copy
from decimal import Decimal
from io import StringIO, TextIOWrapper
Expand All @@ -42,9 +41,7 @@

from .formatters import BaseNameFormatter, CurrentFormatter
from .helpers import BaseHelper

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
from .logging import LOGGER

_F = TypeVar("_F", bound=Callable[..., Any])
_T = TypeVar('_T', bound='_Klass')
Expand Down Expand Up @@ -223,7 +220,7 @@ def _as_json(self: _T, view_: Optional[Type[Any]] = None) -> str:
Internal function that is injected into Classes that are annotated for serialization and deserialization by
``serializable``.
"""
logger.debug(f'Dumping {self} to JSON with view: {view_}...')
LOGGER.debug(f'Dumping {self} to JSON with view: {view_}...')
return json.dumps(self, cls=_SerializableJsonEncoder, view_=view_)


Expand All @@ -232,13 +229,13 @@ def _from_json(cls: Type[_T], data: Dict[str, Any]) -> object:
Internal function that is injected into Classes that are annotated for serialization and deserialization by
``serializable``.
"""
logger.debug(f'Rendering JSON to {cls}...')
LOGGER.debug(f'Rendering JSON to {cls}...')
klass_qualified_name = f'{cls.__module__}.{cls.__qualname__}'
klass = ObjectMetadataLibrary.klass_mappings.get(klass_qualified_name, None)
klass_properties = ObjectMetadataLibrary.klass_property_mappings.get(klass_qualified_name, {})

if klass is None:
warnings.warn(f'{klass_qualified_name} is not a known serializable class', stacklevel=2)
LOGGER.warning(f'{klass_qualified_name} is not a known serializable class', stacklevel=2)
return None

if len(klass_properties) == 1:
Expand All @@ -251,7 +248,7 @@ def _from_json(cls: Type[_T], data: Dict[str, Any]) -> object:
for k, v in data.items():
decoded_k = CurrentFormatter.formatter.decode(property_name=k)
if decoded_k in klass.ignore_during_deserialization:
logger.debug(f'Ignoring {k} when deserializing {cls.__module__}.{cls.__qualname__}')
LOGGER.debug(f'Ignoring {k} when deserializing {cls.__module__}.{cls.__qualname__}')
del _data[k]
continue

Expand All @@ -264,7 +261,7 @@ def _from_json(cls: Type[_T], data: Dict[str, Any]) -> object:
new_key = decoded_k

if new_key is None:
logger.error(
LOGGER.error(
f'Unexpected key {k}/{decoded_k} in data being serialized to {cls.__module__}.{cls.__qualname__}'
)
raise ValueError(
Expand Down Expand Up @@ -302,22 +299,22 @@ def _from_json(cls: Type[_T], data: Dict[str, Any]) -> object:
else:
_data[k] = prop_info.concrete_type(v)
except AttributeError as e:
logger.error(f'There was an AttributeError deserializing JSON to {cls}.{os.linesep}'
LOGGER.error(f'There was an AttributeError deserializing JSON to {cls}.{os.linesep}'
f'The Property is: {prop_info}{os.linesep}'
f'The Value was: {v}{os.linesep}'
f'Exception: {e}{os.linesep}')
raise AttributeError(
f'There was an AttributeError deserializing JSON to {cls} the Property {prop_info}: {e}'
)

logger.debug(f'Creating {cls} from {_data}')
LOGGER.debug(f'Creating {cls} from {_data}')

return cls(**_data)


def _as_xml(self: _T, view_: Optional[Type[_T]] = None, as_string: bool = True, element_name: Optional[str] = None,
xmlns: Optional[str] = None) -> Union[Element, str]:
logger.debug(f'Dumping {self} to XML with view {view_}...')
LOGGER.debug(f'Dumping {self} to XML with view {view_}...')

this_e_attributes = {}
klass_qualified_name = f'{self.__module__}.{self.__class__.__qualname__}'
Expand Down Expand Up @@ -441,10 +438,10 @@ def _as_xml(self: _T, view_: Optional[Type[_T]] = None, as_string: bool = True,

def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, Element],
default_namespace: Optional[str] = None) -> object:
logger.debug(f'Rendering XML from {type(data)} to {cls}...')
LOGGER.debug(f'Rendering XML from {type(data)} to {cls}...')
klass = ObjectMetadataLibrary.klass_mappings.get(f'{cls.__module__}.{cls.__qualname__}', None)
if klass is None:
warnings.warn(f'{cls.__module__}.{cls.__qualname__} is not a known serializable class', stacklevel=2)
LOGGER.warning(f'{cls.__module__}.{cls.__qualname__} is not a known serializable class', stacklevel=2)
return None

klass_properties = ObjectMetadataLibrary.klass_property_mappings.get(f'{cls.__module__}.{cls.__qualname__}', {})
Expand All @@ -467,7 +464,7 @@ def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, Element],
for k, v in data.attrib.items():
decoded_k = CurrentFormatter.formatter.decode(property_name=k)
if decoded_k in klass.ignore_during_deserialization:
logger.debug(f'Ignoring {decoded_k} when deserializing {cls.__module__}.{cls.__qualname__}')
LOGGER.debug(f'Ignoring {decoded_k} when deserializing {cls.__module__}.{cls.__qualname__}')
continue

if decoded_k not in klass_properties:
Expand Down Expand Up @@ -501,7 +498,7 @@ def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, Element],

decoded_k = CurrentFormatter.formatter.decode(property_name=child_e_tag_name)
if decoded_k in klass.ignore_during_deserialization:
logger.debug(f'Ignoring {decoded_k} when deserializing {cls.__module__}.{cls.__qualname__}')
LOGGER.debug(f'Ignoring {decoded_k} when deserializing {cls.__module__}.{cls.__qualname__}')
continue

if decoded_k not in klass_properties:
Expand All @@ -525,7 +522,7 @@ def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, Element],

try:

logger.debug(f'Handling {prop_info}')
LOGGER.debug(f'Handling {prop_info}')

if prop_info.is_array and prop_info.xml_array_config:
array_type, nested_name = prop_info.xml_array_config
Expand Down Expand Up @@ -574,15 +571,15 @@ def _from_xml(cls: Type[_T], data: Union[TextIOWrapper, Element],
else:
_data[decoded_k] = prop_info.concrete_type(child_e.text)
except AttributeError as e:
logger.error(f'There was an AttributeError deserializing JSON to {cls}.{os.linesep}'
LOGGER.error(f'There was an AttributeError deserializing JSON to {cls}.{os.linesep}'
f'The Property is: {prop_info}{os.linesep}'
f'The Value was: {v}{os.linesep}'
f'Exception: {e}{os.linesep}')
raise AttributeError(
f'There was an AttributeError deserializing XML to {cls} the Property {prop_info}: {e}'
)

logger.debug(f'Creating {cls} from {_data}')
LOGGER.debug(f'Creating {cls} from {_data}')

if len(_data) == 0:
return None
Expand Down Expand Up @@ -966,7 +963,7 @@ def register_klass(cls, klass: _T, custom_name: Optional[str],

qualified_class_name = f'{klass.__module__}.{klass.__qualname__}'
cls.klass_property_mappings.update({qualified_class_name: {}})
logger.debug(f'Registering Class {qualified_class_name} with custom name {custom_name}')
LOGGER.debug(f'Registering Class {qualified_class_name} with custom name {custom_name}')
for name, o in inspect.getmembers(klass, ObjectMetadataLibrary.is_property):
qualified_property_name = f'{qualified_class_name}.{name}'
prop_arg_specs = inspect.getfullargspec(o.fget)
Expand Down Expand Up @@ -1123,7 +1120,7 @@ def type_mapping(type_: _T) -> Callable[[_F], _F]:
"""

def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} with custom type: {type_}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} with custom type: {type_}')
ObjectMetadataLibrary.register_property_type_mapping(
qual_name=f'{f.__module__}.{f.__qualname__}', mapped_type=type_
)
Expand All @@ -1139,7 +1136,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def include_none(view_: Optional[Type[_T]] = None, none_value: Optional[Any] = None) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} to include None for view: {view_}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} to include None for view: {view_}')
ObjectMetadataLibrary.register_property_include_none(
qual_name=f'{f.__module__}.{f.__qualname__}', view_=view_, none_value=none_value
)
Expand All @@ -1155,7 +1152,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def json_name(name: str) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} with JSON name: {name}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} with JSON name: {name}')
ObjectMetadataLibrary.register_custom_json_property_name(
qual_name=f'{f.__module__}.{f.__qualname__}', json_property_name=name
)
Expand All @@ -1171,7 +1168,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def string_format(format_: str) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} with String Format: {format_}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} with String Format: {format_}')
ObjectMetadataLibrary.register_custom_string_format(
qual_name=f'{f.__module__}.{f.__qualname__}', string_format=format_
)
Expand All @@ -1187,7 +1184,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def view(view_: ViewType) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} with View: {view_}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} with View: {view_}')
ObjectMetadataLibrary.register_property_view(
qual_name=f'{f.__module__}.{f.__qualname__}', view_=view_
)
Expand All @@ -1203,7 +1200,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def xml_attribute() -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} as XML attribute')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} as XML attribute')
ObjectMetadataLibrary.register_xml_property_attribute(qual_name=f'{f.__module__}.{f.__qualname__}')

@functools.wraps(f)
Expand All @@ -1217,7 +1214,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def xml_array(array_type: XmlArraySerializationType, child_name: str) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} as XML Array: {array_type}:{child_name}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} as XML Array: {array_type}:{child_name}')
ObjectMetadataLibrary.register_xml_property_array_config(
qual_name=f'{f.__module__}.{f.__qualname__}', array_type=array_type, child_name=child_name
)
Expand All @@ -1233,7 +1230,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def xml_name(name: str) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} with XML name: {name}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} with XML name: {name}')
ObjectMetadataLibrary.register_custom_xml_property_name(
qual_name=f'{f.__module__}.{f.__qualname__}', xml_property_name=name
)
Expand All @@ -1249,7 +1246,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:

def xml_sequence(sequence: int) -> Callable[[_F], _F]:
def outer(f: _F) -> _F:
logger.debug(f'Registering {f.__module__}.{f.__qualname__} with XML sequence: {sequence}')
LOGGER.debug(f'Registering {f.__module__}.{f.__qualname__} with XML sequence: {sequence}')
ObjectMetadataLibrary.register_xml_property_sequence(
qual_name=f'{f.__module__}.{f.__qualname__}', sequence=sequence
)
Expand Down
7 changes: 4 additions & 3 deletions serializable/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) Paul Horton. All Rights Reserved.
import re
import warnings
from abc import ABC, abstractmethod
from datetime import date, datetime
from typing import Any

from .logging import LOGGER


class BaseHelper(ABC):

Expand Down Expand Up @@ -75,13 +76,13 @@ def deserialize(cls, o: object) -> date:

if str(o).endswith('Z'):
o = str(o)[:-1]
warnings.warn(
LOGGER.warning(
'Potential data loss will occur: dates with timezones not supported in Python', UserWarning,
stacklevel=2
)
if '+' in str(o):
o = str(o)[:str(o).index('+')]
warnings.warn(
LOGGER.warning(
'Potential data loss will occur: dates with timezones not supported in Python', UserWarning,
stacklevel=2
)
Expand Down
23 changes: 23 additions & 0 deletions serializable/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# encoding: utf-8

# This file is part of py-serializable
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
# Copyright (c) Paul Horton. All Rights Reserved.

import logging

LOGGER = logging.getLogger(f'{__name__}.LOGGER')
LOGGER.setLevel(logging.DEBUG)

0 comments on commit d5f59ef

Please sign in to comment.