PyOdoo 0.9.0__tar.gz → 0.9.2__tar.gz

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.
Files changed (35) hide show
  1. {pyodoo-0.9.0 → pyodoo-0.9.2}/PKG-INFO +5 -7
  2. {pyodoo-0.9.0 → pyodoo-0.9.2}/PyOdoo.egg-info/PKG-INFO +5 -7
  3. pyodoo-0.9.2/PyOdoo.egg-info/requires.txt +2 -0
  4. {pyodoo-0.9.0 → pyodoo-0.9.2}/README.md +2 -3
  5. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/api.py +24 -5
  6. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/constants.py +1 -1
  7. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/model.py +9 -3
  8. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyproject.toml +2 -3
  9. {pyodoo-0.9.0 → pyodoo-0.9.2}/tests/test_contacts.py +88 -9
  10. {pyodoo-0.9.0 → pyodoo-0.9.2}/tests/test_python_code.py +1 -1
  11. pyodoo-0.9.0/PyOdoo.egg-info/requires.txt +0 -2
  12. {pyodoo-0.9.0 → pyodoo-0.9.2}/LICENSE +0 -0
  13. {pyodoo-0.9.0 → pyodoo-0.9.2}/PyOdoo.egg-info/SOURCES.txt +0 -0
  14. {pyodoo-0.9.0 → pyodoo-0.9.2}/PyOdoo.egg-info/dependency_links.txt +0 -0
  15. {pyodoo-0.9.0 → pyodoo-0.9.2}/PyOdoo.egg-info/top_level.txt +0 -0
  16. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/__init__.py +0 -0
  17. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/active_status_choice.py +0 -0
  18. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/boolean_operator.py +0 -0
  19. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/compare_type.py +0 -0
  20. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/filter.py +0 -0
  21. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/implementation.py +0 -0
  22. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/message_subtype.py +0 -0
  23. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/python_code.py +0 -0
  24. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/samples/__init__.py +0 -0
  25. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/samples/awaitable.py +0 -0
  26. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/samples/contacts.py +0 -0
  27. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/samples/contacts_async.py +0 -0
  28. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/samples/python_code.py +0 -0
  29. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/samples/sql_excel_query.py +0 -0
  30. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/sql_excel_query.py +0 -0
  31. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/transport.py +0 -0
  32. {pyodoo-0.9.0 → pyodoo-0.9.2}/pyodoo/v12/__init__.py +0 -0
  33. {pyodoo-0.9.0 → pyodoo-0.9.2}/setup.cfg +0 -0
  34. {pyodoo-0.9.0 → pyodoo-0.9.2}/tests/test_compare_types.py +0 -0
  35. {pyodoo-0.9.0 → pyodoo-0.9.2}/tests/test_sql_excel_query.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOdoo
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: API for Odoo
5
5
  Author-email: Fabio Castelli <muflone@muflone.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -12,23 +12,21 @@ Classifier: Development Status :: 1 - Planning
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3.14
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
24
23
  Requires-Dist: xlrd~=2.0
25
- Requires-Dist: requests~=2.32
24
+ Requires-Dist: requests~=2.34
26
25
  Dynamic: license-file
27
26
 
28
27
  # PyOdoo
29
28
 
