Skip to content

Commit

Permalink
Release 0.6.8
Browse files Browse the repository at this point in the history
- bugfix in ``HTMLTable.to_csv`` for Python 3 related to writing non-ascii characters to csv file
- Link to new example added to readme
- session added property index_rev, which now is used to save index in file
- bugfix in ``utils.AttrDict.__dir__`` method. now it works allowing IPython auto-comlete for
  objects that use ``utils.AttrDict`` class
- better support of last IPython shell
- prefetching:
    - bugfix: some times when passed few fields with
              same names, prefetch raises strange errors (atleast on odoo 7.0 instance)
    - improvement: prefetch only records that have no atleast one field in cache
  • Loading branch information
katyukha committed Apr 27, 2016
2 parents 8826a6a + 45c774b commit 2915433
Show file tree
Hide file tree
Showing 16 changed files with 3,146 additions and 53 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.rst
@@ -1,3 +1,14 @@
0.6.8:
- bugfix in ``HTMLTable.to_csv`` for Python 3 related to writing non-ascii characters to csv file
- Link to new example added to readme
- session added property index_rev, which now is used to save index in file
- bugfix in ``utils.AttrDict.__dir__`` method. now it works allowing IPython auto-comlete for
objects that use ``utils.AttrDict`` class
- better support of last IPython shell
- prefetching:
- bugfix: some times when passed few fields with
same names, prefetch raises strange errors (atleast on odoo 7.0 instance)
- improvement: prefetch only records that have no atleast one field in cache
0.6.7:
- Representation module improvements
- HField: added ``is_header`` parameter, which in HTML representation wraps field in ``<th>`` tag
Expand Down
1 change: 1 addition & 0 deletions README.rst
Expand Up @@ -116,6 +116,7 @@ Examples

- `Basics <http://nbviewer.ipython.org/github/katyukha/openerp-proxy/blob/master/examples/Basics.ipynb>`_
- `Examples & HTML tests <http://nbviewer.ipython.org/github/katyukha/openerp-proxy/blob/master/examples/Examples%20&%20HTML%20tests.ipynb>`_
- `RecordList Representation <http://nbviewer.ipython.org/github/katyukha/openerp-proxy/blob/master/examples/RecordList%20Representation.ipynb>`_


Install
Expand Down
1 change: 1 addition & 0 deletions docs/source/intro.rst
Expand Up @@ -116,6 +116,7 @@ Examples

- `Basics <http://nbviewer.ipython.org/github/katyukha/openerp-proxy/blob/master/examples/Basics.ipynb>`_
- `Examples & HTML tests <http://nbviewer.ipython.org/github/katyukha/openerp-proxy/blob/master/examples/Examples%20&%20HTML%20tests.ipynb>`_
- `RecordList Representation <http://nbviewer.ipython.org/github/katyukha/openerp-proxy/blob/master/examples/RecordList%20Representation.ipynb>`_


Install
Expand Down
2,899 changes: 2,877 additions & 22 deletions examples/RecordList Representation.ipynb

Large diffs are not rendered by default.

17 changes: 12 additions & 5 deletions openerp_proxy/ext/repr/generic.py
Expand Up @@ -369,20 +369,27 @@ def to_csv(self):
if six.PY3:
adapt = lambda s: _(s)
fmode = 'wt'
tmp_file = tempfile.NamedTemporaryFile(
mode=fmode,
dir=CSV_PATH,
suffix='.csv',
encoding='utf-8',
delete=False)
else:
fmode = 'wb'
adapt = lambda s: _(s).encode('utf-8')
tmp_file = tempfile.NamedTemporaryFile(
mode=fmode,
dir=CSV_PATH,
suffix='.csv',
delete=False)

