mirror of
https://github.com/modelscope/modelscope.git
synced 2026-09-02 03:59:31 +02:00
https://www.modelscope.cn/models/damo/multi_modal_clip_vtretrieval_prost/summary Link: https://code.alibaba-inc.com/Ali-MaaS/MaaS-lib/codereview/13487540
468 lines
14 KiB
Python
468 lines
14 KiB
Python
# Modify from: https://github.com/randomir/jsonplus/blob/master/python/jsonplus/__init__.py
|
|
"""Custom datatypes (like datetime) serialization to/from JSON."""
|
|
|
|
# TODO: handle environments without threads
|
|
# (Python compiled without thread support)
|
|
|
|
import numpy as np
|
|
import simplejson as json
|
|
import threading
|
|
import uuid
|
|
from collections import namedtuple
|
|
from datetime import date, datetime, time, timedelta
|
|
from dateutil.parser import parse as parse_datetime
|
|
from decimal import Decimal
|
|
from fractions import Fraction
|
|
from functools import partial, wraps
|
|
from operator import attrgetter, methodcaller
|
|
from sortedcontainers import SortedList
|
|
|
|
try:
|
|
from moneyed import Currency, Money
|
|
except ImportError:
|
|
# defer failing to actual (de-)serialization
|
|
pass
|
|
|
|
__all__ = [
|
|
"loads", "dumps", "pretty", "json_loads", "json_dumps", "json_prettydump",
|
|
"encoder", "decoder"
|
|
]
|
|
|
|
# Should we aim for the *exact* reproduction of Python types,
|
|
# or for maximum *compatibility* when (de-)serializing?
|
|
#
|
|
# By default, we prefer the exactness of reproduction.
|
|
# For example, `tuple`, `namedtuple`, `Decimal`, or `datetime` will all be
|
|
# restored to the appropriate type (same as the starting type -- even the
|
|
# custom class for the `namedtuple` is recreated).
|
|
# When compatible coding if turned on, we shall fallback to standard JSON
|
|
# types, and values from the example above will be serialized as
|
|
# `list` (`Array`), `dict` (`Object`), `Number` and `ISO8601 timestamp string`,
|
|
# respectively.
|
|
#
|
|
# Please note:
|
|
# - `compat` mode is lossy -- `namedtuple` serialized as `dict`/`Object`
|
|
# can never be deserialized as `namedtuple`.
|
|
# - `exact` mode is verbose -- and if you have a standard JSON decoder on
|
|
# the other end, all that additional type info is useless/discared.
|
|
#
|
|
# To switch between representation styles, use `jsonplus.prefer(coding)`,
|
|
# where `coding` is `jsonplus.EXACT` or `jsonplus.COMPAT`. Another way, maybe
|
|
# simpler, is to use `jsonplus.prefer_exact()` and `jsonplus.prefer_compat()`.
|
|
#
|
|
# The preference is stored thread-local.
|
|
|
|
EXACT = 1
|
|
COMPAT = 2
|
|
CODING_DEFAULT = EXACT
|
|
|
|
_local = threading.local()
|
|
|
|
|
|
def prefer(coding):
|
|
_local.coding = coding
|
|
|
|
|
|
def prefer_exact():
|
|
prefer(EXACT)
|
|
|
|
|
|
def prefer_compat():
|
|
prefer(COMPAT)
|
|
|
|
|
|
def getattrs(value, attrs):
|
|
"""Helper function that extracts a list of attributes from
|
|
`value` object in a `dict`/mapping of (attr, value[attr]).
|
|
|
|
Args:
|
|
value (object):
|
|
Any Python object upon which `getattr` can act.
|
|
|
|
attrs (iterable):
|
|
Any iterable containing attribute names for extract.
|
|
|
|
Returns:
|
|
`dict` of attr -> val mappings.
|
|
|
|
Example:
|
|
>>> getattrs(complex(2,3), ['imag', 'real'])
|
|
{'imag': 3.0, 'real': 2.0}
|
|
"""
|
|
return dict([(attr, getattr(value, attr)) for attr in attrs])
|
|
|
|
|
|
def kwargified(constructor):
|
|
"""Function decorator that wraps a function receiving
|
|
keyword arguments into a function receiving a dictionary
|
|
of arguments.
|
|
|
|
Example:
|
|
@kwargified
|
|
def test(a=1, b=2):
|
|
return a + b
|
|
|
|
>>> test({'b': 3})
|
|
4
|
|
"""
|
|
|
|
@wraps(constructor)
|
|
def kwargs_constructor(kwargs):
|
|
return constructor(**kwargs)
|
|
|
|
return kwargs_constructor
|
|
|
|
|
|
_PredicatedEncoder = namedtuple('_PredicatedEncoder',
|
|
'priority predicate encoder typename')
|
|
|
|
|
|
def encoder(classname, predicate=None, priority=None, exact=True):
|
|
"""A decorator for registering a new encoder for object type
|
|
defined either by a `classname`, or detected via `predicate`.
|
|
|
|
Predicates are tested according to priority (low to high),
|
|
but always before classname.
|
|
|
|
Args:
|
|
classname (str):
|
|
Classname of the object serialized, equal to
|
|
``type(obj).__name__``.
|
|
|
|
predicate (callable, default=None):
|
|
A predicate for testing if object is of certain type.
|
|
The predicate shall receive a single argument, the object
|
|
being encoded, and it has to return a boolean `True/False`.
|
|
See examples below.
|
|
|
|
priority (int, default=None):
|
|
Predicate priority. If undefined, encoder is added at
|
|
the end, with lowest priority.
|
|
|
|
exact (bool, default=True):
|
|
Determines the kind of encoder registered, an exact
|
|
(default), or a compact representation encoder.
|
|
|
|
Examples:
|
|
@encoder('mytype')
|
|
def mytype_exact_encoder(myobj):
|
|
return myobj.to_json()
|
|
|
|
Functional discriminator usage is appropriate for serialization
|
|
of objects with a different classname, but which can be encoded
|
|
with the same encoder:
|
|
|
|
@encoder('BaseClass', lambda obj: isinstance(obj, BaseClass))
|
|
def all_derived_classes_encoder(derived):
|
|
return derived.base_encoder()
|
|
"""
|
|
if exact:
|
|
subregistry = _encode_handlers['exact']
|
|
else:
|
|
subregistry = _encode_handlers['compat']
|
|
|
|
# if priority undefined, set it to lowest
|
|
if priority is None:
|
|
if len(subregistry['predicate']) > 0:
|
|
priority = subregistry['predicate'][-1].priority + 100
|
|
else:
|
|
priority = 1000
|
|
|
|
def _decorator(f):
|
|
if predicate:
|
|
subregistry['predicate'].add(
|
|
_PredicatedEncoder(priority, predicate, f, classname))
|
|
else:
|
|
subregistry['classname'].setdefault(classname, f)
|
|
return f
|
|
|
|
return _decorator
|
|
|
|
|
|
def _json_default_exact(obj):
|
|
"""Serialization handlers for types unsupported by `simplejson`
|
|
that try to preserve the exact data types.
|
|
"""
|
|
|
|
# first try predicate-based encoders
|
|
for handler in _encode_handlers['exact']['predicate']:
|
|
if handler.predicate(obj):
|
|
return {
|
|
"__class__": handler.typename,
|
|
"__value__": handler.encoder(obj)
|
|
}
|
|
|
|
# then classname-based
|
|
classname = type(obj).__name__
|
|
if classname in _encode_handlers['exact']['classname']:
|
|
return {
|
|
"__class__": classname,
|
|
"__value__": _encode_handlers['exact']['classname'][classname](obj)
|
|
}
|
|
|
|
raise TypeError(repr(obj) + " is not JSON serializable")
|
|
|
|
|
|
def _json_default_compat(obj):
|
|
"""Serialization handlers that try to dump objects in
|
|
compatibility mode. Similar to above.
|
|
"""
|
|
for handler in _encode_handlers['compat']['predicate']:
|
|
if handler.predicate(obj):
|
|
return handler.encoder(obj)
|
|
classname = type(obj).__name__
|
|
if classname in _encode_handlers['compat']['classname']:
|
|
return _encode_handlers['compat']['classname'][classname](obj)
|
|
raise TypeError(repr(obj) + " is not JSON serializable")
|
|
|
|
|
|
def decoder(classname):
|
|
"""A decorator for registering a new decoder for `classname`.
|
|
Only ``exact`` decoders can be registered, since it is an assumption
|
|
the ``compat`` mode serializes to standard JSON.
|
|
|
|
Example:
|
|
@decoder('mytype')
|
|
def mytype_decoder(value):
|
|
return mytype(value, reconstruct=True)
|
|
"""
|
|
|
|
def _decorator(f):
|
|
_decode_handlers.setdefault(classname, f)
|
|
|
|
return _decorator
|
|
|
|
|
|
def _json_object_hook(dict):
|
|
"""Deserialization handlers for types unsupported by `simplejson`.
|
|
"""
|
|
classname = dict.get('__class__')
|
|
if classname:
|
|
constructor = _decode_handlers.get(classname)
|
|
value = dict.get('__value__')
|
|
if constructor:
|
|
return constructor(value)
|
|
raise TypeError("Unknown class: '%s'" % classname)
|
|
return dict
|
|
|
|
|
|
def _encoder_default_args(kw):
|
|
"""Shape default arguments for encoding functions."""
|
|
|
|
# manual override of the preferred coding with `exact=False`
|
|
if kw.pop('exact', getattr(_local, 'coding', CODING_DEFAULT) == EXACT):
|
|
# settings necessary for the "exact coding"
|
|
kw.update({
|
|
'default': _json_default_exact,
|
|
'use_decimal': False, # don't encode `Decimal` as JSON's `Number`
|
|
'tuple_as_array': False, # don't encode `tuple` as `Array`
|
|
'namedtuple_as_object':
|
|
False # don't call `_asdict` on `namedtuple`
|
|
})
|
|
else:
|
|
# settings for the "compatibility coding"
|
|
kw.update({
|
|
'default': _json_default_compat,
|
|
'ignore_nan': True # be compliant with the ECMA-262 specification:
|
|
# serialize nan/inf as null
|
|
})
|
|
|
|
# NOTE: if called from ``simplejson.dumps()`` with ``cls=JSONEncoder``,
|
|
# we will receive all kw set to simplejson defaults -- and our defaults for
|
|
# ``separators`` and ``for_json`` will not be applied. In contrast, they
|
|
# are applied when called from ``jsonplus.dumps()``, unless user explicitly
|
|
# sets some of those.
|
|
# This causes inconsistent behaviour between ``dumps()`` and ``JSONEncoder()``.
|
|
|
|
# prefer compact json repr
|
|
kw.setdefault('separators', (',', ':'))
|
|
|
|
# allow objects to provide json serialization on its behalf
|
|
kw.setdefault('for_json', True)
|
|
|
|
|
|
def _decoder_default_args(kw):
|
|
"""Shape default arguments for decoding functions."""
|
|
|
|
kw.update({'object_hook': _json_object_hook})
|
|
|
|
|
|
class JSONEncoder(json.JSONEncoder):
|
|
|
|
def __init__(self, **kw):
|
|
"""Constructor for simplejson.JSONEncoder, with defaults overriden
|
|
for jsonplus.
|
|
"""
|
|
_encoder_default_args(kw)
|
|
super(JSONEncoder, self).__init__(**kw)
|
|
|
|
|
|
class JSONDecoder(json.JSONDecoder):
|
|
|
|
def __init__(self, **kw):
|
|
"""Constructor for simplejson.JSONDecoder, with defaults overriden
|
|
for jsonplus.
|
|
"""
|
|
_decoder_default_args(kw)
|
|
super(JSONDecoder, self).__init__(**kw)
|
|
|
|
|
|
def dumps(*pa, **kw):
|
|
_encoder_default_args(kw)
|
|
return json.dumps(*pa, **kw)
|
|
|
|
|
|
def loads(*pa, **kw):
|
|
_decoder_default_args(kw)
|
|
return json.loads(*pa, **kw)
|
|
|
|
|
|
def pretty(x, sort_keys=True, indent=4 * ' ', separators=(',', ': '), **kw):
|
|
kw.setdefault('sort_keys', sort_keys)
|
|
kw.setdefault('indent', indent)
|
|
kw.setdefault('separators', separators)
|
|
return dumps(x, **kw)
|
|
|
|
|
|
json_dumps = dumps
|
|
json_loads = loads
|
|
json_prettydump = pretty
|
|
|
|
|
|
def np_to_list(value):
|
|
return value.tolist()
|
|
|
|
|
|
def generic_to_item(value):
|
|
return value.item()
|
|
|
|
|
|
_encode_handlers = {
|
|
'exact': {
|
|
'classname': {
|
|
'datetime':
|
|
methodcaller('isoformat'),
|
|
'date':
|
|
methodcaller('isoformat'),
|
|
'time':
|
|
methodcaller('isoformat'),
|
|
'timedelta':
|
|
partial(getattrs, attrs=['days', 'seconds', 'microseconds']),
|
|
'tuple':
|
|
list,
|
|
'set':
|
|
list,
|
|
'ndarray':
|
|
np_to_list,
|
|
'float16':
|
|
generic_to_item,
|
|
'float32':
|
|
generic_to_item,
|
|
'frozenset':
|
|
list,
|
|
'complex':
|
|
partial(getattrs, attrs=['real', 'imag']),
|
|
'Decimal':
|
|
str,
|
|
'Fraction':
|
|
partial(getattrs, attrs=['numerator', 'denominator']),
|
|
'UUID':
|
|
partial(getattrs, attrs=['hex']),
|
|
'Money':
|
|
partial(getattrs, attrs=['amount', 'currency'])
|
|
},
|
|
'predicate': SortedList(key=attrgetter('priority'))
|
|
},
|
|
'compat': {
|
|
'classname': {
|
|
'datetime': methodcaller('isoformat'),
|
|
'date': methodcaller('isoformat'),
|
|
'time': methodcaller('isoformat'),
|
|
'set': list,
|
|
'ndarray': np_to_list,
|
|
'float16': generic_to_item,
|
|
'float32': generic_to_item,
|
|
'frozenset': list,
|
|
'complex': partial(getattrs, attrs=['real', 'imag']),
|
|
'Fraction': partial(getattrs, attrs=['numerator', 'denominator']),
|
|
'UUID': str,
|
|
'Currency': str,
|
|
'Money': str,
|
|
},
|
|
'predicate': SortedList(key=attrgetter('priority'))
|
|
}
|
|
}
|
|
|
|
# all decode handlers are for EXACT decoding BY CLASSNAME
|
|
_decode_handlers = {
|
|
'datetime': parse_datetime,
|
|
'date': lambda v: parse_datetime(v).date(),
|
|
'time': lambda v: parse_datetime(v).timetz(),
|
|
'timedelta': kwargified(timedelta),
|
|
'tuple': tuple,
|
|
'set': set,
|
|
'ndarray': np.asarray,
|
|
'float16': np.float16,
|
|
'float32': np.float32,
|
|
'frozenset': frozenset,
|
|
'complex': kwargified(complex),
|
|
'Decimal': Decimal,
|
|
'Fraction': kwargified(Fraction),
|
|
'UUID': kwargified(uuid.UUID)
|
|
}
|
|
|
|
|
|
@encoder('namedtuple',
|
|
lambda obj: isinstance(obj, tuple) and hasattr(obj, '_fields'))
|
|
def _dump_namedtuple(obj):
|
|
return {
|
|
"name": type(obj).__name__,
|
|
"fields": list(obj._fields),
|
|
"values": list(obj)
|
|
}
|
|
|
|
|
|
@decoder('namedtuple')
|
|
def _load_namedtuple(val):
|
|
cls = namedtuple(val['name'], val['fields'])
|
|
return cls(*val['values'])
|
|
|
|
|
|
@encoder('timedelta', exact=False)
|
|
def _timedelta_total_seconds(td):
|
|
# timedelta.total_seconds() is only available since python 2.7
|
|
return (td.microseconds +
|
|
(td.seconds + td.days * 24 * 3600.0) * 10**6) / 10**6
|
|
|
|
|
|
@encoder('Currency')
|
|
def _dump_currency(obj):
|
|
"""Serialize standard (ISO-defined) currencies to currency code only,
|
|
and non-standard (user-added) currencies in full.
|
|
"""
|
|
from moneyed import CurrencyDoesNotExist, get_currency
|
|
try:
|
|
get_currency(obj.code)
|
|
return obj.code
|
|
except CurrencyDoesNotExist:
|
|
return getattrs(obj, ['code', 'numeric', 'name', 'countries'])
|
|
|
|
|
|
@decoder('Currency')
|
|
def _load_currency(val):
|
|
"""Deserialize string values as standard currencies, but
|
|
manually define fully-defined currencies (with code/name/numeric/countries).
|
|
"""
|
|
from moneyed import get_currency
|
|
try:
|
|
return get_currency(code=val)
|
|
except:
|
|
return Currency(**val)
|
|
|
|
|
|
@decoder('Money')
|
|
def _load_money(val):
|
|
# wrap with function to delay Currency/Money
|
|
# parsing if not installed (and not needed)
|
|
return Money(**val)
|