30
29
  [![CircleCI Build Status](https://img.shields.io/circleci/project/github/muflone/pyodoo/master.svg)](https://circleci.com/gh/muflone/pyodoo)
31
- [![Python 3.9](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml)
32
30
  [![Python 3.10](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml)
33
31
  [![Python 3.11](https://github.com/muflone/pyodoo/actions/workflows/python-3.11.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.11.yml)
34
32
  [![Python 3.12](https://github.com/muflone/pyodoo/actions/workflows/python-3.12.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.12.yml)
@@ -60,9 +58,9 @@ Currently the following implementations are provided:
60
58
 
61
59
  # System Requirements
62
60
 
63
- * Python 3.x
61
+ * Python >= 3.10
64
62
  * xlrd 2.0.x (https://pypi.org/project/xlrd/)
65
- * requests 2.32.x (https://pypi.org/project/requests/)
63
+ * requests 2.34.x (https://pypi.org/project/requests/)
66
64
 
67
65
  # Usage
68
66
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOdoo
3
- Version: 0.9.0
3
+ Version: 0.9.2
4
4
  Summary: API for Odoo
5
5
  Author-email: Fabio Castelli <muflone@muflone.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -12,23 +12,21 @@ Classifier: Development Status :: 1 - Planning
12
12
  Classifier: Intended Audience :: Developers
13
13
  Classifier: Operating System :: OS Independent
14
14
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
20
19
  Classifier: Programming Language :: Python :: 3.14
21
- Requires-Python: >=3.9
20
+ Requires-Python: >=3.10
22
21
  Description-Content-Type: text/markdown
23
22
  License-File: LICENSE
24
23
  Requires-Dist: xlrd~=2.0
25
- Requires-Dist: requests~=2.32
24
+ Requires-Dist: requests~=2.34
26
25
  Dynamic: license-file
27
26
 
28
27
  # PyOdoo
29
28
 
30
29
  [![CircleCI Build Status](https://img.shields.io/circleci/project/github/muflone/pyodoo/master.svg)](https://circleci.com/gh/muflone/pyodoo)
31
- [![Python 3.9](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml)
32
30
  [![Python 3.10](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml)
33
31
  [![Python 3.11](https://github.com/muflone/pyodoo/actions/workflows/python-3.11.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.11.yml)
34
32
  [![Python 3.12](https://github.com/muflone/pyodoo/actions/workflows/python-3.12.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.12.yml)
@@ -60,9 +58,9 @@ Currently the following implementations are provided:
60
58
 
61
59
  # System Requirements
62
60
 
63
- * Python 3.x
61
+ * Python >= 3.10
64
62
  * xlrd 2.0.x (https://pypi.org/project/xlrd/)
65
- * requests 2.32.x (https://pypi.org/project/requests/)
63
+ * requests 2.34.x (https://pypi.org/project/requests/)
66
64
 
67
65
  # Usage
68
66
 
@@ -0,0 +1,2 @@
1
+ xlrd~=2.0
2
+ requests~=2.34
@@ -1,7 +1,6 @@
1
1
  # PyOdoo
2
2
 
3
3
  [![CircleCI Build Status](https://img.shields.io/circleci/project/github/muflone/pyodoo/master.svg)](https://circleci.com/gh/muflone/pyodoo)
4
- [![Python 3.9](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.9.yml)
5
4
  [![Python 3.10](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.10.yml)
6
5
  [![Python 3.11](https://github.com/muflone/pyodoo/actions/workflows/python-3.11.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.11.yml)
7
6
  [![Python 3.12](https://github.com/muflone/pyodoo/actions/workflows/python-3.12.yml/badge.svg)](https://github.com/muflone/pyodoo/actions/workflows/python-3.12.yml)
@@ -33,9 +32,9 @@ Currently the following implementations are provided:
33
32
 
34
33
  # System Requirements
35
34
 
36
- * Python 3.x
35
+ * Python >= 3.10
37
36
  * xlrd 2.0.x (https://pypi.org/project/xlrd/)
38
- * requests 2.32.x (https://pypi.org/project/requests/)
37
+ * requests 2.34.x (https://pypi.org/project/requests/)
39
38
 
40
39
  # Usage
41
40
 
@@ -113,8 +113,13 @@ class Api(object):
113
113
  }
114
114
  },
115
115
  timeout=self.timeout)
116
- result = response.json() if response else None
117
- self.uid = result['result']['uid'] if result else None
116
+ response.raise_for_status()
117
+ result = response.json()
118
+ if not isinstance(result, dict):
119
+ raise RuntimeError('Invalid Odoo RPC response: not an object')
120
+ if 'error' in result:
121
+ raise RuntimeError(f'Odoo RPC error: {result["error"]}')
122
+ self.uid = result['result']['uid'] if 'result' in result else None
118
123
  else:
119
124
  # Invalid implementation
120
125
  raise ValueError(f'Invalid implementation {self.implementation}')
@@ -225,7 +230,14 @@ class Api(object):
225
230
  }
226
231
  },
227
232
  timeout=self.timeout
228
- ).json().get('result')
233
+ )
234
+ results.raise_for_status()
235
+ results = results.json()
236
+ if not isinstance(results, dict):
237
+ raise RuntimeError('Invalid Odoo RPC response: not an object')
238
+ if 'error' in results:
239
+ raise RuntimeError(f'Odoo RPC error: {results["error"]}')
240
+ results = results.get('result')
229
241
  elif self.implementation == Implementation.WEBRPC:
230
242
  # Execute for WEB-RPC
231
243
  results = self.session.post(
@@ -243,7 +255,14 @@ class Api(object):
243
255
  }
244
256
  },
245
257
  timeout=self.timeout
246
- ).json().get('result')
258
+ )
259
+ results.raise_for_status()
260
+ results = results.json()
261
+ if not isinstance(results, dict):
262
+ raise RuntimeError('Invalid Odoo RPC response: not an object')
263
+ if 'error' in results:
264
+ raise RuntimeError(f'Odoo RPC error: {results["error"]}')
265
+ results = results.get('result')
247
266
  else:
248
267
  # Invalid implementation
249
268
  raise ValueError(f'Invalid implementation {self.implementation}')
@@ -294,7 +313,7 @@ class Api(object):
294
313
  :return: Dictionary with the requested fields
295
314
  """
296
315
  results = self.do_execute(method_name='fields_get',
297
- args=fields,
316
+ args=[fields],
298
317
  kwargs=options)
299
318
  return results
300
319
 
@@ -19,7 +19,7 @@
19
19
  ##
20
20
 
21
21
  APP_NAME = 'PyOdoo'
22
- APP_VERSION = '0.9.0'
22
+ APP_VERSION = '0.9.2'
23
23
  APP_DESCRIPTION = 'API for Odoo'
24
24
  APP_DOMAIN = 'pyodoo'
25
25
  APP_ID = f'{APP_DOMAIN}.muflone.com'
@@ -922,6 +922,7 @@ class Model(object):
922
922
  entity_id: int,
923
923
  body: str,
924
924
  author_id: int,
925
+ options: Optional[dict[str, Any]] = None,
925
926
  ignore_none_errors: bool = False
926
927
  ) -> int:
927
928
  """
@@ -930,6 +931,7 @@ class Model(object):
930
931
  :param entity_id: The object ID to which to add the message
931
932
  :param body: The message body to add
932
933
  :param author_id: The partner ID which authored the message
934
+ :param options: Dictionary with any existing options
933
935
  :param ignore_none_errors: Ignore RPC errors returning None
934
936
  :return: Returned message ID
935
937
  """
@@ -938,13 +940,14 @@ class Model(object):
938
940
  author_id=author_id,
939
941
  subject=False,
940
942
  body=body,
941
- options=None)
943
+ options=options)
942
944
 
943
945
  @_ignore_none_errors
944
946
  def post_message_as_comment(self,
945
947
  entity_id: int,
946
948
  body: str,
947
949
  author_id: int,
950
+ options: Optional[dict[str, Any]] = None,
948
951
  ignore_none_errors: bool = False
949
952
  ) -> int:
950
953
  """
@@ -953,6 +956,7 @@ class Model(object):
953
956
  :param entity_id: The object ID to which to add the message
954
957
  :param body: The message body to add
955
958
  :param author_id: The partner ID which authored the message
959
+ :param options: Dictionary with any existing options
956
960
  :param ignore_none_errors: Ignore RPC errors returning None
957
961
  :return: Returned message ID
958
962
  """
@@ -961,13 +965,14 @@ class Model(object):
961
965
  author_id=author_id,
962
966
  subject=False,
963
967
  body=body,
964
- options=None)
968
+ options=options)
965
969
 
966
970
  @_ignore_none_errors
967
971
  def post_message_as_note(self,
968
972
  entity_id: int,
969
973
  body: str,
970
974
  author_id: int,
975
+ options: Optional[dict[str, Any]] = None,
971
976
  ignore_none_errors: bool = False
972
977
  ) -> int:
973
978
  """
@@ -976,6 +981,7 @@ class Model(object):
976
981
  :param entity_id: The object ID to which to add the message
977
982
  :param body: The message body to add
978
983
  :param author_id: The partner ID which authored the message
984
+ :param options: Dictionary with any existing options
979
985
  :param ignore_none_errors: Ignore RPC errors returning None
980
986
  :return: Returned message ID
981
987
  """
@@ -984,4 +990,4 @@ class Model(object):
984
990
  author_id=author_id,
985
991
  subject=False,
986
992
  body=body,
987
- options=None)
993
+ options=options)
@@ -12,17 +12,16 @@ authors = [
12
12
  license = 'GPL-3.0-or-later'
13
13
  license-files = ['LICENSE']
14
14
  dynamic = ['version']
15
- requires-python = '>=3.9'
15
+ requires-python = '>=3.10'
16
16
  dependencies = [
17
17
  'xlrd~=2.0',
18
- 'requests~=2.32',
18
+ 'requests~=2.34',
19
19
  ]
20
20
  classifiers = [
21
21
  'Development Status :: 1 - Planning',
22
22
  'Intended Audience :: Developers',
23
23
  'Operating System :: OS Independent',
24
24
  'Programming Language :: Python :: 3',
25
- 'Programming Language :: Python :: 3.9',
26
25
  'Programming Language :: Python :: 3.10',
27
26
  'Programming Language :: Python :: 3.11',
28
27
  'Programming Language :: Python :: 3.12',
@@ -678,7 +678,30 @@ class BaseTests():
678
678
  if isinstance(entity_id, list)
679
679
  else entity_id, 0)
680
680
 
681
- def test_34_post_message_comment(self
681
+ def test_34_post_message_activity_html(self
682
+ ) -> None:
683
+ """
684
+ Post a new message as activity as HTML
685
+ """
686
+ filters = [Filter(field='name',
687
+ compare_type=CompareType.EQUAL,
688
+ value=f'{APP_NAME} v.{APP_VERSION}')]
689
+ results = self.model.search(filters=filters)
690
+ # Check if we have results
691
+ self.assertIsNotNone(results)
692
+ self.assertGreater(len(results), 0)
693
+ # Add a comment
694
+ entity_id = self.model.post_message_as_activity(
695
+ entity_id=results[0],
696
+ body='This is an <strong>HTML activity message</strong>',
697
+ author_id=results[0],
698
+ options={'body_is_html': True})
699
+ self.assertIsNotNone(entity_id)
700
+ self.assertGreater(entity_id[0]
701
+ if isinstance(entity_id, list)
702
+ else entity_id, 0)
703
+
704
+ def test_35_post_message_comment(self
682
705
  ) -> None:
683
706
  """
684
707
  Post a new message as comment
@@ -700,7 +723,30 @@ class BaseTests():
700
723
  if isinstance(entity_id, list)
701
724
  else entity_id, 0)
702
725
 
703
- def test_35_post_message_note(self
726
+ def test_36_post_message_comment_html(self
727
+ ) -> None:
728
+ """
729
+ Post a new message as comment as HTML
730
+ """
731
+ filters = [Filter(field='name',
732
+ compare_type=CompareType.EQUAL,
733
+ value=f'{APP_NAME} v.{APP_VERSION}')]
734
+ results = self.model.search(filters=filters)
735
+ # Check if we have results
736
+ self.assertIsNotNone(results)
737
+ self.assertGreater(len(results), 0)
738
+ # Add a comment
739
+ entity_id = self.model.post_message_as_comment(
740
+ entity_id=results[0],
741
+ body='This is a <strong>HTML comment message</strong>',
742
+ author_id=results[0],
743
+ options={'body_is_html': True})
744
+ self.assertIsNotNone(entity_id)
745
+ self.assertGreater(entity_id[0]
746
+ if isinstance(entity_id, list)
747
+ else entity_id, 0)
748
+
749
+ def test_37_post_message_note(self
704
750
  ) -> None:
705
751
  """
706
752
  Post a new message as note
@@ -722,7 +768,31 @@ class BaseTests():
722
768
  if isinstance(entity_id, list)
723
769
  else entity_id, 0)
724
770
 
725
- def test_36_delete(self
771
+
772
+ def test_38_post_message_note_html(self
773
+ ) -> None:
774
+ """
775
+ Post a new message as note as HTML
776
+ """
777
+ filters = [Filter(field='name',
778
+ compare_type=CompareType.EQUAL,
779
+ value=f'{APP_NAME} v.{APP_VERSION}')]
780
+ results = self.model.search(filters=filters)
781
+ # Check if we have results
782
+ self.assertIsNotNone(results)
783
+ self.assertGreater(len(results), 0)
784
+ # Add a comment
785
+ entity_id = self.model.post_message_as_note(
786
+ entity_id=results[0],
787
+ body='This is a <strong>HTML note message</strong>',
788
+ author_id=results[0],
789
+ options={'body_is_html': True})
790
+ self.assertIsNotNone(entity_id)
791
+ self.assertGreater(entity_id[0]
792
+ if isinstance(entity_id, list)
793
+ else entity_id, 0)
794
+
795
+ def test_39_delete(self
726
796
  ) -> None:
727
797
  """
728
798
  Delete the newly created rows.
@@ -756,7 +826,7 @@ class BaseTests():
756
826
  # We catched a different error, re-raise it
757
827
  raise error
758
828
 
759
- def test_37_delete_many(self
829
+ def test_40_delete_many(self
760
830
  ) -> None:
761
831
  """
762
832
  Delete the newly created rows.
@@ -783,7 +853,7 @@ class BaseTests():
783
853
  # We catched a different error, re-raise it
784
854
  raise error
785
855
 
786
- def test_38_language(self
856
+ def test_41_language(self
787
857
  ) -> None:
788
858
  """
789
859
  Get the current default language, change and restore it
@@ -801,7 +871,7 @@ class BaseTests():
801
871
  results = self.model.language
802
872
  self.assertEqual(results, original_language)
803
873
 
804
- def test_39_get_fields(self
874
+ def test_42_get_fields(self
805
875
  ) -> None:
806
876
  """
807
877
  Get the model fields
@@ -810,7 +880,16 @@ class BaseTests():
810
880
  # Check if we have results
811
881
  self.assertIsNotNone(results)
812
882
 
813
- def test_40_get_fields_attributes(self
883
+ def test_43_get_fields_fields(self
884
+ ) -> None:
885
+ """
886
+ Get the model fields
887
+ """
888
+ results = self.model.get_fields(fields=('id', 'name'))
889
+ # Check if we have results
890
+ self.assertIsNotNone(results)
891
+
892
+ def test_44_get_fields_attributes(self
814
893
  ) -> None:
815
894
  """
816
895
  Get the model fields
@@ -824,7 +903,7 @@ class BaseTests():
824
903
  self.assertIn('string', field_name)
825
904
  self.assertIn('type', field_name)
826
905
 
827
- def test_41_get_model_authenticate(self
906
+ def test_45_get_model_authenticate(self
828
907
  ) -> None:
829
908
  """
830
909
  Get a new Model object
@@ -841,7 +920,7 @@ class BaseTests():
841
920
  self.assertIsNotNone(results)
842
921
  self.assertGreater(results, 0)
843
922
 
844
- def test_42_get_model_no_authenticate(self
923
+ def test_46_get_model_no_authenticate(self
845
924
  ) -> None:
846
925
  """
847
926
  Get a new Model object using the existing UID
@@ -150,7 +150,7 @@ class TestCasesJsonRpc(BaseTests, unittest.TestCase):
150
150
  username=info['user'],
151
151
  password=info['password'],
152
152
  language='en_US',
153
- implementation=Implementation.XMLRPC):
153
+ implementation=Implementation.JSONRPC):
154
154
  # The model sql.excel.pdf is available
155
155
  cls.script_name = uuid.uuid4().hex
156
156
  cls.script = PythonCode(name=cls.script_name,
@@ -1,2 +0,0 @@
1
- xlrd~=2.0
2
- requests~=2.32
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes