tryton 7.0.7__py3-none-any.whl → 7.2.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tryton might be problematic. Click here for more details.

Files changed (100) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +137 -69
  4. tryton/common/completion.py +2 -2
  5. tryton/common/datetime_.py +3 -1
  6. tryton/common/domain_inversion.py +2 -1
  7. tryton/common/domain_parser.py +17 -7
  8. tryton/common/selection.py +6 -3
  9. tryton/common/tempfile.py +34 -0
  10. tryton/config.py +4 -5
  11. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  12. tryton/data/locale/bg/LC_MESSAGES/tryton.po +42 -4
  13. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/ca/LC_MESSAGES/tryton.po +47 -8
  15. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/cs/LC_MESSAGES/tryton.po +42 -4
  17. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/de/LC_MESSAGES/tryton.po +45 -6
  19. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/es/LC_MESSAGES/tryton.po +46 -7
  21. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +43 -5
  23. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/et/LC_MESSAGES/tryton.po +46 -6
  25. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -7
  27. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/fi/LC_MESSAGES/tryton.po +41 -4
  29. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/fr/LC_MESSAGES/tryton.po +46 -7
  31. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/hu/LC_MESSAGES/tryton.po +46 -6
  33. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  34. tryton/data/locale/id/LC_MESSAGES/tryton.po +43 -4
  35. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  36. tryton/data/locale/it/LC_MESSAGES/tryton.po +45 -6
  37. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/lo/LC_MESSAGES/tryton.po +45 -6
  40. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/lt/LC_MESSAGES/tryton.po +46 -6
  42. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/nl/LC_MESSAGES/tryton.po +46 -7
  44. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/pl/LC_MESSAGES/tryton.po +84 -60
  46. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/pt/LC_MESSAGES/tryton.po +45 -6
  48. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/ro/LC_MESSAGES/tryton.po +57 -17
  50. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/ru/LC_MESSAGES/tryton.po +43 -6
  52. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/sl/LC_MESSAGES/tryton.po +46 -5
  54. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  55. tryton/data/locale/tr/LC_MESSAGES/tryton.po +41 -4
  56. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  57. tryton/data/locale/uk/LC_MESSAGES/tryton.po +46 -6
  58. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  59. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +46 -5
  60. tryton/device_cookie.py +1 -1
  61. tryton/gui/main.py +3 -2
  62. tryton/gui/window/about.py +1 -1
  63. tryton/gui/window/dblogin.py +2 -2
  64. tryton/gui/window/email_.py +1 -1
  65. tryton/gui/window/form.py +6 -4
  66. tryton/gui/window/log.py +24 -2
  67. tryton/gui/window/view_form/model/field.py +84 -34
  68. tryton/gui/window/view_form/model/group.py +3 -1
  69. tryton/gui/window/view_form/model/record.py +64 -15
  70. tryton/gui/window/view_form/screen/screen.py +83 -46
  71. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
  72. tryton/gui/window/view_form/view/form.py +6 -12
  73. tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
  74. tryton/gui/window/view_form/view/form_gtk/dictionary.py +37 -24
  75. tryton/gui/window/view_form/view/form_gtk/document.py +9 -10
  76. tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
  77. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  78. tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
  79. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  80. tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
  81. tryton/gui/window/view_form/view/list.py +68 -35
  82. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
  83. tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
  84. tryton/gui/window/view_form/view/screen_container.py +3 -5
  85. tryton/gui/window/win_export.py +1 -2
  86. tryton/gui/window/win_form.py +9 -7
  87. tryton/gui/window/win_import.py +9 -4
  88. tryton/gui/window/wizard.py +13 -10
  89. tryton/jsonrpc.py +46 -28
  90. tryton/plugins/__init__.py +5 -3
  91. tryton/pyson.py +55 -5
  92. tryton/rpc.py +18 -0
  93. tryton/tests/test_common_domain_parser.py +8 -0
  94. tryton/translate.py +5 -2
  95. {tryton-7.0.7.data → tryton-7.2.13.data}/scripts/tryton +8 -7
  96. {tryton-7.0.7.dist-info → tryton-7.2.13.dist-info}/METADATA +6 -6
  97. {tryton-7.0.7.dist-info → tryton-7.2.13.dist-info}/RECORD +100 -98
  98. {tryton-7.0.7.dist-info → tryton-7.2.13.dist-info}/WHEEL +1 -1
  99. {tryton-7.0.7.dist-info → tryton-7.2.13.dist-info}/LICENSE +0 -0
  100. {tryton-7.0.7.dist-info → tryton-7.2.13.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,8 @@ from tryton.common.cellrenderertext import (
23
23
  CellRendererText, CellRendererTextCompletion)
24
24
  from tryton.common.cellrenderertoggle import CellRendererToggle
25
25
  from tryton.common.completion import get_completion, update_completion
26
- from tryton.common.datetime_ import CellRendererDate, CellRendererTime
26
+ from tryton.common.datetime_ import (
27
+ CellRendererDate, CellRendererTime, date_parse)
27
28
  from tryton.common.domain_parser import quote
28
29
  from tryton.common.selection import (
29
30
  PopdownMixin, SelectionMixin, selection_shortcuts)
@@ -214,8 +215,12 @@ class Affix(Cell):
214
215
  else:
215
216
  value = self.icon
216
217
  if self.attrs.get('icon_type') == 'url':
218
+ def callback(pixbuf):
219
+ self.display_counters.pop(record.id, None)
220
+ self.view.treeview.queue_resize() # trigger a redraw
217
221
  pixbuf = common.IconFactory.get_pixbuf_url(
218
- value, size_param=self.attrs.get('url_size'))
222
+ value, size_param=self.attrs.get('url_size'),
223
+ callback=callback)
219
224
  else:
