<commit_before>"""
Classes and utilities to serialize objects to JSON.

There are two ways of using jsonalize:

* By creating a subclass of JSONAlizable and filling its schema::

    from pyflu import jsonalize

    class MyClass(jsonalize.JSONAlizable):
        
        schema = {
                "foo": None,
                "bar": 123,
            }

* Or by registering a class and an associated ``uncall()`` function. This
function takes an instance of the class in parameter and returns the positional
and keyword arguments needed to recreate this instance::

    from pyflu import jsonalize

    class MyClass:

        def __init__(self, a, b):
            self.a = a
            self.b = b

    def uncall_myclass(obj):
        return (obj.a, obj.b), {}

    jsonalize.register(MyClass, uncall_myclass)

You can then dump and load objects with the usual dump(s) and load(s)
functions::

    obj = MyClass()
    dumped_state = jsonalize.dumps(obj)
    obj2 = jsonalize.loads(dumped_state)
    assert obj == obj2

"""

from copy import deepcopy
import collections
import json
from pyflu.meta.inherit import InheritMeta


class JSONAlizeError(Exception): pass 
class NameConflictError(JSONAlizeError): pass
class SchemaValidationError(JSONAlizeError): pass

class UnregisteredClassError(JSONAlizeError):

    def __init__(self, msg, class_name):
        super(UnregisteredClassError, self).__init__(msg)
        self.class_name = class_name


# Classes and uncall() methods registry
registry = {}
reverse_registry = {}


def register(cls, uncall, name=None, constructor=None):
    """
    Register *cls* for auto serialization.

    *uncall* must be a callable taking the instance to be serialized and
    returning the arguments to pass to the constructor. *uncall* should return
    a tuple containing the positionnal and keyword arguments, in an iterable
    and a mapping.

    If *name* is given it is used to as the code to register *cls*. The default
    is to use *cls*' name.

    You can specify *constructor* to alter the way instances are created from
    the serialized state. It should be a callable and will be passed the
    arguments returned by *uncall* when unserializing this type of object.
    """
    if name is None:
        name = cls.__name__
    # Check for name conflicts
    if name in registry:
        conflicting_cls = registry[name][0]
        raise NameConflictError("can't register class %s "
                "under name '%s', it is already used by %s" % 
                (cls.__name__, name, conflicting_cls.__name__))
    registry[name] = (cls, constructor)
    reverse_registry[cls] = (uncall, name)


class JSONAlizableMeta(InheritMeta):
    """
    Base type of JSONAlizable.

    Inherit and validate schema, register the new class.
    """

    inherited_dicts = ("schema",)
    reserved_names = ("uncall", "schema", "json_class_name", "__args__",
            "__kwargs__", "__class__")
<commit_msg>pyflu: jsonalize: added unregister()<commit_after>"""
Classes and utilities to serialize objects to JSON.

There are two ways of using jsonalize:

* By creating a subclass of JSONAlizable and filling its schema::

    from pyflu import jsonalize

    class MyClass(jsonalize.JSONAlizable):
        
        schema = {
                "foo": None,
                "bar": 123,
            }

* Or by registering a class and an associated ``uncall()`` function. This
function takes an instance of the class in parameter and returns the positional
and keyword arguments needed to recreate this instance::

    from pyflu import jsonalize

    class MyClass:

        def __init__(self, a, b):
            self.a = a
            self.b = b

    def uncall_myclass(obj):
        return (obj.a, obj.b), {}

    jsonalize.register(MyClass, uncall_myclass)

You can then dump and load objects with the usual dump(s) and load(s)
functions::

    obj = MyClass()
    dumped_state = jsonalize.dumps(obj)
    obj2 = jsonalize.loads(dumped_state)
    assert obj == obj2

"""

from copy import deepcopy
import collections
import json
from pyflu.meta.inherit import InheritMeta


class JSONAlizeError(Exception): pass 
class NameConflictError(JSONAlizeError): pass
class SchemaValidationError(JSONAlizeError): pass

class UnregisteredClassError(JSONAlizeError):

    def __init__(self, msg, class_name):
        super(UnregisteredClassError, self).__init__(msg)
        self.class_name = class_name


# Classes and uncall() methods registry
registry = {}
reverse_registry = {}


def register(cls, uncall, name=None, constructor=None):
    """
    Register *cls* for auto serialization.

    *uncall* must be a callable taking the instance to be serialized and
    returning the arguments to pass to the constructor. *uncall* should return
    a tuple containing the positionnal and keyword arguments, in an iterable
    and a mapping.

    If *name* is given it is used to as the code to register *cls*. The default
    is to use *cls*' name.

    You can specify *constructor* to alter the way instances are created from
    the serialized state. It should be a callable and will be passed the
    arguments returned by *uncall* when unserializing this type of object.
    """
    if name is None:
        name = cls.__name__
    # Check for name conflicts
    if name in registry:
        conflicting_cls = registry[name][0]
        raise NameConflictError("can't register class %s "
                "under name '%s', it is already used by %s" % 
                (cls.__name__, name, conflicting_cls.__name__))
    registry[name] = (cls, constructor)
    reverse_registry[cls] = (uncall, name)


def unregister(cls, name=None):
    """
    Unregister *cls*.
    """
    if name is None:
        name = cls.__name__
    del registry[name]
    del reverse_registry[cls]
    


class JSONAlizableMeta(InheritMeta):
    """
    Base type of JSONAlizable.

    Inherit and validate schema, register the new class.
    """

    inherited_dicts = ("schema",)
    reserved_names = ("uncall", "schema", "json_class_name", "__args__",
            "__kwargs__", "__class__")

{'fraction_non_alphanumeric': 0.08215808461790751, 'fraction_numerical': 0.0019678583142013774, 'mean_word_length': 3.0257425742574258, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3173378', 'n_tokens_mistral': 1764, 'n_tokens_neox': 1703, 'n_words': 650}