one minor issue is that the identity object gets dump'd twice..main
| @@ -1,7 +1,7 @@ | |||||
| VIRTUALENV?=python3 -m venv | VIRTUALENV?=python3 -m venv | ||||
| MODULES=medashare | MODULES=medashare | ||||
| test: fixtures/sample.data.pasn1 fixtures/sample.persona.pasn1 fixtures/sample.mtree | |||||
| test: fixtures/sample.data.sqlite3 fixtures/sample.persona.pasn1 fixtures/sample.mtree | |||||
| (. ./p/bin/activate && \ | (. ./p/bin/activate && \ | ||||
| ((find fixtures -type f; find $(MODULES) -type f) | entr sh -c 'python3 -m coverage run -m unittest --failfast $(MODULES).tests && coverage report -m --omit=p/\*')) | ((find fixtures -type f; find $(MODULES) -type f) | entr sh -c 'python3 -m coverage run -m unittest --failfast $(MODULES).tests && coverage report -m --omit=p/\*')) | ||||
| @@ -10,7 +10,7 @@ env: | |||||
| (. ./p/bin/activate && \ | (. ./p/bin/activate && \ | ||||
| pip install -r requirements.txt) | pip install -r requirements.txt) | ||||
| fixtures/sample.data.pasn1 fixtures/sample.persona.pasn1: fixtures/genfixtures.py | |||||
| fixtures/sample.data.sqlite3 fixtures/sample.persona.pasn1: fixtures/genfixtures.py | |||||
| (. ./p/bin/activate && cd fixtures && PYTHONPATH=.. python3 genfixtures.py ) | (. ./p/bin/activate && cd fixtures && PYTHONPATH=.. python3 genfixtures.py ) | ||||
| fixtures/sample.mtree: fixtures/mtree.dir | fixtures/sample.mtree: fixtures/mtree.dir | ||||
| @@ -206,7 +206,14 @@ | |||||
| "title": "dump is correct", | "title": "dump is correct", | ||||
| "cmd": [ "dump" ], | "cmd": [ "dump" ], | ||||
| "exit": 0, | "exit": 0, | ||||
| "stdout_re": "{.*name.*Changed Name.*type.*identity.*}\n{.*filename.*newfile.txt.*hashes.*90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c.*size.*19.*type.*file.*}\n{.*foo.*bar=baz.*hashes.*90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c.*type.*metadata.*}\n{.*filename.*test.txt.*90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c.*size.*19.*type.*file.*}\n{.*filename.*newfile.txt.*90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c.*size.*19.*type.*file.*}\n{.*filename.*newfile.txt.*90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c.*size.*19.*type.*file.*}\n" | |||||
| "stdout_check": [ | |||||
| { "name": "Changed Name", "type": "identity" }, | |||||
| { "filename": "newfile.txt", "hashes": [ "sha512:90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c" ], "size": 19, "type": "file" }, | |||||
| { "foo": [ "bar=baz" ], "hashes": [ "sha512:90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c" ], "type": "metadata" }, | |||||
| { "filename": "test.txt", "hashes": [ "sha512:90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c" ], "size": 19, "type": "file" }, | |||||
| { "filename": "newfile.txt", "hashes": [ "sha512:90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c" ], "size": 19, "type": "file" }, | |||||
| { "filename": "newfile.txt", "hashes": [ "sha512:b0551b2fb5d045a74d36a08ac49aea66790ea4fb5e84f9326a32db44fc78ca0676e65a8d1f0d98f62589eeaef105f303c81f07f3f862ad3bace7960fe59de4d5" ], "size": 17, "type": "file" } | |||||
| ] | |||||
| }, | }, | ||||
| { | { | ||||
| "title": "that import can be done", | "title": "that import can be done", | ||||
| @@ -23,7 +23,7 @@ | |||||
| "count": 5 | "count": 5 | ||||
| }, | }, | ||||
| { | { | ||||
| "title": "verify correct files imported", | |||||
| "title": "verify correct files imported a", | |||||
| "cmd": [ "dump" ], | "cmd": [ "dump" ], | ||||
| "stdout_re": "fileb.txt.*file.*\n.*foo.*bar.*cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063.*\n.*filed.txt.*file.*\n.*filef.txt.*file.*\n.*fileb.txt.*filed.txt.*filef.txt.*cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1.*7831bd05e23877e08a97362bab2ad7bcc7d08d8f841f42e8dee545781792b987aa7637f12cec399e261f798c10d3475add0db7de2643af86a346b6b451a69ec4.*be688838ca8686e5c90689bf2ab585cef1137c.*incomplete.*true.*container.*magnet:\\?xt=urn:btih:501cf3bd4797f49fd7a624e8a9a8ce5cccceb602&dn=somedir" | "stdout_re": "fileb.txt.*file.*\n.*foo.*bar.*cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063.*\n.*filed.txt.*file.*\n.*filef.txt.*file.*\n.*fileb.txt.*filed.txt.*filef.txt.*cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1.*7831bd05e23877e08a97362bab2ad7bcc7d08d8f841f42e8dee545781792b987aa7637f12cec399e261f798c10d3475add0db7de2643af86a346b6b451a69ec4.*be688838ca8686e5c90689bf2ab585cef1137c.*incomplete.*true.*container.*magnet:\\?xt=urn:btih:501cf3bd4797f49fd7a624e8a9a8ce5cccceb602&dn=somedir" | ||||
| }, | }, | ||||
| @@ -45,9 +45,25 @@ | |||||
| "cmd": [ "container", "somedir.torrent" ] | "cmd": [ "container", "somedir.torrent" ] | ||||
| }, | }, | ||||
| { | { | ||||
| "title": "verify correct files imported", | |||||
| "title": "verify correct files imported b", | |||||
| "cmd": [ "dump" ], | "cmd": [ "dump" ], | ||||
| "stdout_re": ".*\n.*fileb.txt.*file.*\n.*foo.*bar.*cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063.*\n.*filed.txt.*file.*\n.*filef.txt.*file.*\n.*filea.txt.*fileb.txt.*filec.txt.*filed.txt.*filee.txt.*filef.txt.*0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6.*cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1.*7831bd05e23877e08a97362bab2ad7bcc7d08d8f841f42e8dee545781792b987aa7637f12cec399e261f798c10d3475add0db7de2643af86a346b6b451a69ec4.*be688838ca8686e5c90689bf2ab585cef1137c.*container.*magnet:\\?xt=urn:btih:501cf3bd4797f49fd7a624e8a9a8ce5cccceb602&dn=somedir" | |||||
| "stdout_check": [ | |||||
| { "type": "identity" }, | |||||
| { "foo": [ "bar" ], "hashes": [ | |||||
| "sha512:cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063" ] }, | |||||
| { "foo": [ "bar" ], "hashes": [ | |||||
| "sha512:7831bd05e23877e08a97362bab2ad7bcc7d08d8f841f42e8dee545781792b987aa7637f12cec399e261f798c10d3475add0db7de2643af86a346b6b451a69ec4" ] }, | |||||
| { "filename": "filea.txt", "type": "file" }, | |||||
| { "filename": "fileb.txt", "type": "file" }, | |||||
| { "filename": "filec.txt", "type": "file" }, | |||||
| { "filename": "filed.txt", "type": "file" }, | |||||
| { "filename": "filee.txt", "type": "file" }, | |||||
| { "filename": "filef.txt", "type": "file" }, | |||||
| { "files": [ "filea.txt", "fileb.txt", "filec.txt", "filed.txt", "filee.txt", "filef/filef.txt" ], | |||||
| "hashes": [ "sha512:0cf9180a764aba863a67b6d72f0918bc131c6772642cb2dce5a34f0a702f9470ddc2bf125c12198b1995c233c34b4afd346c54a2334c350a948a51b6e8b4e6b6", "sha512:cc06808cbbee0510331aa97974132e8dc296aeb795be229d064bae784b0a87a5cf4281d82e8c99271b75db2148f08a026c1a60ed9cabdb8cac6d24242dac4063", "sha512:cb9eb9ec6c2cd9b0d9451e6b179d91e24906a3123be5e5f18e182be09fab30ad6f5de391bb3cf53933d3a1ca29fdd68d23e17c49fbc1a9117c8ab08154c7df30", "sha512:7831bd05e23877e08a97362bab2ad7bcc7d08d8f841f42e8dee545781792b987aa7637f12cec399e261f798c10d3475add0db7de2643af86a346b6b451a69ec4", "sha512:b09a577f24fb7a6f4b3ea641b2b67120e187b605ef27db97bef178457d9002bec846435a205466e327e5ab151ab1b350b5ac1c9f97e48333cec84fecec3b7037", "sha512:be688838ca8686e5c90689bf2ab585cef1137c999b48c70b92f67a5c34dc15697b5d11c982ed6d71be1e1e7f7b4e0733884aa97c3f7a339a8ed03577cf74be09" ], | |||||
| "type": "container", | |||||
| "uri": "magnet:?xt=urn:btih:501cf3bd4797f49fd7a624e8a9a8ce5cccceb602&dn=somedir" } | |||||
| ] | |||||
| }, | }, | ||||
| { | { | ||||
| "special": "verify store object cnt", | "special": "verify store object cnt", | ||||
| @@ -37,7 +37,7 @@ | |||||
| "title": "that a mapping with unknown host errors", | "title": "that a mapping with unknown host errors", | ||||
| "cmd": [ "mapping", "--create", "ceaa4862-dd00-41ba-9787-7480ec1b267a:/foo", "efdb5d9c-d123-4b30-aaa8-45a9ea8f6053:/bar" ], | "cmd": [ "mapping", "--create", "ceaa4862-dd00-41ba-9787-7480ec1b267a:/foo", "efdb5d9c-d123-4b30-aaa8-45a9ea8f6053:/bar" ], | ||||
| "exit": 1, | "exit": 1, | ||||
| "stderr": "ERROR: Unable to find host 'ceaa4862-dd00-41ba-9787-7480ec1b267a'\n" | |||||
| "stderr": "ERROR: Unable to find host ceaa4862-dd00-41ba-9787-7480ec1b267a\n" | |||||
| }, | }, | ||||
| { | { | ||||
| "title": "that a host mapping that isn't absolute errors", | "title": "that a host mapping that isn't absolute errors", | ||||
| @@ -1,12 +1,13 @@ | |||||
| import pasn1 | import pasn1 | ||||
| import cli | |||||
| from medashare import cli | |||||
| import datetime | import datetime | ||||
| import uuid | import uuid | ||||
| persona = cli.Persona() | persona = cli.Persona() | ||||
| persona.generate_key() | persona.generate_key() | ||||
| cbr = persona.get_identity().uuid | cbr = persona.get_identity().uuid | ||||
| objst = cli.ObjectStore(cbr) | |||||
| storename = 'sample.data.sqlite3' | |||||
| objst = cli.ObjectStore.load(storename, cbr) | |||||
| list(map(objst.loadobj, | list(map(objst.loadobj, | ||||
| [ | [ | ||||
| { | { | ||||
| @@ -21,5 +22,4 @@ list(map(objst.loadobj, | |||||
| ] | ] | ||||
| )) | )) | ||||
| objst.store('sample.data.pasn1') | |||||
| persona.store('sample.persona.pasn1') | persona.store('sample.persona.pasn1') | ||||
| @@ -0,0 +1,197 @@ | |||||
| import base64 | |||||
| import copy | |||||
| import datetime | |||||
| import itertools | |||||
| import json | |||||
| import unittest | |||||
| import uuid | |||||
| from .utils import _makeuuid, _makedatetime, _makebytes, _asn1coder | |||||
| class _JSONEncoder(json.JSONEncoder): | |||||
| def default(self, o): | |||||
| if isinstance(o, uuid.UUID): | |||||
| return str(o) | |||||
| elif isinstance(o, datetime.datetime): | |||||
| o = o.astimezone(datetime.timezone.utc) | |||||
| return o.strftime('%Y-%m-%dT%H:%M:%S.%fZ') | |||||
| elif isinstance(o, bytes): | |||||
| return base64.urlsafe_b64encode(o).decode('US-ASCII') | |||||
| return json.JSONEncoder.default(self, o) | |||||
| _jsonencoder = _JSONEncoder() | |||||
| class _TestJSONEncoder(unittest.TestCase): | |||||
| def test_defaultfailure(self): | |||||
| class Foo: | |||||
| pass | |||||
| self.assertRaises(TypeError, _jsonencoder.encode, Foo()) | |||||
| # XXX - add validation | |||||
| # XXX - how to add singletons | |||||
| class MDBase(object): | |||||
| '''This is a simple wrapper that turns a JSON object into a pythonesc | |||||
| object where attribute accesses work.''' | |||||
| _type = 'invalid' | |||||
| _generated_properties = { | |||||
| 'uuid': uuid.uuid4, | |||||
| 'modified': lambda: datetime.datetime.now( | |||||
| tz=datetime.timezone.utc), | |||||
| } | |||||
| # When decoding, the decoded value should be passed to this function | |||||
| # to get the correct type | |||||
| _instance_properties = { | |||||
| 'uuid': _makeuuid, | |||||
| 'modified': _makedatetime, | |||||
| 'created_by_ref': _makeuuid, | |||||
| 'parent_refs': lambda x: [ _makeuuid(y) for y in x ], | |||||
| 'sig': _makebytes, | |||||
| } | |||||
| # Override on a per subclass basis | |||||
| _class_instance_properties = { | |||||
| } | |||||
| _common_properties = [ 'type', 'created_by_ref' ] # XXX - add lang? | |||||
| _common_optional = set(('parent_refs', 'sig')) | |||||
| _common_names = set(_common_properties + list( | |||||
| _generated_properties.keys())) | |||||
| _common_names_list = _common_properties + list( | |||||
| _generated_properties.keys()) | |||||
| def __init__(self, obj={}, **kwargs): | |||||
| obj = copy.deepcopy(obj) | |||||
| obj.update(kwargs) | |||||
| if self._type == MDBase._type: | |||||
| raise ValueError('call MDBase.create_obj instead so correct class is used.') | |||||
| if 'type' in obj and obj['type'] != self._type: | |||||
| raise ValueError( | |||||
| 'trying to create the wrong type of object, got: %s, expected: %s' % | |||||
| (repr(obj['type']), repr(self._type))) | |||||
| if 'type' not in obj: | |||||
| obj['type'] = self._type | |||||
| for x in self._common_properties: | |||||
| if x not in obj: | |||||
| raise ValueError('common property %s not present' % repr(x)) | |||||
| for x, fun in itertools.chain( | |||||
| self._instance_properties.items(), | |||||
| self._class_instance_properties.items()): | |||||
| if x in obj: | |||||
| obj[x] = fun(obj[x]) | |||||
| for x, fun in self._generated_properties.items(): | |||||
| if x not in obj: | |||||
| obj[x] = fun() | |||||
| self._obj = obj | |||||
| @classmethod | |||||
| def create_obj(cls, obj): | |||||
| '''Using obj as a base, create an instance of MDBase of the | |||||
| correct type. | |||||
| If the correct type is not found, a ValueError is raised.''' | |||||
| if isinstance(obj, cls): | |||||
| obj = obj._obj | |||||
| ty = obj['type'] | |||||
| for i in MDBase.__subclasses__(): | |||||
| if i._type == ty: | |||||
| return i(obj) | |||||
| else: | |||||
| raise ValueError('Unable to find class for type %s' % | |||||
| repr(ty)) | |||||
| def new_version(self, *args, dels=(), replaces=()): | |||||
| '''For each k, v pair, add the property k as an additional one | |||||
| (or new one if first), with the value v. | |||||
| Any key in dels is removed. | |||||
| Any k, v pair in replaces, replaces the entire key.''' | |||||
| obj = copy.deepcopy(self._obj) | |||||
| common = self._common_names | self._common_optional | |||||
| uniquify = set() | |||||
| for k, v in args: | |||||
| if k in common: | |||||
| obj[k] = v | |||||
| else: | |||||
| uniquify.add(k) | |||||
| obj.setdefault(k, []).append(v) | |||||
| for k in uniquify: | |||||
| obj[k] = list(set(obj[k])) | |||||
| for i in dels: | |||||
| del obj[i] | |||||
| for k, v in replaces: | |||||
| obj[k] = v | |||||
| del obj['modified'] | |||||
| return self.create_obj(obj) | |||||
| def __repr__(self): # pragma: no cover | |||||
| return '%s(%s)' % (self.__class__.__name__, repr(self._obj)) | |||||
| def __getattr__(self, k): | |||||
| try: | |||||
| return self._obj[k] | |||||
| except KeyError: | |||||
| raise AttributeError(k) | |||||
| def __setattr__(self, k, v): | |||||
| if k[0] == '_': # direct attribute | |||||
| self.__dict__[k] = v | |||||
| else: | |||||
| self._obj[k] = v | |||||
| def __getitem__(self, k): | |||||
| return self._obj[k] | |||||
| def __to_dict__(self): | |||||
| '''Returns an internal object. If modification is necessary, | |||||
| make sure to .copy() it first.''' | |||||
| return self._obj | |||||
| def __eq__(self, o): | |||||
| return self._obj == o | |||||
| def __contains__(self, k): | |||||
| return k in self._obj | |||||
| def items(self, skipcommon=True): | |||||
| return [ (k, v) for k, v in self._obj.items() if | |||||
| not skipcommon or k not in self._common_names ] | |||||
| def encode(self, meth='asn1'): | |||||
| if meth == 'asn1': | |||||
| return _asn1coder.dumps(self) | |||||
| return _jsonencoder.encode(self._obj) | |||||
| @classmethod | |||||
| def decode(cls, s, meth='asn1'): | |||||
| if meth == 'asn1': | |||||
| obj = _asn1coder.loads(s) | |||||
| else: | |||||
| obj = json.loads(s) | |||||
| return cls.create_obj(obj) | |||||
| @@ -0,0 +1,72 @@ | |||||
| import uuid | |||||
| from sqlalchemy import Table, Column, DateTime, String, Integer, LargeBinary | |||||
| from sqlalchemy import types | |||||
| from sqlalchemy.orm import declarative_base | |||||
| from .cli import _debprint | |||||
| from .mdb import MDBase | |||||
| Base = declarative_base() | |||||
| class MDBaseType(types.TypeDecorator): | |||||
| impl = LargeBinary | |||||
| cache_ok = True | |||||
| def process_bind_param(self, value, dialect): | |||||
| return value.encode() | |||||
| def process_result_value(self, value, dialect): | |||||
| return MDBase.decode(value) | |||||
| class UUID(types.TypeDecorator): | |||||
| impl = String(32) | |||||
| cache_ok = True | |||||
| def process_bind_param(self, value, dialect): | |||||
| return value.hex | |||||
| def process_result_value(self, value, dialect): | |||||
| return uuid.UUID(hex=value) | |||||
| class Dummy(Base): | |||||
| __tablename__ = 'dummy' | |||||
| id = Column(Integer, primary_key=True) | |||||
| class UUIDv5Table(Base): | |||||
| __tablename__ = 'uuidv5_index' | |||||
| uuid = Column(UUID, primary_key=True) | |||||
| objid = Column(UUID) | |||||
| class HostMapping(Base): | |||||
| __tablename__ = 'hostmapping' | |||||
| hostid = Column(UUID, primary_key=True) | |||||
| objid = Column(UUID, primary_key=True) | |||||
| # https://stackoverflow.com/questions/57685385/how-to-avoid-inserting-duplicate-data-when-inserting-data-into-sqlite3-database | |||||
| #UniqueConstraint('hostid', 'objid', on conflict ignore) | |||||
| class HostTable(Base): | |||||
| __tablename__ = 'hosttable' | |||||
| hostid = Column(UUID, primary_key=True) | |||||
| objid = Column(UUID) | |||||
| class HashTable(Base): | |||||
| __tablename__ = 'hash_index' | |||||
| hash = Column(String, primary_key=True) | |||||
| uuid = Column(UUID, primary_key=True) | |||||
| class MetaDataObject(Base): | |||||
| __tablename__ = 'metadata_objects' | |||||
| uuid = Column(UUID, primary_key=True) | |||||
| modified = Column(DateTime) | |||||
| data = Column(MDBaseType) | |||||
| def __repr__(self): | |||||
| return 'MetaDataObject(uuid=%s, modified=%s, data=%s)' % (repr(self.uuid), repr(self.modified), repr(self.data)) | |||||
| @@ -1,6 +1,6 @@ | |||||
| from .btv import _TestCases as btv_test_cases | from .btv import _TestCases as btv_test_cases | ||||
| from .btv.bencode import _TestCases as bencode_test_cases | from .btv.bencode import _TestCases as bencode_test_cases | ||||
| from .mdb import _TestJSONEncoder | |||||
| from .cli import _TestCononicalCoder, _TestCases as cli_test_cases | from .cli import _TestCononicalCoder, _TestCases as cli_test_cases | ||||
| from .cli import _TestJSONEncoder | |||||
| from .mtree import Test | from .mtree import Test | ||||
| from .server import _TestCases, _TestPostConfig | from .server import _TestCases, _TestPostConfig | ||||
| @@ -0,0 +1,47 @@ | |||||
| import base64 | |||||
| import datetime | |||||
| import pasn1 | |||||
| import uuid | |||||
| def _makeuuid(s): | |||||
| if isinstance(s, uuid.UUID): | |||||
| return s | |||||
| if isinstance(s, bytes): | |||||
| return uuid.UUID(bytes=s) | |||||
| else: | |||||
| return uuid.UUID(s) | |||||
| def _makedatetime(s): | |||||
| if isinstance(s, datetime.datetime): | |||||
| return s | |||||
| return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%fZ').replace( | |||||
| tzinfo=datetime.timezone.utc) | |||||
| def _makebytes(s): | |||||
| if isinstance(s, bytes): | |||||
| return s | |||||
| return base64.urlsafe_b64decode(s) | |||||
| def _trytodict(o): | |||||
| if isinstance(o, uuid.UUID): | |||||
| return 'bytes', o.bytes | |||||
| if isinstance(o, tuple): | |||||
| return 'list', o | |||||
| try: | |||||
| return 'dict', o.__to_dict__() | |||||
| except Exception: # pragma: no cover | |||||
| raise TypeError('unable to find __to_dict__ on %s: %s' % | |||||
| (type(o), repr(o))) | |||||
| class CanonicalCoder(pasn1.ASN1DictCoder): | |||||
| def enc_dict(self, obj, **kwargs): | |||||
| class FakeIter: | |||||
| def items(self): | |||||
| return iter(sorted(obj.items())) | |||||
| return pasn1.ASN1DictCoder.enc_dict(self, FakeIter(), **kwargs) | |||||
| _asn1coder = CanonicalCoder(coerce=_trytodict) | |||||
| @@ -25,6 +25,7 @@ setup( | |||||
| 'fastapi', | 'fastapi', | ||||
| 'fastapi_restful', | 'fastapi_restful', | ||||
| 'httpx', | 'httpx', | ||||
| 'SQLAlchemy', | |||||
| 'hypercorn', # option, for server only? | 'hypercorn', # option, for server only? | ||||
| 'orm', | 'orm', | ||||
| 'pasn1 @ git+https://www.funkthat.com/gitea/jmg/pasn1.git@c6c64510b42292557ace2b77272eb32cb647399d#egg=pasn1', | 'pasn1 @ git+https://www.funkthat.com/gitea/jmg/pasn1.git@c6c64510b42292557ace2b77272eb32cb647399d#egg=pasn1', | ||||