220
225
  pixbuf = common.IconFactory.get_pixbuf(
221
226
  value, Gtk.IconSize.BUTTON)
@@ -376,6 +381,14 @@ class GenericText(Cell):
376
381
  editable.connect('remove-widget', remove)
377
382
  return False
378
383
 
384
+ def get_editable(self, renderer):
385
+ if self.renderer == renderer:
386
+ return self.editable
387
+ for cell in self.prefixes + self.suffixes:
388
+ editable = cell.get_editable(renderer)
389
+ if editable:
390
+ return editable
391
+
379
392
 
380
393
  class Char(GenericText):
381
394
 
@@ -401,19 +414,19 @@ class Int(GenericText):
401
414
  self.symbol = attrs.get('symbol')
402
415
  self.grouping = bool(int(attrs.get('grouping', 1)))
403
416
  if self.symbol:
404
- self.renderer_prefix = Symbol(view, attrs, 0)
405
- self.renderer_suffix = Symbol(view, attrs, 1)
417
+ self._cell_prefix = Symbol(view, attrs, 0)
418
+ self._cell_suffix = Symbol(view, attrs, 1)
406
419
 
407
420
  @property
408
421
  def prefixes(self):
409
422
  if self.symbol:
410
- return [self.renderer_prefix]
423
+ return [self._cell_prefix]
411
424
  return []
412
425
 
413
426
  @property
414
427
  def suffixes(self):
415
428
  if self.symbol:
416
- return [self.renderer_suffix]
429
+ return [self._cell_suffix]
417
430
  return []
418
431
 
419
432
  @catch_errors()
@@ -493,6 +506,17 @@ class Date(GenericText):
493
506
  else:
494
507
  return ''
495
508
 
509
+ def value_from_text(self, record, text, callback=None):
510
+ if isinstance(text, str):
511
+ field = record[self.attrs['name']]
512
+ try:
513
+ # Use a datetime instance and rely on field to convert to the
514
+ # proper type
515
+ text = date_parse(text, self.get_format(record, field))
516
+ except (ValueError, OverflowError):
517
+ text = None
518
+ return super().value_from_text(record, text, callback=callback)
519
+
496
520
 
497
521
  class Time(Date):
498
522
 
@@ -554,20 +578,20 @@ class Binary(GenericText):
554
578
  super(Binary, self).__init__(view, attrs, renderer=renderer)
