PK!"Xayyodoorpc_utils/__init__.py__version__ = '0.1.0' from .odoorpc_instantiator import OdooInstantiator from .fast_deep_queries import FastDeepQueries PK!!"odoorpc_utils/fast_deep_queries.pyimport concurrent.futures class FastDeepQueries: def __init__(self, odoo_obj, prefetched_metadata: dict = None, prefetch_model_names: list = None): self.strip_name_from__id_fields = False self.odoo = odoo_obj if prefetched_metadata: self.prefetched_metadata = prefetched_metadata else: self.prefetched_metadata = self.prefetch_metadata(prefetch_model_names) def set_prefetched_metadata(self, prefetched_metadata: dict): self.prefetched_metadata = prefetched_metadata def get_prefetched_metadata(self): return self.prefetched_metadata def prefetch_metadata(self, model_names: list = None): domain = [('ttype', 'in', ['one2many', 'many2one'])] if isinstance(model_names, list) and len(model_names) > 0: domain.append(['model', 'in', model_names]) fields = self.query_fast('ir.model.fields', domain=domain, fields=['name', 'model', 'relation']) # will keep relations on its own 'relations' entry to permit changing the cache format # when we implement support for many2many fields later on cache = dict() for f in fields: cache.setdefault(f['model'], {'relations': {}})['relations'][f['name']] = f['relation'] return cache def strip_name_from__id_fields(self, strip: bool = True): self.strip_name_from__id_fields = strip def query_fast(self, model_name, domain=None, ids=None, fields=None, order=None, limit=None, offset=None, strip_name_from__id_fields: bool = None): if strip_name_from__id_fields is None: strip_name_from__id_fields = self.strip_name_from__id_fields if domain is not None and ids is not None: domain.append(('id', 'in', ids)) ids = None if domain is None and ids is None: domain = [] method: str = None args = [] if domain is not None and fields is not None: method = 'search_read' args = [domain] elif domain is not None: method = 'search' args = [domain] elif ids is not None: method, args = ('read', [ids]) if fields else ('search', [[('id', 'in', ids)]]) if method is None: raise(RuntimeError('Wrong paramaters on call to function query_opt')) kwargs = {'context': self.odoo.env.context} if isinstance(fields, list): kwargs['fields'] = fields if order: kwargs['order'] = order if limit: kwargs['limit'] = limit if offset: kwargs['offset'] = offset resp = self.odoo.execute_kw(model_name, method, args, kwargs) if strip_name_from__id_fields: for r in resp: for k in r.keys(): if k[-3:] == '_id' and isinstance(r[k], list) and len(r[k]) == 2: r[k].pop() return resp def build_fields_deep_structure(self, model_name, fields_deep: list): def _parse(f): splitted_field = f.split(sep='.', maxsplit=1) mfs = [x.strip() for x in splitted_field[0].split(',') if len(x) > 0] rfs = (mfs[-1], splitted_field[1]) if len(splitted_field) == 2 and len(mfs) > 0 else None return mfs, rfs mfs = list() rfs = dict() for f in fields_deep: mf, rf = _parse(f) mfs.extend(mf) if rf is not None: if rfs.get(rf[0]) is None: rfs[rf[0]] = [self.prefetched_metadata[model_name]['relations'][rf[0]]] rfs[rf[0]].append(rf[1]) # Logic to support '*', '*.*', 'product_id.*'-like syntax if '*' in mfs: mfs = '*' star = rfs.get('*') if star is not None: for f in self.prefetched_metadata[model_name]['relations'].keys(): if rfs.get(f) is None: rfs[f] = [self.prefetched_metadata[model_name]['relations'][f]] rfs[f].extend(star) del rfs['*'] if rfs: rfs = {f: self.build_fields_deep_structure(rfs[f][0], rfs[f][1:]) for f in rfs} else: rfs = None return {'model_name': model_name, 'fields': list(set(mfs)), 'related_fields': rfs if rfs else None} def query_deep(self, fields_deep: dict, domain=None, ids=None, order=None, limit=None, offset=None, strip_name_from__id_fields: bool = False, n_threads: int = 6): assert (fields_deep is not None) if strip_name_from__id_fields is None: strip_name_from__id_fields = self.strip_name_from__id_fields retval = self.query_fast(model_name=fields_deep['model_name'], domain=domain, ids=ids, fields=fields_deep['fields'], order=order, limit=limit, offset=offset, strip_name_from__id_fields=strip_name_from__id_fields) related_fields = fields_deep.get('related_fields') if related_fields: rfield_names = related_fields.keys() related_ids = \ {f: list(set([i for r in retval for i in r.setdefault(f, [None]) or [None] if isinstance(i, int)])) for f in rfield_names} with concurrent.futures.ThreadPoolExecutor(max_workers=n_threads) as ex: calls = {ex.submit(self.query_deep, deep_fields=related_fields[f], ids=related_ids[f], strip_name_from__id_fields=strip_name_from__id_fields): f for f in rfield_names} cache = {} for future in concurrent.futures.as_completed(calls): f = calls[future] print('Model name: {} - field: {}'.format(fields_deep['model_name'], f)) data = future.result() cache[f] = dict(zip(map(lambda x: x['id'], data), data)) for r in retval: r[f] = [cache[f][id] for id in r[f] or [None] if isinstance(id, int)] return retval PK!Fzz%odoorpc_utils/odoorpc_instantiator.pyfrom configparser import ConfigParser import os import shlex CONF_FILENAMES = ['odoorpc-utils.ini', 'odooly.ini'] class OdooInstantiator: def __init__(self, odoorpc_obj, config_opts: dict = None): self.odoo = odoorpc_obj self.config_opts = config_opts def get_client(self): return self.odoo def get_configopts(self): return self.config_opts @classmethod def from_config_file(cls, library_name: str, section: str, path: str = None, **kwargs): kwargs['section'] = section kwargs['path'] = path if library_name == 'odoorpc': return cls.odoorpc_from_config_file(**kwargs) elif library_name == 'odooly': return cls.odooly_from_config_file(**kwargs) elif library_name == 'odoo_fastrpc': return cls.fastrpc_from_config_file(**kwargs) @classmethod def fastrpc_from_config_file(cls, section: str, path: str = None, n_threads: int = 8): # TO-DO: will be part of this package return None @classmethod def odoorpc_from_config_file(cls, section: str, path: str = None, opener=None): import odoorpc env = cls.parse_config(section, path) if env.get('host') is None: env['host'] = 'localhost' if env.get('protocol_odoorpc') is not None: env['protocol'] = env.get('protocol_odoorpc') if env.get('protocol') is None: env['protocol'] = 'jsonrpc+ssl' if env.get('scheme') == 'https' else 'jsonrpc' if env.get('port') is None: env['port'] = 8069 if env.get('timeout') is None: env['timeout'] = 120 if env.get('db') is None: env['db'] = env.get('database') if env.get('login') is None: env['login'] = env.get('user') odoo = odoorpc.ODOO(host=env['host'], protocol=env['protocol'], port=env['port'], timeout=env['timeout'], version=env.get('version'), opener=opener) odoo.login(env['db'], env['login'], env['password']) return cls(odoo, env) @classmethod def odooly_from_config_file(cls, section: str, path: str = None, user: str = None, transport=None, verbose: bool = False): import odooly env = cls.parse_config(section, path) if verbose is None: verbose = env.get('verbose') if env.get('scheme') is None: env['scheme'] = 'https' if env['scheme'] == 'local': env['server'] = shlex.split(env.get('options', '')) else: if env.get('protocol') is None: env['protocol'] = 'jsonrpc' env['server'] = '%s://%s:%s/%s' % (env['scheme'], env['host'], env['port'], env['protocol']) if env.get('database') is None and env.get('db') is not None: env['database'] = env['db'] if env.get('user') is None: env['user'] = env.get('login') if user is None else user elif user and user != env.get('user'): env['password'] = None client = odooly.Client(env['server'], user=env['user'], password=env['password'], transport=transport, verbose=verbose) client.env.name = section client.login() return cls(client, env) @classmethod def parse_config(cls, section: str, path: str = None): if path is None: for fn in CONF_FILENAMES: obj = cls.parse_config(section, os.path.join(os.curdir, fn)) if obj is not None: return obj return None if path[:1] != '/': path = os.path.join(os.curdir, path) p = ConfigParser() with open(path) as f: p.read_file(f) if section is None: return p.sections() return dict(p.items(section)) PK!r6}%odoorpc_utils-0.1.2.dist-info/LICENSECC0 LICENSE =========== To the extent possible under law, Marcelo Bello has waived all copyright and related or neighboring rights to odoorpc-utils. This work is published from: Brasil. PK!HڽTU#odoorpc_utils-0.1.2.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!HtL{&odoorpc_utils-0.1.2.dist-info/METADATAN1 E @EA2֞Z$bξLQRL,=5 G8dvIffnxqjP]L wr% v6Q<mڧv֪-?|Oϫ#_bc2A"n2fQ8RTkuniVWf6SSwQlD9Xbnߵkyiެ.nܨOPK!Hڑ]hF$odoorpc_utils-0.1.2.dist-info/RECORDr0},ZtA!TTP)ɤ%j AiV16t?pJ. }vr®ˌPG KA$+v$QR5 O Xa~׬oQN[by{Oeyթi?V pYm!B@T$kH@z7 bz'r >{\IwL.+ u-*…L艽ơ;ؿicz