tmp_file = tempfile.NamedTemporaryFile(
mode=fmode,
dir=CSV_PATH,
suffix='.csv',
delete=False)
with tmp_file as csv_file:
csv_writer = csv.writer(csv_file)
csv_writer.writerow(tuple((adapt(h) for h in self.fields)))
for row in self:
csv_writer.writerow(tuple((adapt(val) for val in row)))

return FileLink(
os.path.join(CSV_PATH, os.path.split(tmp_file.name)[-1]))

Expand Down
9 changes: 4 additions & 5 deletions openerp_proxy/ext/repr/orm.py
Expand Up @@ -71,11 +71,10 @@ def as_html_table(self, *fields, **kwargs):
table = HTMLTable(self, fields, **kwargs)

# Prefetch available fields
# Disabled, in some cases, greatly reduce performance
# to_prefetch = (f._field
# for f in table.fields
# if isinstance(f._field, six.string_types))
# self._lcache.prefetch_fields(to_prefetch)
to_prefetch = (f._field
for f in table.fields
if isinstance(f._field, six.string_types))
self._lcache.prefetch_fields(to_prefetch)

return table

Expand Down
8 changes: 7 additions & 1 deletion openerp_proxy/main.py
Expand Up @@ -81,7 +81,13 @@ def main():
}
try:
from IPython import embed
embed(user_ns=_locals, header=header)
try:
from IPython.terminal.ipapp import load_default_config
ip_config = load_default_config()
except:
ip_config = None

embed(user_ns=_locals, header=header, config=ip_config)
except ImportError:
from code import interact
interact(local=_locals, banner=header)
Expand Down
48 changes: 35 additions & 13 deletions openerp_proxy/orm/cache.py
@@ -1,5 +1,4 @@
import six
import numbers
import collections

__all__ = ('empty_cache', 'Cache', 'ObjectCache')
Expand Down Expand Up @@ -30,6 +29,10 @@ def __missing__(self, key):
return self[key]

def update_keys(self, keys):
""" Add new IDs to cache.
:param list keys: list of new IDs to be added to cache
"""
if not self:
# for large amounts of data, this may be faster (no need for set
# and difference calls)
Expand All @@ -53,10 +56,20 @@ def update_context(self, new_context):
self._context.update(new_context)
return self.context

def get_ids_to_read(self, field):
""" Return list of ids, that have no specified field in cache
def get_ids_to_read(self, *fields):
""" Return list of ids, that have no at least one of specified
fields in cache
For example::
cache.get_ids_to_read('name', 'country_id', 'parent_id')
This code will traverse all record ids managed by this cache,
and find those that have no at least one field in cache.
This is highly useful in prefetching
"""
return [key for key, val in six.viewitems(self) if field not in val]
return [key for key, val in six.viewitems(self)
if any(( (field not in val) for field in fields))]

def cache_field(self, rid, ftype, field_name, value):
""" This method impelment additional caching functionality,
Expand All @@ -72,11 +85,15 @@ def cache_field(self, rid, ftype, field_name, value):
rcache = self._root_cache[self._object.
columns_info[field_name]['relation']]