555
579
  self.renderer.set_property('editable', False)
556
580
  self.renderer.set_property('xalign', self.align)
557
- self.renderer_save = _BinarySave(self)
558
- self.renderer_select = _BinarySelect(self)
581
+ self._cell_save = _BinarySave(self)
582
+ self._cell_select = _BinarySelect(self)
559
583
  if self.attrs.get('filename'):
560
- self.renderer_open = _BinaryOpen(self)
584
+ self._cell_open = _BinaryOpen(self)
561
585
  else:
562
- self.renderer_open = None
586
+ self._cell_open = None
563
587
 
564
588
  @property
565
589
  def prefixes(self):
566
- return filter(None, [self.renderer_open])
590
+ return filter(None, [self._cell_open])
567
591
 
568
592
  @property
569
593
  def suffixes(self):
570
- return [self.renderer_save, self.renderer_select]
594
+ return [self._cell_save, self._cell_select]
571
595
 
572
596
  @catch_errors()
573
597
  def get_textual_value(self, record):
@@ -718,7 +742,7 @@ class _BinarySelect(_BinaryIcon):
718
742
  invisible = field.get_state_attrs(record).get('invisible', False)
719
743
  readonly = self.attrs.get('readonly',
720
744
  field.get_state_attrs(record).get('readonly', False))
721
- if readonly and size:
745
+ if readonly or size:
722
746
  cell.set_property('visible', False)
723
747
  else:
724
748
  cell.set_property('visible', not invisible)
@@ -802,6 +826,7 @@ class M2O(GenericText):
802
826
  if renderer is None and int(attrs.get('completion', 1)):
803
827
  renderer = partial(CellRendererTextCompletion, self.set_completion)
804
828
  super(M2O, self).__init__(view, attrs, renderer=renderer)
829
+ self._popup = False
805
830
 
806
831
  def get_model(self, record, field):
807
832
  return self.attrs['relation']
@@ -830,7 +855,8 @@ class M2O(GenericText):
830
855
  if model and common.get_toplevel_window().get_focus():
831
856
  field = record[self.attrs['name']]
832
857
  win = self.search_remote(record, field, text, callback=callback)
833
- win.show()
858
+ if win:
859
+ win.show()
834
860
 
835
861
  def editing_started(self, cell, editable, path):
836
862
  super(M2O, self).editing_started(cell, editable, path)
@@ -899,7 +925,9 @@ class M2O(GenericText):
899
925
  domain = field.domain_get(record)
900
926
  context = field.get_context(record)
901
927
  if not create and changed:
902
- self.search_remote(record, field, text, callback=callback).show()
928
+ win = self.search_remote(record, field, text, callback=callback)
929
+ if win:
930
+ win.show()
903
931
  return
904
932
  target_id = self.id_from_value(field.get(record))
905
933
 
@@ -939,6 +967,11 @@ class M2O(GenericText):
939
967
  access = common.MODELACCESS[model]
940
968
  create_access = int(self.attrs.get('create', 1)) and access['create']
941
969
 
970
+ if self._popup:
971
+ return
972
+ else:
973
+ self._popup = True
974
+
942
975
  def search_callback(found):
943
976
  value = None
944
977
  if found:
@@ -946,6 +979,7 @@ class M2O(GenericText):
946
979
  field.set_client(record, value)
947
980
  if callback:
948
981
  callback()
