PK!TWWconseil/__init__.pyfrom conseil.api import ConseilApi from conseil.core import Client conseil = Client() PK!7Qconseil/api.pyimport requests class ConseilException(Exception): pass class ConseilApi: def __init__(self, api_key='bakingbad', api_host='https://conseil-dev.cryptonomic-infra.tech', api_version=2): self._api_key = api_key self._api_host = api_host self._api_version = api_version def _request(self, method, path, json=None): response = requests.request( method=method, url=f'{self._api_host}/v{self._api_version}/{path}', headers={'apiKey': self._api_key}, json=json ) if response.status_code != 200: raise ConseilException(f'[{response.status_code}]: {response.text}') return response def get(self, path): return self._request(method='GET', path=path) def post(self, path, json): return self._request(method='POST', path=path, json=json) PK!0OOconseil/core.pyfrom decimal import Decimal from conseil.query import MetadataQuery, DataQuery from conseil.api import ConseilException def not_(predicate: dict): res = predicate.copy() res['inverse'] = True return res class Attribute(MetadataQuery): __query_path__ = 'metadata/{platform_id}/{network_id}/{entity_id}/{attribute_id}' def __hash__(self): return id(self) def __getattr__(self, item): return item def query(self) -> DataQuery: """ Request specific attribute :return: DataQuery """ kwargs = self._extend(attributes={self['attribute_id']: self}) return DataQuery(self._api, **kwargs) def _predicate(self, operation, *args, inverse=False): predicate = { 'field': self['attribute_id'], 'operation': operation, 'set': list(args), 'inverse': inverse } if args and all(map(lambda x: isinstance(x, Decimal), args)): precision = max(map(lambda x: max(0, -x.as_tuple().exponent), args)) predicate['precision'] = precision return predicate def _sort_order(self, direction): return { 'field': self['attribute_id'], 'direction': direction } def _aggregate(self, function): return self._spawn(aggregation={ 'field': self['attribute_id'], 'function': function }) def in_(self, *args): if len(args) == 0: return self.is_(None) if len(args) == 1: return self.is_(args[0]) return self._predicate('in', *args) def notin_(self, *args): if len(args) == 0: return self.isnot(None) if len(args) == 1: return self.isnot(args[0]) return self._predicate('in', *args, inverse=True) def is_(self, value): if value is None: return self._predicate('isnull') return self._predicate('eq', value) def isnot(self, value): if value is None: return self._predicate('isnull', inverse=True) return self._predicate('eq', value, inverse=True) def between(self, first, second): return self._predicate('between', first, second) def like(self, value): return self._predicate('like', value) def notlike(self, value): return self._predicate('like', value, inverse=True) def __lt__(self, other): return self._predicate('lt', other) def __ge__(self, other): return self._predicate('lt', other, inverse=True) def __gt__(self, other): return self._predicate('gt', other) def __le__(self, other): return self._predicate('gt', other, inverse=True) def __eq__(self, other): return self.is_(other) def __ne__(self, other): return self.isnot(other) def startswith(self, value): return self._predicate('startsWith', value) def endswith(self, value): return self._predicate('endsWith', value) def asc(self): return self._sort_order('asc') def desc(self): return self._sort_order('desc') def sum(self): return self._aggregate('sum') def count(self): return self._aggregate('count') def avg(self): return self._aggregate('avg') def min(self): return self._aggregate('min') def max(self): return self._aggregate('max') class Entity(MetadataQuery): __child_key__ = 'attribute_id' __child_class__ = Attribute __query_path__ = 'metadata/{platform_id}/{network_id}/{entity_id}/attributes' def query(self, *args) -> DataQuery: """__query_path__ Request an entity or specific fields :param args: Array of attributes (of a common entity) or a single entity :return: DataQuery """ if all(map(lambda x: isinstance(x, Attribute), args)): attributes = {x['attribute_id']: x for x in args} if any(map(lambda x: x['entity_id'] != self['entity_id'], args)): raise ConseilException('Entity mismatch') elif len(args) == 0: attributes = dict() else: raise ConseilException('List of attributes (single entity) or an entity is allowed') kwargs = self._extend(attributes=attributes) return DataQuery(self._api, **kwargs) class Network(MetadataQuery): __child_key__ = 'entity_id' __child_class__ = Entity __query_path__ = 'metadata/{platform_id}/{network_id}/entities' def query(self, *args) -> DataQuery: """ Request an entity or specific fields :param args: Array of attributes (of a common entity) or a single entity :return: DataQuery """ if all(map(lambda x: isinstance(x, Attribute), args)): attributes = {x['attribute_id']: x for x in args} if any(map(lambda x: x['entity_id'] != args[0]['entity_id'], args)): raise ConseilException('Mixed entities') elif len(args) == 1 and isinstance(args[0], Entity): attributes = dict() else: raise ConseilException('List of attributes (single entity) or an entity is allowed') kwargs = self._extend( attributes=attributes, entity_id=args[0]['entity_id'] ) return DataQuery(self._api, **kwargs) class Platform(MetadataQuery): __child_key__ = 'network_id' __child_class__ = Network __query_path__ = 'metadata/{platform_id}/networks' class Client(MetadataQuery): __child_key__ = 'platform_id' __child_class__ = Platform __query_path__ = 'metadata/platforms' PK!mb:Cconseil/query.pyimport re from os.path import basename from functools import lru_cache from pprint import pformat from conseil.api import ConseilApi, ConseilException def get_class_docstring(class_type): docstring = '' names = filter(lambda x: not x.startswith('_'), dir(class_type)) for name in names: attr = getattr(class_type, name, None) if type(attr) == property: title = f'.{name}' else: title = f'.{name}()' if attr.__doc__: doc = re.sub(r' {3,}', '', attr.__doc__) else: doc = '' docstring += f'{title}{doc}\n' return docstring class Query: __query_path__ = None def __init__(self, api=ConseilApi(), **kwargs): self._api = api self._kwargs = kwargs def _extend(self, **kwargs): params = self._kwargs.copy() for key, value in kwargs.items(): if isinstance(params.get(key), list): params[key].extend(value) else: params[key] = value return params def _spawn(self, **kwargs): params = self._extend(**kwargs) return self.__class__(self._api, **params) @property def _query_path(self): return self.__query_path__.format(**self._kwargs) def __getitem__(self, item): return self._kwargs.get(item) def __repr__(self): if self.__query_path__: return f'Path\n{self._query_path}\n\n' class MetadataQuery(Query): __child_key__ = None __child_class__ = None @lru_cache(maxsize=None) def _request(self): try: if self.__query_path__: return self._api.get(self._query_path).json() except ConseilException: pass return list() @property def _attr_names(self): return filter(lambda x: x, map(lambda x: x.get('name', x) if isinstance(x, dict) else x, self._request())) def __repr__(self): docstring = super(MetadataQuery, self).__repr__() attr_names = '\n'.join(map(lambda x: f'.{x}', self._attr_names)) if attr_names: title = basename(self._query_path).capitalize() docstring += f'{title}\n{attr_names}\n' docstring += get_class_docstring(self.__class__) return docstring def __call__(self): return self._request() def __dir__(self): return list(super(MetadataQuery, self).__dir__()) + list(self._attr_names) @lru_cache(maxsize=None) def __getattr__(self, item): if self.__child_class__: kwargs = { self.__child_key__: item, **self._kwargs } return self.__child_class__(self._api, **kwargs) raise ConseilException(item) class DataQuery(Query): __query_path__ = 'data/{platform_id}/{network_id}/{entity_id}' def payload(self): """ Resulting Conseil query :return: object """ attributes = self['attributes'] or {} having = self['having'] or [] orders = self['order_by'] or [] for predicate in having: try: attributes[predicate['field']]['aggregation']['predicate'] = predicate except (KeyError, TypeError): raise ConseilException(f'Orphan HAVING predicate on `{predicate["field"]}`') aggregation = [x['aggregation'] for x in attributes.values() if x['aggregation']] for order in orders: try: function = attributes[order['field']]['aggregation']['function'] order['field'] = f'{function}_{order["field"]}' except (KeyError, TypeError): pass return { 'fields': list(attributes.keys()), 'predicates': list(self['predicates'] or []), 'aggregation': aggregation, 'orderBy': orders, 'limit': self['limit'], 'output': self['output'] or 'json' } def __repr__(self): docstring = super(DataQuery, self).__repr__() docstring += f'Query\n{pformat(self.payload())}\n\n' return docstring def filter(self, *args): """ Use predicates to filter results (conjunction) :param args: array of predicates :return: DataQuery """ return self._spawn(predicates=args) def filter_by(self, **kwargs): """ Use simple `eq` predicates to filter results (conjunction) :param kwargs: pairs = :return: DataQuery """ predicates = [ { 'field': k, 'operation': 'eq', 'set': [v], 'inverse': False } for k, v in kwargs.items() ] return self._spawn(predicates=predicates) def order_by(self, *args): """ Sort results by specified columns :param args: one or many sort rules :return: DataQuery """ order_by = [x if isinstance(x, dict) else x.asc() for x in args] return self._spawn(order_by=order_by) def limit(self, limit: int): """ Limit results count :param limit: integer :return: DataQuery """ return self._spawn(limit=limit) def having(self, *args): """ Filter results by aggregated column :param args: array of predicates on aggregated columns :return: DataQuery """ return self._spawn(having=args) def all(self, output='json'): """ Get all results :param output: output format (json/csv), default is JSON :return: list (json) or string (csv) """ self._kwargs['output'] = output res = self._api.post(path=self._query_path, json=self.payload()) if output == 'json': return res.json() return res.text def one(self): """ Get single result, fail if there are no or multiple records (json only) :return: object """ res = self.all() if len(res) == 0: raise ConseilException('Not found') if len(res) > 1: raise ConseilException('Multiple results') return res[0] def one_or_none(self): """ Get single result or None (do not fail) :return: object or None """ try: return self.one() except ConseilException: pass def scalar(self): """ Get value of a single attribute (single column, single row) :return: scalar """ res = self.one() if len(res) != 1: raise ConseilException('Multiple keys') return next(iter(res.values())) PK!T>11conseil-0.1.0.dist-info/LICENSEMIT License Copyright (c) 2019 Tezos Baking Bad Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTUconseil-0.1.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!Hux/  conseil-0.1.0.dist-info/METADATAXkw6\ =:ixWc{n$̴{鞞`yܙ3ay- 9Ŕ%0BfQt7ߍN<纞]Yl4{Y= |qcB j锽89fGeՅH5WX^BsIm=/;-JVKbYL,O?ts)xƚoDbǯ_=WTFZѭ=0E#֥ g^ iMEwzYGg=dme~4Y/'ܸ\,k^$z!C:O7.q|Yjb.O_`tϳJ8zn6/ӱ~|!a;ǵ2!= qeTnfaHճ`NDҪB2Cq~6ܕѝ;icyqm{>4fٜUV܎:I 2`02eY&ׂxD3U[\fkF.`Ҏh%`8HBYKa(fe.`!XW1q* ٗ?)D}}_dumMVysE&Ri8:JU4؁Z̳rqhIzмa=tg1 ![4sۚ 9vw2dwhNpv"ݾL>oq8r=@g gX*k ǒ`XÔOfڈl1/1/evG%Qf;>ue1E!|-G/Ͽ{]X)cʓ' _jGÞ_d*$ %or塌G`0XnuJkXT"Y90O5Z+p.8,NTk`K $ ;LK=C·}9@%.e751:ۅ QQ ^p1;YɅeS>Aϔ*` t|WQ&oT}*jLmgK[4g~SLbKQ6HPp"e7Z1:ҚcעazJy9CiT B=nI5'~S_[:|UU)xF3~gxZIU`υ@5чOlQx|4dA޽6QW̚6o&m'o m,UtQFj`gl9Z[D7nt٭Frc8U֠|b)i76:.h]!x{x395Z[:rWTsZ$Qd\oeID&#Yavp"-:9ƒXp^fK*IF@߫3rFۺjx+&4zζZwsW;dwyǥu| >,6_ %_xLN4+nbXԍmlv|x8Dw5 Ql d@{AtZ!yQb^tY;0>F<<آ6B&!N![㛄񭿿a;bT""!r'/ҺX"{K.SfWtD?2 6Y|*D3Lo_Rޏ VqkCa_.55n<]!)p4s:=%)0Ijf7Q_UV( [SQcsUHٷ^~8AY ;h.96{Hx +?Z/@g;SҼPQ+3NǏNY`}Ӂco^xrÚ JSIN@CPw#qcٓ;U0R g{7a_}ޡ.wur@ V! %ѨR93z2U~2d ~f.؈,G@]4I`՛dvlSsnB6^03@ 'Rlq ܧWkg& d1}>Wigry> 8]bk04Skl^/gq{~h_xG/N5-J-G7¥~sRTeˊ1` RE_@DRr۝ЅJ|i t?ÕZw+74w٧_ =g6hyc1[b\_vU.HF -8d@T2%{j缨;qJWiX\R#ALiDž~e?AsuUF- HPK!H.{Gconseil-0.1.0.dist-info/RECORDu͒c@vv\5#L])r$x*E?OLˋSn6jǹQ}|WDSñ115conseil-0.1.0.dist-info/LICENSEPK!HڽTUQ:conseil-0.1.0.dist-info/WHEELPK!Hux/  :conseil-0.1.0.dist-info/METADATAPK!H.{G0Econseil-0.1.0.dist-info/RECORDPK*F