if isinstance(value, numbers.Integral): # pragma: no cover
if isinstance(value, six.integer_types): # pragma: no cover
# internal dict {'id': key} will be created by default
# (see ObjectCache)
# (see ObjectCache.__missing__)
rcache[value]
elif isinstance(value, collections.Iterable):
elif isinstance(value, (list, tuple)):
# usualy for many2one fields odoo returns tuples like
# (id, name), where id is ID of remote record, and name
# is human readable name of record (result of name_get method)
# so we cache this name for futher usage too
rcache[value[0]]['__name_get_result'] = value[1]
elif value and ftype in ('many2many', 'one2many'):
rcache = self._root_cache[self._object.
Expand All @@ -96,19 +113,19 @@ def parse_prefetch_fields(self, fields):
``{'related.object': ['relatedfield1', 'relatedfield2.relatedfield']}``
"""
rel_fields = collections.defaultdict(list)
prefetch_fields = []
prefetch_fields = set()
for field in fields:
field_path = field.split('.', 1)
xfield = field_path.pop(0)
xfield_info = self._object.columns_info.get(xfield, None)
if xfield_info is not None:
prefetch_fields.append(xfield)
prefetch_fields.add(xfield)
relation = xfield_info.get('relation', False)
if field_path and relation:
# only one item left
rel_fields[relation].append(field_path[0])

return prefetch_fields, rel_fields
return list(prefetch_fields), rel_fields

def prefetch_fields(self, fields):
""" Prefetch specified fields for this cache.
Expand All @@ -124,7 +141,8 @@ def prefetch_fields(self, fields):
to_prefetch, related = self.parse_prefetch_fields(fields)

col_info = self._object.columns_info
for data in self._object.read(list(self), to_prefetch):
for data in self._object.read(self.get_ids_to_read(*to_prefetch),
to_prefetch):
for field, value in data.items():

# Fill related cache
Expand All @@ -138,7 +156,11 @@ def prefetch_fields(self, fields):


class Cache(dict):
""" Cache to be used for Record's data
""" Cache to be used for Record's data.
This is root cache, which manages model local cache
cache['res.partner'] -> ObjectCache('res.partner')
"""
__slots__ = ('_client',)

Expand All @@ -148,7 +170,7 @@ def __init__(self, client, *args, **kwargs):

@property
def client(self):
""" Access to Client instance this cache is belongs to
""" Access to Client instance this cache belongs to
"""
return self._client

Expand Down
2 changes: 2 additions & 0 deletions openerp_proxy/orm/object.py
Expand Up @@ -45,6 +45,8 @@ class Object(six.with_metaclass(ObjectType, DirMixIn)):
"""

__slots__ = ('_service', '_obj_name', '_columns_info')

def __init__(self, service, object_name):
self._service = service
self._obj_name = object_name
Expand Down
4 changes: 2 additions & 2 deletions openerp_proxy/orm/record.py
Expand Up @@ -393,7 +393,7 @@ def __init__(self, obj, ids=None, fields=None, cache=None, context=None):

# We need to add these ids to cache to make prefetching and data
# reading work correctly. if some of ids will not be present in cache,
# then, on acces to field of record with such id, data will not been
# then, on access to field of record with such id, data will not be
# read from database.
# Look into *Record._get_field* method for more info
self._lcache.update_keys(ids)
Expand Down Expand Up @@ -742,7 +742,7 @@ def prefetch(self, *fields):
"""
fields = fields if fields else self.object.simple_fields

self._cache[self.object.name].prefetch_fields(fields)
self._lcache.prefetch_fields(fields)

return self

Expand Down
2 changes: 1 addition & 1 deletion openerp_proxy/plugins/external_ids.py
Expand Up @@ -102,5 +102,5 @@ def get_record(self, xml_id, module=None):
e_record = self.get_for(xml_id, module=module)
if e_record:
e_record = e_record[0]
return self.client[e_record.model][e_record.res_id]
return self.client[e_record.model].browse(e_record.res_id)
return False
13 changes: 12 additions & 1 deletion openerp_proxy/session.py
Expand Up @@ -193,6 +193,17 @@ def index(self):
self._index_url(url)
return dict(self._db_index)

@property
def index_rev(self):
""" Reverse index.
Property which returns dict with {url: index}
"""
if not self._db_index_rev:
for url in self._databases.keys():
self._index_url(url)
return dict(self._db_index_rev)

def _index_url(self, url):
""" Returns index of specified URL, or adds it to
store assigning new index
Expand Down Expand Up @@ -360,7 +371,7 @@ def save(self):
'databases': databases,
'aliases': self._db_aliases,
'options': self._options,
'index': self._db_index_rev,
'index': self.index_rev,
}

json_write(self.data_file, data, indent=4)
Expand Down

0 comments on commit 2915433

Please sign in to comment.