982
+ self._popup = False
949
983
  win = WinSearch(model, search_callback, sel_multi=False,
950
984
  context=context, domain=domain,
951
985
  order=order, view_ids=self.attrs.get('view_ids', '').split(','),
@@ -1233,11 +1267,11 @@ class Reference(M2O):
1233
1267
 
1234
1268
  def __init__(self, view, attrs, renderer=None):
1235
1269
  super(Reference, self).__init__(view, attrs, renderer=renderer)
1236
- self.renderer_selection = _ReferenceSelection(view, attrs)
1270
+ self._cell_selection = _ReferenceSelection(view, attrs)
1237
1271
 
1238
1272
  @property
1239
1273
  def prefixes(self):
1240
- return [self.renderer_selection]
1274
+ return [self._cell_selection]
1241
1275
 
1242
1276
  def get_model(self, record, field):
1243
1277
  value = field.get_client(record)
@@ -1385,12 +1419,13 @@ class Button(Cell):
1385
1419
  cell.set_property('visible', not invisible)
1386
1420
  readonly = states.get('readonly', False)
1387
1421
  cell.set_property('sensitive', not readonly)
1388
- parent = record.parent if record else None
1389
- while parent:
1390
- if parent.modified:
1391
- cell.set_property('sensitive', False)
1392
- break
1393
- parent = parent.parent
1422
+ if self.attrs.get('type', 'class') == 'class':
1423
+ parent = record.parent if record else None
1424
+ while parent:
1425
+ if parent.modified:
1426
+ cell.set_property('sensitive', False)
1427
+ break
1428
+ parent = parent.parent
1394
1429
  # TODO icon
1395
1430
  self._set_visual(cell, record)
1396
1431
 
@@ -185,7 +185,8 @@ class Selection(Gtk.ScrolledWindow):
185
185
 
186
186
  class ScreenContainer(object):
187
187
 
188
- def __init__(self, tab_domain):
188
+ def __init__(self, screen, tab_domain):
189
+ self.screen = screen
189
190
  self.viewport = Gtk.Viewport()
190
191
  self.viewport.set_shadow_type(Gtk.ShadowType.NONE)
191
192
  self.vbox = Gtk.VBox(spacing=3)
@@ -351,12 +352,9 @@ class ScreenContainer(object):
351
352
  def widget_get(self):
352
353
  return self.vbox
353
354
 
354
- def set_screen(self, screen):
355
- self.screen = screen
355
+ def show_filter(self):
356
356
  self.but_bookmark.set_sensitive(bool(list(self.bookmarks())))
357
357
  self.bookmark_match()
358
-
359
- def show_filter(self):
360
358
  if self.filter_vbox:
361
359
  self.filter_vbox.show()
362
360
  if self.notebook:
@@ -7,7 +7,6 @@ import gettext
7
7
  import json
8
8
  import locale
9
9
  import os
10
- import tempfile
11
10
  import urllib.parse
12
11
  from itertools import zip_longest
13
12
  from numbers import Number
@@ -15,7 +14,7 @@ from numbers import Number
15
14
  from gi.repository import Gdk, GObject, Gtk
16
15
 
17
16
  import tryton.common as common
18
- from tryton.common import RPCException, RPCExecute
17
+ from tryton.common import RPCException, RPCExecute, tempfile
19
18
  from tryton.config import CONFIG
20
19
  from tryton.gui.window.win_csv import WinCSV
21
20
  from tryton.jsonrpc import JSONEncoder
@@ -69,7 +69,7 @@ class WinForm(NoModal, InfoBar):
69
69
  self.accel_group = Gtk.AccelGroup()
70
70
  self.win.add_accel_group(self.accel_group)
71
71
 
72
- readonly = self.screen.readonly or self.screen.group.readonly
72
+ readonly = self.screen.group.readonly
73
73
 
74
74
  self.but_ok = None
75
75
  self.but_new = None
@@ -360,7 +360,7 @@ class WinForm(NoModal, InfoBar):
360
360
  deletable = True
361
361
  if self.screen.current_record:
362
362
  deletable = self.screen.current_record.deletable
363
- readonly = self.screen.readonly or self.screen.group.readonly
363
+ readonly = self.screen.group.readonly
364
364
  if position >= 1:
365
365
  name = str(position)
366
366
  if self.domain is not None:
@@ -373,9 +373,11 @@ class WinForm(NoModal, InfoBar):
373
373
  self.but_pre.set_sensitive(True)
374
374
  else:
375
375
  self.but_pre.set_sensitive(False)
376
- if access['delete'] and not readonly and deletable:
377
- self.but_del.set_sensitive(True)
378
- self.but_undel.set_sensitive(True)
376
+ self.but_del.set_sensitive(bool(
377
+ not readonly
378
+ and access['delete']
379
+ and deletable))
380
+ self.but_undel.set_sensitive(bool(not readonly))
379
381
  else:
380
382
  self.but_del.set_sensitive(False)
381
383
  self.but_undel.set_sensitive(False)
@@ -399,7 +401,7 @@ class WinForm(NoModal, InfoBar):
399
401
  cancel_responses = [
400
402
  Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]
401
403
  self.screen.current_view.set_value()
402
- readonly = self.screen.readonly or self.screen.group.readonly
404
+ readonly = self.screen.group.readonly
403
405
  if (response_id not in cancel_responses
404
406
  and not readonly
405
407
  and self.screen.current_record is not None):
@@ -445,7 +447,7 @@ class WinForm(NoModal, InfoBar):
445
447
  record.modified_fields.setdefault('id')
446
448
  result = False
447
449
  else:
448
- result = response_id not in cancel_responses
450
+ result = (response_id not in cancel_responses) and not readonly
449
451
  if self.callback:
450
452
  self.callback(result)
451
453
  self.destroy()
@@ -2,6 +2,7 @@
2
2
  # this repository contains the full copyright notices and license terms.
3
3
  import base64
4
4
  import csv
5
+ import datetime as dt
5
6
  import gettext
6
7
  import locale
7
8
  from decimal import Decimal
@@ -10,7 +11,6 @@ from gi.repository import Gtk
10
11
 
11
12
  import tryton.common as common
12
13
  from tryton.common import RPCException, RPCExecute
13
- from tryton.common.datetime_ import date_parse
14
14
  from tryton.gui.window.win_csv import WinCSV
15
15
 
16
16
  _ = gettext.gettext
@@ -212,13 +212,18 @@ class WinImport(WinCSV):
212
212
  val = locale.atof(val)
213
213
  elif type_ == 'numeric':
214
214
  val = Decimal(locale.delocalize(val))
215
- elif type_ in ['date', 'datetime']:
216
- val = date_parse(val, common.date_format())
215
+ elif type_ == 'date':
216
+ val = dt.datetime.strptime(
217
+ val, common.date_format()).date()
218
+ elif type_ == 'datetime':
219
+ val = dt.datetime.strptime(
220
+ val, common.date_format() + ' %X')
217
221
  elif type_ == 'binary':
218
222
  val = base64.b64decode(val)
219
223
  row.append(val)
220
224
  data.append(row)
221
- except (IOError, UnicodeDecodeError, csv.Error) as exception:
225
+ except (IOError, UnicodeDecodeError, csv.Error, ValueError) \
226
+ as exception:
222
227
  common.warning(str(exception), _("Import failed"))
223
228
  return
224
229
  try:
@@ -150,16 +150,17 @@ class Wizard(InfoBar):
150
150
 
151
151
  def end(self, callback=None):
152
152
  def end_callback(action):
153
- self.destroy(action=action())
154
- if callback:
155
- callback()
156
- try:
157
- RPCExecute('wizard', self.action, 'delete', self.session_id,
158
- process_exception=False, callback=end_callback)
159
- except Exception:
160
- logger.warn(
161
- "Unable to delete session %s of wizard %s",
162
- self.session_id, self.action, exc_info=True)
153
+ try:
154
+ self.destroy(action=action())
155
+ if callback:
156
+ callback()
157
+ except RPCException:
158
+ logger.warn(
159
+ "Unable to delete session %s of wizard %s",
160
+ self.session_id, self.action, exc_info=True)
161
+ RPCExecute(
162
+ 'wizard', self.action, 'delete', self.session_id,
163
+ process_exception=False, callback=end_callback)
163
164
 
164
165
  def clean(self):
165
166
  for widget in self.widget.get_children():
@@ -402,6 +403,8 @@ class WizardDialog(Wizard, NoModal):
402
403
  return True
403
404
 
404
405
  def show(self):
406
+ if not self.screen:
407
+ return
405
408
  view = self.screen.current_view
406
409
  if view.view_type == 'form':
407
410
  expand = False
tryton/jsonrpc.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import base64
4
4
  import datetime
5
5
  import errno
6
+ import gettext
6
7
  import hashlib
7
8
  import http.client
8
9
  import json
@@ -11,17 +12,20 @@ import socket
11
12
  import ssl
12
13
  import threading
13
14
  import xmlrpc.client
14
- from collections import defaultdict
15
15
  from contextlib import contextmanager
16
16
  from decimal import Decimal
17
17
  from functools import partial, reduce
18
18
  from urllib.parse import quote, urljoin
19
19
 
20
+ from .cache import CacheDict
21
+ from .config import CONFIG
22
+
20
23
  __all__ = ["ResponseError", "Fault", "ProtocolError", "Transport",
21
24
  "ServerProxy", "ServerPool"]
22
25
  CONNECT_TIMEOUT = 5
23
26
  DEFAULT_TIMEOUT = None
24
27
  logger = logging.getLogger(__name__)
28
+ _ = gettext.gettext
25
29
 
26
30
 
27
31
  def deepcopy(obj):
@@ -138,7 +142,6 @@ class JSONUnmarshaller(object):
138
142
  class Transport(xmlrpc.client.SafeTransport):
139
143
 
140
144
  accept_gzip_encoding = True
141
- encode_threshold = 1400 # common MTU
142
145
 
143
146
  def __init__(
144
147
  self, fingerprints=None, ca_certs=None, session=None):
@@ -194,34 +197,38 @@ class Transport(xmlrpc.client.SafeTransport):
194
197
  ssl_ctx = ssl.create_default_context(cafile=self.__ca_certs)
195
198
 
196
199
  def http_connection():
197
- self._connection = host, http.client.HTTPConnection(chost,
198
- timeout=CONNECT_TIMEOUT)
199
- self._connection[1].connect()
200
- sock = self._connection[1].sock
201
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
202
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
200
+ connection = http.client.HTTPConnection(
201
+ chost, timeout=CONNECT_TIMEOUT)
202
+ self._connection = host, connection
203
+ connection.connect()
204
+ sock = connection.sock
205
+ if sock:
206
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
207
+ return connection
203
208
 
204
209
  def https_connection(allow_http=False):
205
- self._connection = host, http.client.HTTPSConnection(chost,
206
- timeout=CONNECT_TIMEOUT, context=ssl_ctx)
210
+ connection = http.client.HTTPSConnection(
211
+ chost, timeout=CONNECT_TIMEOUT, context=ssl_ctx)
212
+ self._connection = host, connection
207
213
  try:
208
- self._connection[1].connect()
209
- sock = self._connection[1].sock
210
- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
211
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
212
- try:
213
- peercert = sock.getpeercert(True)
214
- except socket.error:
215
- peercert = None
214
+ connection.connect()
215
+ sock = connection.sock
216
+ if sock:
217
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
218
+ try:
219
+ peercert = sock.getpeercert(True)
220
+ except socket.error:
221
+ peercert = None
216
222
 
217
223
  def format_hash(value):
218
224
  return reduce(lambda x, y: x + y[1].upper()
219
225
  + ((y[0] % 2 and y[0] + 1 < len(value)) and ':' or ''),
220
226
  enumerate(value), '')
221
- return format_hash(hashlib.sha1(peercert).hexdigest())
227
+ return connection, format_hash(
228
+ hashlib.sha1(peercert).hexdigest())
222
229
  except (socket.error, ssl.SSLError, ssl.CertificateError):
223
230
  if allow_http:
224
- http_connection()
231
+ return http_connection(), None
225
232
  else:
226
233
  raise
227
234
 
@@ -229,17 +236,24 @@ class Transport(xmlrpc.client.SafeTransport):
229
236
  if (self.__fingerprints is not None
230
237
  and self.__fingerprints.exists(chost)):
231
238
  if self.__fingerprints.get(chost):
232
- fingerprint = https_connection()
239
+ connection, fingerprint = https_connection()
233
240
  else:
234
- http_connection()
241
+ connection = http_connection()
235
242
  else:
236
- fingerprint = https_connection(allow_http=True)
243
+ connection, fingerprint = https_connection(allow_http=True)
237
244
 
238
245
  if self.__fingerprints is not None:
239
246
  self.__fingerprints.set(chost, fingerprint)
240
- self._connection[1].timeout = DEFAULT_TIMEOUT
241
- self._connection[1].sock.settimeout(DEFAULT_TIMEOUT)
242
- return self._connection[1]
247
+ connection.timeout = DEFAULT_TIMEOUT
248
+ sock = connection.sock
249
+ if sock:
250
+ sock.settimeout(DEFAULT_TIMEOUT)
251
+ return connection
252
+
253
+ @property
254
+ def encode_threshold(self):
255
+ if self.session:
256
+ return 1400 # common MTU
243
257
 
244
258
 
245
259
  class ServerProxy(xmlrpc.client.ServerProxy):
@@ -298,7 +312,8 @@ class ServerProxy(xmlrpc.client.ServerProxy):
298
312
  self.__transport.close()
299
313
  raise
300
314
  if response['id'] != id_:
301
- raise ResponseError('Invalid response id (%s) excpected %s' %
315
+ raise ResponseError(
316
+ _("Invalid response id (%s) expected %s") %
302
317
  (response['id'], id_))
303
318
  if response.get('error'):
304
319
  raise Fault(*response['error'])
@@ -392,7 +407,10 @@ class ServerPool(object):
392
407
  class _Cache:
393
408
 
394
409
  def __init__(self):
395
- self.store = defaultdict(dict)
410
+ cache_size = CONFIG['rpc.cache_size']
411
+ self.store = CacheDict(
412
+ cache_len=cache_size,
413
+ default_factory=lambda: CacheDict(cache_len=cache_size))
396
414
 
397
415
  def cached(self, prefix):
398
416
  return prefix in self.store
@@ -24,9 +24,11 @@ def register():
24
24
  imported = set()
25
25
  for path in paths:
26
26
  finder = importlib.machinery.FileFinder(
27
- path, (
28
- importlib.machinery.SourceFileLoader,
29
- importlib.machinery.SOURCE_SUFFIXES))
27
+ path,
28
+ (importlib.machinery.SourceFileLoader,
29
+ importlib.machinery.SOURCE_SUFFIXES),
30
+ (importlib.machinery.SourcelessFileLoader,
31
+ importlib.machinery.BYTECODE_SUFFIXES))
30
32
  for plugin in os.listdir(path):
31
33
  module = os.path.splitext(plugin)[0]
32
34
  if (module.startswith('_') or module in imported):
tryton/pyson.py CHANGED
@@ -9,6 +9,8 @@ from dateutil.relativedelta import relativedelta
9
9
 
10
10
 
11
11
  class PYSON(object):
12
+ _operator = None
13
+ _binary_operator = None
12
14
 
13
15
  def pyson(self):
14
16
  raise NotImplementedError
@@ -81,8 +83,17 @@ class PYSON(object):
81
83
  return In(k, self)
82
84
 
83
85
  def __repr__(self):
84
- klass = self.__class__.__name__
85
- return '%s(%s)' % (klass, ', '.join(map(repr, self.__repr_params__)))
86
+ params = self.__repr_params__
87
+ if self._operator and isinstance(params[0], PYSON):
88
+ return '%s.%s(%s)' % (
89
+ repr(params[0]), self._operator,
90
+ ', '.join(map(repr, params[1:])))
91
+ elif self._binary_operator and isinstance(params[0], PYSON):
92
+ return '(%s %s %s)' % (
93
+ repr(params[0]), self._binary_operator, repr(params[1]))
94
+ else:
95
+ klass = self.__class__.__name__
96
+ return '%s(%s)' % (klass, ', '.join(map(repr, params)))
86
97
 
87
98
  @property
88
99
  def __repr_params__(self):
@@ -137,7 +148,10 @@ class Eval(PYSON):
137
148
 
138
149
  @property
139
150
  def __repr_params__(self):
140
- return self._value, self._default
151
+ params = (self._value,)
152
+ if isinstance(self._default, PYSON) or self._default != '':
153
+ params += (self._default,)
154
+ return params
141
155
 
142
156
  def pyson(self):
143
157
  return {
@@ -183,6 +197,17 @@ class Not(PYSON):
183
197
  v = bool(v)
184
198
  self._value = v
185
199
 
200
+ def __repr__(self):
201
+ if (isinstance(self._value, Equal)
202
+ and isinstance(self._value._statement1, PYSON)):
203
+ return '(%s != %s)' % (
204
+ repr(self._value._statement1),
205
+ repr(self._value._statement2))
206
+ elif isinstance(self._value, PYSON):
207
+ return '~%s' % repr(self._value)
208
+ else:
209
+ return super().__repr__()
210
+
186
211
  @property
187
212
  def __repr_params__(self):
188
213
  return (self._value,)
@@ -198,7 +223,7 @@ class Not(PYSON):
198
223
 
199
224
  @staticmethod
200
225
  def eval(dct, context):
201
- return not dct['v']
226
+ return not Bool(dct['v']).eval(dct, context)
202
227
 
203
228
 
204
229
  class Bool(PYSON):
@@ -226,6 +251,8 @@ class Bool(PYSON):
226
251
 
227
252
 
228
253
  class And(PYSON):
254
+ _pyson_class = 'And'
255
+ _binary_operator = '&'
229
256
 
230
257
  def __init__(self, *statements, **kwargs):
231
258
  super(And, self).__init__()
@@ -258,6 +285,7 @@ class And(PYSON):
258
285
 
259
286
 
260
287
  class Or(And):
288
+ _binary_operator = '|'
261
289
 
262
290
  def pyson(self):
263
291
  res = super(Or, self).pyson()
@@ -270,6 +298,7 @@ class Or(And):
270
298
 
271
299
 
272
300
  class Equal(PYSON):
301
+ _binary_operator = '=='
273
302
 
274
303
  def __init__(self, s1, s2):
275
304
  statement1, statement2 = s1, s2
@@ -332,6 +361,10 @@ class Greater(PYSON):
332
361
  self._statement2 = statement2
333
362
  self._equal = equal
334
363
 
364
+ @property
365
+ def _binary_operator(self):
366
+ return '>=' if self._equal else '>'
367
+
335
368
  @property
336
369
  def __repr_params__(self):
337
370
  return (self._statement1, self._statement2, self._equal)
@@ -378,6 +411,10 @@ class Greater(PYSON):
378
411
 
379
412
  class Less(Greater):
380
413
 
414
+ @property
415
+ def _binary_operator(self):
416
+ return '<=' if self._equal else '<'
417
+
381
418
  def pyson(self):
382
419
  res = super(Less, self).pyson()
383
420
  res['__class__'] = 'Less'
@@ -440,6 +477,7 @@ class If(PYSON):
440
477
 
441
478
 
442
479
  class Get(PYSON):
480
+ _operator = 'get'
443
481
 
444
482
  def __init__(self, v, k, d=''):
445
483
  obj, key, default = v, k, d
@@ -458,7 +496,10 @@ class Get(PYSON):
458
496
 
459
497
  @property
460
498
  def __repr_params__(self):
461
- return (self._obj, self._key, self._default)
499
+ params = (self._obj, self._key)
500
+ if self._default != '':
501
+ params += (self._default,)
502
+ return params
462
503
 
463
504
  def pyson(self):
464
505
  return {
@@ -480,6 +521,7 @@ class Get(PYSON):
480
521
 
481
522
 
482
523
  class In(PYSON):
524
+ _operator = 'in_'
483
525
 
484
526
  def __init__(self, k, v):
485
527
  key, obj = k, v
@@ -508,6 +550,14 @@ class In(PYSON):
508
550
  self._key = key
509
551
  self._obj = obj
510
552
 
553
+ def __repr__(self):
554
+ params = self.__repr_params__
555
+ if isinstance(params[1], PYSON):
556
+ return '%s.contains(%s)' % (
557
+ repr(params[1]), ', '.join(map(repr, params[:1] + params[2:])))
558
+ else:
559
+ return super().__repr__()
560
+
511
561
  @property
512
562
  def __repr_params__(self):
513
563
  return (self._key, self._obj)