python-epo-ops-client 4.2.0__tar.gz → 4.2.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 (43) hide show
  1. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/CHANGELOG.md +9 -0
  2. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/Makefile +1 -0
  3. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/PKG-INFO +16 -11
  4. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/README.md +5 -3
  5. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/authors.md +5 -0
  6. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/release.md +1 -1
  7. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/api.py +19 -12
  8. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/models.py +8 -12
  9. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/pyproject.toml +52 -67
  10. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/PKG-INFO +16 -11
  11. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/SOURCES.txt +1 -0
  12. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/requires.txt +7 -6
  13. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/setup.py +13 -10
  14. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_api.py +8 -0
  15. python_epo_ops_client-4.2.2/tests/test_raise_for_status.py +67 -0
  16. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/LICENSE +0 -0
  17. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/MANIFEST.in +0 -0
  18. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/backlog.md +0 -0
  19. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/contributing.md +0 -0
  20. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/sandbox.md +0 -0
  21. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/__init__.py +0 -0
  22. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/__version__.py +0 -0
  23. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/exceptions.py +0 -0
  24. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/__init__.py +0 -0
  25. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/__init__.py +0 -0
  26. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/dogpile/__init__.py +0 -0
  27. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/dogpile/dogpile.py +0 -0
  28. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/dogpile/helpers.py +0 -0
  29. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/middleware.py +0 -0
  30. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/__init__.py +0 -0
  31. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/storages/__init__.py +0 -0
  32. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/storages/sqlite.py +0 -0
  33. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/storages/storage.py +0 -0
  34. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/throttler.py +0 -0
  35. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/utils.py +0 -0
  36. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/utils.py +0 -0
  37. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/dependency_links.txt +0 -0
  38. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/not-zip-safe +0 -0
  39. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/top_level.txt +0 -0
  40. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/setup.cfg +0 -0
  41. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_models.py +0 -0
  42. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_ops_quota.py +0 -0
  43. {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_utils.py +0 -0
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.2.2 (2026-05-17)
4
+
5
+ - Verify support for Python 3.14
6
+ - Made `raise_for_status` configurable. Thanks, @ofipify.
7
+
8
+ ## 4.2.1 (2025-09-09)
9
+
10
+ - Made network timeout configurable. Thanks, @ofipify.
11
+
3
12
  ## 4.2.0 (2025-07-26)
4
13
 
5
14
  - Added support for the "legal" service endpoint, providing legal status
@@ -37,6 +37,7 @@ lint: ## lint the project
37
37
  ruff format --check .
38
38
 
39
39
  format: ## Run code formatting
40
+ pyproject-fmt --keep-full-version pyproject.toml
40
41
  ruff format .
41
42
  # Configure Ruff not to auto-fix (remove!):
42
43
  # Ignore unused imports (F401), unused variables (F841), `print` statements (T201), and commented-out code (ERA001).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-epo-ops-client
3
- Version: 4.2.0
3
+ Version: 4.2.2
4
4
  Summary: Python client for EPO OPS, the European Patent Office's Open Patent Services API.
5
5
  Home-page: https://github.com/ip-tools/python-epo-ops-client
6
6
  Download-URL: https://pypi.org/project/python-epo-ops-client/#files
@@ -8,11 +8,11 @@ Author: George Song
8
8
  Author-email: george@monozuku.com
9
9
  Maintainer: Andreas Motl
10
10
  Maintainer-email: andreas.motl@ip-tools.org
11
+ License: Apache-2.0
11
12
  Keywords: ops,epo,epo-ops,patent-data,patent-office,patent-data-api,european patent office,open patent services
12
13
  Classifier: Development Status :: 5 - Production/Stable
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Natural Language :: English
15
- Classifier: License :: OSI Approved :: Apache Software License
16
16
  Classifier: Programming Language :: Python
17
17
  Classifier: Programming Language :: Python :: 3.6
18
18
  Classifier: Programming Language :: Python :: 3.7
@@ -22,25 +22,27 @@ Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
25
26
  Classifier: Topic :: Software Development :: Libraries
26
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
28
  Description-Content-Type: text/markdown
28
29
  License-File: LICENSE
29
- Requires-Dist: dogpile.cache<1.5
30
+ Requires-Dist: dogpile.cache<1.6
30
31
  Requires-Dist: importlib-metadata; python_version < "3.8"
31
32
  Requires-Dist: python-dateutil<2.10
32
33
  Requires-Dist: requests<3,>=2.27
33
34
  Requires-Dist: six<2
34
35
  Provides-Extra: develop
35
- Requires-Dist: ruff<0.13; python_version >= "3.7" and extra == "develop"
36
+ Requires-Dist: pyproject-fmt<3; extra == "develop"
37
+ Requires-Dist: ruff<0.16; python_version >= "3.7" and extra == "develop"
36
38
  Requires-Dist: twine<7; extra == "develop"
37
39
  Requires-Dist: wheel<1; extra == "develop"
38
40
  Provides-Extra: test
39
- Requires-Dist: pytest<9; extra == "test"
41
+ Requires-Dist: pytest<10; extra == "test"
40
42
  Requires-Dist: pytest-cache<2; extra == "test"
41
- Requires-Dist: pytest-cov<6.3; extra == "test"
42
- Requires-Dist: python-dotenv<1.2; extra == "test"
43
- Requires-Dist: responses<0.26; extra == "test"
43
+ Requires-Dist: pytest-cov<7.2; extra == "test"
44
+ Requires-Dist: python-dotenv<1.3; extra == "test"
45
+ Requires-Dist: responses<0.27; extra == "test"
44
46
  Dynamic: author
45
47
  Dynamic: author-email
46
48
  Dynamic: classifier
@@ -49,6 +51,7 @@ Dynamic: description-content-type
49
51
  Dynamic: download-url
50
52
  Dynamic: home-page
51
53
  Dynamic: keywords
54
+ Dynamic: license
52
55
  Dynamic: license-file
53
56
  Dynamic: maintainer
54
57
  Dynamic: maintainer-email
@@ -125,9 +128,11 @@ you'll interact with mostly.
125
128
 
126
129
  When you issue a request, the response is a [requests.Response][] object. If
127
130
  `response.status_code != 200` then a `requests.HTTPError` exception will be
128
- raised — it's your responsibility to handle those exceptions if you want to. The
129
- one case that's handled is when the access token has expired: in this case, the
130
- client will automatically handle the HTTP 400 status and renew the token.
131
+ raised — it's your responsibility to handle those exceptions if you want to.
132
+ This default can be disabled by passing `raise_for_status=False` when
133
+ constructing the client. The one case that's handled is when the access token
134
+ has expired: in this case, the client will automatically handle the HTTP 400
135
+ status and renew the token.
131
136
 
132
137
  Note that the Client does not attempt to interpret the data supplied by OPS, so
133
138
  it's your responsibility to parse the XML or JSON payload for your own purpose.
@@ -67,9 +67,11 @@ you'll interact with mostly.
67
67
 
68
68
  When you issue a request, the response is a [requests.Response][] object. If
69
69
  `response.status_code != 200` then a `requests.HTTPError` exception will be
70
- raised — it's your responsibility to handle those exceptions if you want to. The
71
- one case that's handled is when the access token has expired: in this case, the
72
- client will automatically handle the HTTP 400 status and renew the token.
70
+ raised — it's your responsibility to handle those exceptions if you want to.
71
+ This default can be disabled by passing `raise_for_status=False` when
72
+ constructing the client. The one case that's handled is when the access token
73
+ has expired: in this case, the client will automatically handle the HTTP 400
74
+ status and renew the token.
73
75
 
74
76
  Note that the Client does not attempt to interpret the data supplied by OPS, so
75
77
  it's your responsibility to parse the XML or JSON payload for your own purpose.
@@ -13,12 +13,17 @@ thank you so much. The list is alphabetically sorted.
13
13
 
14
14
  ## Contributors
15
15
 
16
+ - [Alejandro Marti](https://github.com/MartiONE)
16
17
  - [Alexander Meinhardt Scheurer](https://github.com/BeneCollyridam)
17
18
  - [Daniel Blasco](https://github.com/dablak)
19
+ - [Dan Bolser](https://github.com/CholoTook)
18
20
  - [fe60](https://github.com/fe60)
19
21
  - [Felipe Eltermann](https://github.com/eltermann)
20
22
  - [Geoffrey Cline](https://github.com/geoffcline)
21
23
  - [Hiro Kobashi](https://github.com/kobaski)
24
+ - [Matt Keanny](https://github.com/mattkeanny)
22
25
  - [Mike Matheson](https://github.com/mmath)
26
+ - [Oliver Fuerst](https://github.com/ofipify)
23
27
  - [Roberto Faga](https://github.com/rfaga)
28
+ - [Sebastian Vilstrup](https://github.com/ipr-sv)
24
29
  - [Sotiris Fragkiskos](https://github.com/sfranky)
@@ -15,5 +15,5 @@ To release the package, exercise those steps:
15
15
 
16
16
  - Push to remote.
17
17
  ```shell
18
- git push --follow-tags
18
+ git push && git push --tags
19
19
  ```
@@ -10,17 +10,12 @@ from requests.exceptions import HTTPError
10
10
 
11
11
  from . import exceptions
12
12
  from .middlewares import Throttler
13
- from .models import (
14
- NETWORK_TIMEOUT,
15
- AccessToken,
16
- Docdb,
17
- Epodoc,
18
- Original,
19
- Request,
20
- )
13
+ from .models import AccessToken, Docdb, Epodoc, Original, Request
21
14
 
22
15
  log = logging.getLogger(__name__)
23
16
 
17
+ DEFAULT_NETWORK_TIMEOUT = 10.0
18
+
24
19
 
25
20
  class Client(object):
26
21
  __auth_url__ = "https://ops.epo.org/3.2/auth/accesstoken"
@@ -35,14 +30,24 @@ class Client(object):
35
30
  __register_path__ = "register"
36
31
  __register_search_path__ = "register/search"
37
32
 
38
- def __init__(self, key, secret, accept_type="xml", middlewares=None):
33
+ def __init__(
34
+ self,
35
+ key,
36
+ secret,
37
+ accept_type="xml",
38
+ middlewares=None,
39
+ timeout=DEFAULT_NETWORK_TIMEOUT,
40
+ raise_for_status=True,
41
+ ):
39
42
  self.accept_type = "application/{0}".format(accept_type)
40
43
  self.middlewares = middlewares
41
44
  if middlewares is None:
42
45
  self.middlewares = [Throttler()]
43
- self.request = Request(self.middlewares)
46
+ self.request = Request(self.middlewares, timeout)
44
47
  self.key = key
45
48
  self.secret = secret
49
+ self.timeout = timeout
50
+ self.raise_for_status = raise_for_status
46
51
  self._access_token = None
47
52
 
48
53
  def family(
@@ -153,6 +158,7 @@ class Client(object):
153
158
  input=input,
154
159
  )
155
160
  )
161
+
156
162
  def number(
157
163
  self,
158
164
  reference_type: str,
@@ -351,7 +357,7 @@ class Client(object):
351
357
  self.__auth_url__,
352
358
  headers=headers,
353
359
  data=payload,
354
- timeout=NETWORK_TIMEOUT,
360
+ timeout=self.timeout,
355
361
  )
356
362
  response.raise_for_status()
357
363
  self._access_token = AccessToken(response)
@@ -394,7 +400,8 @@ class Client(object):
394
400
  )
395
401
  response = self._check_for_expired_token(response)
396
402
  response = self._check_for_exceeded_quota(response)
397
- response.raise_for_status()
403
+ if self.raise_for_status:
404
+ response.raise_for_status()
398
405
  return response
399
406
 
400
407
  # info: {
@@ -11,9 +11,6 @@ from .utils import quote, validate_date
11
11
  log = logging.getLogger(__name__)
12
12
 
13
13
 
14
- NETWORK_TIMEOUT = 10.0
15
-
16
-
17
14
  def _prepare_part(part):
18
15
  return "({0})".format(quote(part))
19
16
 
@@ -68,8 +65,9 @@ class AccessToken(object):
68
65
 
69
66
 
70
67
  class Request(object):
71
- def __init__(self, middlewares):
68
+ def __init__(self, middlewares, timeout=None):
72
69
  self.middlewares = middlewares
70
+ self.timeout = timeout
73
71
  self.reset_env()
74
72
 
75
73
  @property
@@ -86,10 +84,10 @@ class Request(object):
86
84
  self.env.update(self.default_env)
87
85
 
88
86
  def post(self, url, data=None, **kwargs):
89
- return self._request(_post_callback, url, data, **kwargs)
87
+ return self._request(self._post_callback, url, data, **kwargs)
90
88
 
91
89
  def get(self, url, data=None, **kwargs):
92
- return self._request(_get_callback, url, data, **kwargs)
90
+ return self._request(self._get_callback, url, data, **kwargs)
93
91
 
94
92
  def _request(self, callback, url, data=None, **kwargs):
95
93
  self.reset_env()
@@ -110,10 +108,8 @@ class Request(object):
110
108
  self.reset_env()
111
109
  return response
112
110
 
111
+ def _post_callback(self, url, data, **kwargs):
112
+ return requests.post(url, data, **kwargs, timeout=self.timeout)
113
113
 
114
- def _post_callback(url, data, **kwargs):
115
- return requests.post(url, data, **kwargs, timeout=NETWORK_TIMEOUT)
116
-
117
-
118
- def _get_callback(url, data, **kwargs):
119
- return requests.get(url, **kwargs, timeout=NETWORK_TIMEOUT)
114
+ def _get_callback(self, url, data, **kwargs):
115
+ return requests.get(url, **kwargs, timeout=self.timeout)
@@ -1,63 +1,45 @@
1
- # ==================
2
- # Build system setup
3
- # ==================
4
-
5
1
  [build-system]
2
+ build-backend = "setuptools.build_meta"
6
3
  requires = [
7
4
  "setuptools>=42", # At least v42 of setuptools required!
8
5
  "versioningit",
9
6
  ]
10
- build-backend = "setuptools.build_meta"
11
-
12
- [tool.pytest.ini_options]
13
- minversion = "2.0"
14
- addopts = """
15
- -rsfEX -p pytester --strict-markers --verbosity=3
16
- --cov=epo_ops tests --cov-report=term-missing --cov-report=xml
17
- """
18
- log_level = "DEBUG"
19
- log_cli_level = "DEBUG"
20
- testpaths = ["tests"]
21
- xfail_strict = true
22
- markers = []
23
7
 
24
8
  [tool.ruff]
25
9
  line-length = 80
26
-
10
+ extend-exclude = [ "__init__.py" ]
27
11
  lint.select = [
28
- # Bandit
29
- "S",
30
- # Bugbear
31
- "B",
32
12
  # Builtins
33
13
  "A",
14
+ # Bugbear
15
+ "B",
16
+ "B9",
34
17
  # comprehensions
35
18
  "C",
19
+ # Pycodestyle
20
+ "E",
36
21
  # eradicate
37
22
  "ERA",
38
- # flake8-2020
39
- "YTT",
23
+ # Pyflakes
24
+ "F",
40
25
  # isort
41
26
  "I",
42
27
  # pandas-vet
43
28
  "PD",
44
- # print
45
- "T20",
46
- # Pycodestyle
47
- "E",
48
- "W",
49
- # Pyflakes
50
- "F",
51
29
  # return
52
30
  "RET",
31
+ # Bandit
32
+ "S",
53
33
  # from `.flake8` file
54
- "T", # T4
55
- "B9",
34
+ "T", # T4
35
+ # print
36
+ "T20",
37
+ "W",
38
+ # flake8-2020
39
+ "YTT",
56
40
  ]
57
-
58
- extend-exclude = ["__init__.py"]
59
-
60
41
  lint.ignore = [
42
+ "B905", # B905 `zip()` without an explicit `strict=` parameter
61
43
  "E203",
62
44
  "E266",
63
45
  "E501",
@@ -65,12 +47,25 @@ lint.ignore = [
65
47
  "RET505", # Unnecessary `else` after `return` statement
66
48
  # "W503", # Unknown rule selector
67
49
  ]
68
-
50
+ # FIXME: Improve this situation wrt. SQL injection, even it is not be an actual attack vector.
51
+ lint.per-file-ignores."*sqlite.py" = [
52
+ "S608", # Possible SQL injection vector through string-based query construction
53
+ ]
54
+ lint.per-file-ignores."epo_ops/api.py" = [
55
+ "A001", # Variable `range` is shadowing a Python builtin
56
+ "A002", # Argument `input` is shadowing a Python builtin
57
+ "C408", # Unnecessary `dict` call (rewrite as a literal)
58
+ ]
59
+ lint.per-file-ignores."tests/*" = [
60
+ "S101", # Use of `assert` detected
61
+ ]
62
+ lint.per-file-ignores."tests/middlewares/throttle/conftest.py" = [
63
+ "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
64
+ ]
69
65
  # from `.isort.cfg` file
70
- [tool.ruff.lint.isort]
71
- combine-as-imports = true
72
- force-wrap-aliases = true
73
- known-third-party = [
66
+ lint.isort.combine-as-imports = true
67
+ lint.isort.force-wrap-aliases = true
68
+ lint.isort.known-third-party = [
74
69
  "dateutil",
75
70
  "dogpile",
76
71
  "dotenv",
@@ -78,34 +73,24 @@ known-third-party = [
78
73
  "requests",
79
74
  "six",
80
75
  ]
81
- split-on-trailing-comma = false
82
-
76
+ lint.isort.split-on-trailing-comma = false
83
77
  # from `.flake8` file
84
- [tool.ruff.lint.mccabe]
85
78
  # Flag errors (`C901`) whenever the complexity level exceeds the configured value.
86
- max-complexity = 7
87
-
88
-
89
- [tool.ruff.lint.per-file-ignores]
90
- "tests/*" = [
91
- "S101", # Use of `assert` detected
92
- ]
93
- "epo_ops/api.py" = [
94
- "A001", # Variable `range` is shadowing a Python builtin
95
- "A002", # Argument `input` is shadowing a Python builtin
96
- "C408", # Unnecessary `dict` call (rewrite as a literal)
97
- ]
98
- # FIXME: Improve this situation wrt. SQL injection, even it is not be an actual attack vector.
99
- "*sqlite.py" = [
100
- "S608", # Possible SQL injection vector through string-based query construction
101
- ]
102
- "tests/middlewares/throttle/conftest.py" = [
103
- "S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
104
- ]
79
+ lint.mccabe.max-complexity = 7
105
80
 
81
+ [tool.pytest]
82
+ ini_options.minversion = "2.0"
83
+ ini_options.addopts = """
84
+ -rsfEX -p pytester --strict-markers --verbosity=3
85
+ --cov=epo_ops tests --cov-report=term-missing --cov-report=xml
86
+ """
87
+ ini_options.log_level = "DEBUG"
88
+ ini_options.log_cli_level = "DEBUG"
89
+ ini_options.testpaths = [ "tests" ]
90
+ ini_options.xfail_strict = true
91
+ ini_options.markers = []
106
92
 
107
93
  [tool.versioningit]
108
-
109
- [tool.versioningit.vcs]
110
- method = "git"
111
- default-tag = "0.0.0"
94
+ vcs.method = "git-archive"
95
+ vcs.default-tag = "v0.0.0"
96
+ vcs.describe-subst = "$Format:%(describe:tags,match=v*)$"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-epo-ops-client
3
- Version: 4.2.0
3
+ Version: 4.2.2
4
4
  Summary: Python client for EPO OPS, the European Patent Office's Open Patent Services API.
5
5
  Home-page: https://github.com/ip-tools/python-epo-ops-client
6
6
  Download-URL: https://pypi.org/project/python-epo-ops-client/#files
@@ -8,11 +8,11 @@ Author: George Song
8
8
  Author-email: george@monozuku.com
9
9
  Maintainer: Andreas Motl
10
10
  Maintainer-email: andreas.motl@ip-tools.org
11
+ License: Apache-2.0
11
12
  Keywords: ops,epo,epo-ops,patent-data,patent-office,patent-data-api,european patent office,open patent services
12
13
  Classifier: Development Status :: 5 - Production/Stable
13
14
  Classifier: Intended Audience :: Developers
14
15
  Classifier: Natural Language :: English
15
- Classifier: License :: OSI Approved :: Apache Software License
16
16
  Classifier: Programming Language :: Python
17
17
  Classifier: Programming Language :: Python :: 3.6
18
18
  Classifier: Programming Language :: Python :: 3.7
@@ -22,25 +22,27 @@ Classifier: Programming Language :: Python :: 3.10
22
22
  Classifier: Programming Language :: Python :: 3.11
23
23
  Classifier: Programming Language :: Python :: 3.12
24
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
25
26
  Classifier: Topic :: Software Development :: Libraries
26
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
27
28
  Description-Content-Type: text/markdown
28
29
  License-File: LICENSE
29
- Requires-Dist: dogpile.cache<1.5
30
+ Requires-Dist: dogpile.cache<1.6
30
31
  Requires-Dist: importlib-metadata; python_version < "3.8"
31
32
  Requires-Dist: python-dateutil<2.10
32
33
  Requires-Dist: requests<3,>=2.27
33
34
  Requires-Dist: six<2
34
35
  Provides-Extra: develop
35
- Requires-Dist: ruff<0.13; python_version >= "3.7" and extra == "develop"
36
+ Requires-Dist: pyproject-fmt<3; extra == "develop"
37
+ Requires-Dist: ruff<0.16; python_version >= "3.7" and extra == "develop"
36
38
  Requires-Dist: twine<7; extra == "develop"
37
39
  Requires-Dist: wheel<1; extra == "develop"
38
40
  Provides-Extra: test
39
- Requires-Dist: pytest<9; extra == "test"
41
+ Requires-Dist: pytest<10; extra == "test"
40
42
  Requires-Dist: pytest-cache<2; extra == "test"
41
- Requires-Dist: pytest-cov<6.3; extra == "test"
42
- Requires-Dist: python-dotenv<1.2; extra == "test"
43
- Requires-Dist: responses<0.26; extra == "test"
43
+ Requires-Dist: pytest-cov<7.2; extra == "test"
44
+ Requires-Dist: python-dotenv<1.3; extra == "test"
45
+ Requires-Dist: responses<0.27; extra == "test"
44
46
  Dynamic: author
45
47
  Dynamic: author-email
46
48
  Dynamic: classifier
@@ -49,6 +51,7 @@ Dynamic: description-content-type
49
51
  Dynamic: download-url
50
52
  Dynamic: home-page
51
53
  Dynamic: keywords
54
+ Dynamic: license
52
55
  Dynamic: license-file
53
56
  Dynamic: maintainer
54
57
  Dynamic: maintainer-email
@@ -125,9 +128,11 @@ you'll interact with mostly.
125
128
 
126
129
  When you issue a request, the response is a [requests.Response][] object. If
127
130
  `response.status_code != 200` then a `requests.HTTPError` exception will be
128
- raised — it's your responsibility to handle those exceptions if you want to. The
129
- one case that's handled is when the access token has expired: in this case, the
130
- client will automatically handle the HTTP 400 status and renew the token.
131
+ raised — it's your responsibility to handle those exceptions if you want to.
132
+ This default can be disabled by passing `raise_for_status=False` when
133
+ constructing the client. The one case that's handled is when the access token
134
+ has expired: in this case, the client will automatically handle the HTTP 400
135
+ status and renew the token.
131
136
 
132
137
  Note that the Client does not attempt to interpret the data supplied by OPS, so
133
138
  it's your responsibility to parse the XML or JSON payload for your own purpose.
@@ -37,4 +37,5 @@ python_epo_ops_client.egg-info/top_level.txt
37
37
  tests/test_api.py
38
38
  tests/test_models.py
39
39
  tests/test_ops_quota.py
40
+ tests/test_raise_for_status.py
40
41
  tests/test_utils.py
@@ -1,4 +1,4 @@
1
- dogpile.cache<1.5
1
+ dogpile.cache<1.6
2
2
  python-dateutil<2.10
3
3
  requests<3,>=2.27
4
4
  six<2
@@ -7,15 +7,16 @@ six<2
7
7
  importlib-metadata
8
8
 
9
9
  [develop]
10
+ pyproject-fmt<3
10
11
  twine<7
11
12
  wheel<1
12
13
 
13
14
  [develop:python_version >= "3.7"]
14
- ruff<0.13
15
+ ruff<0.16
15
16
 
16
17
  [test]
17
- pytest<9
18
+ pytest<10
18
19
  pytest-cache<2
19
- pytest-cov<6.3
20
- python-dotenv<1.2
21
- responses<0.26
20
+ pytest-cov<7.2
21
+ python-dotenv<1.3
22
+ responses<0.27
@@ -1,7 +1,7 @@
1
1
  import codecs
2
2
  from os import path
3
3
 
4
- from setuptools import setup
4
+ from setuptools import find_packages, setup
5
5
  from versioningit import get_cmdclasses
6
6
 
7
7
  here = path.abspath(path.dirname(__file__))
@@ -15,6 +15,7 @@ setup(
15
15
  description=(
16
16
  "Python client for EPO OPS, the European Patent Office's Open Patent Services API."
17
17
  ),
18
+ license="Apache-2.0",
18
19
  long_description_content_type="text/markdown",
19
20
  long_description=readme,
20
21
  author="George Song",
@@ -23,11 +24,12 @@ setup(
23
24
  maintainer_email="andreas.motl@ip-tools.org",
24
25
  url="https://github.com/ip-tools/python-epo-ops-client",
25
26
  download_url="https://pypi.org/project/python-epo-ops-client/#files",
26
- packages=["epo_ops", "epo_ops.middlewares"],
27
- package_dir={"epo_ops": "epo_ops"},
27
+ packages=find_packages(
28
+ include=["epo_ops*"],
29
+ ),
28
30
  include_package_data=True,
29
31
  install_requires=[
30
- "dogpile.cache<1.5",
32
+ "dogpile.cache<1.6",
31
33
  "importlib-metadata; python_version<'3.8'",
32
34
  "python-dateutil<2.10",
33
35
  "requests>=2.27,<3",
@@ -35,16 +37,17 @@ setup(
35
37
  ],
36
38
  extras_require={
37
39
  "develop": [
38
- "ruff<0.13; python_version >= '3.7'",
40
+ "pyproject-fmt<3",
41
+ "ruff<0.16; python_version >= '3.7'",
39
42
  "twine<7",
40
43
  "wheel<1",
41
44
  ],
42
45
  "test": [
43
- "pytest<9",
46
+ "pytest<10",
44
47
  "pytest-cache<2",
45
- "pytest-cov<6.3",
46
- "python-dotenv<1.2",
47
- "responses<0.26",
48
+ "pytest-cov<7.2",
49
+ "python-dotenv<1.3",
50
+ "responses<0.27",
48
51
  ],
49
52
  },
50
53
  zip_safe=False,
@@ -52,7 +55,6 @@ setup(
52
55
  "Development Status :: 5 - Production/Stable",
53
56
  "Intended Audience :: Developers",
54
57
  "Natural Language :: English",
55
- "License :: OSI Approved :: Apache Software License",
56
58
  "Programming Language :: Python",
57
59
  "Programming Language :: Python :: 3.6",
58
60
  "Programming Language :: Python :: 3.7",
@@ -62,6 +64,7 @@ setup(
62
64
  "Programming Language :: Python :: 3.11",
63
65
  "Programming Language :: Python :: 3.12",
64
66
  "Programming Language :: Python :: 3.13",
67
+ "Programming Language :: Python :: 3.14",
65
68
  "Topic :: Software Development :: Libraries",
66
69
  "Topic :: Software Development :: Libraries :: Python Modules",
67
70
  ],
@@ -28,6 +28,12 @@ def test_instantiate_simple_client():
28
28
  client = Client("key", "secret")
29
29
  assert len(client.middlewares) == 1
30
30
  assert client.middlewares[0].history.db_path == sqlite.DEFAULT_DB_PATH
31
+ assert client.raise_for_status is True
32
+
33
+
34
+ def test_instantiate_client_without_raise_for_status():
35
+ client = Client("key", "secret", raise_for_status=False)
36
+ assert client.raise_for_status is False
31
37
 
32
38
 
33
39
  def test_family(all_clients):
@@ -45,9 +51,11 @@ def test_family_legal(all_clients):
45
51
  def test_image(all_clients):
46
52
  assert_image_success(all_clients)
47
53
 
54
+
48
55
  def test_legal(all_clients):
49
56
  assert_legal_success(all_clients)
50
57
 
58
+
51
59
  def test_published_data(all_clients):
52
60
  assert_published_data_success(all_clients)
53
61
 
@@ -0,0 +1,67 @@
1
+ import pytest
2
+ import responses
3
+ from pytest import raises
4
+ from requests.exceptions import HTTPError
5
+
6
+ from epo_ops.api import Client
7
+ from epo_ops.models import Docdb
8
+
9
+
10
+ @pytest.fixture
11
+ def ops_backend_413():
12
+ """
13
+ Emulate an OPS backend returning 413 on the fulltext endpoint for an
14
+ ambiguous input. The real upstream returns 413 for e.g. EP.0536425/fulltext.
15
+ """
16
+ token = responses.Response(
17
+ responses.POST,
18
+ url="https://ops.epo.org/3.2/auth/accesstoken",
19
+ status=200,
20
+ json={"access_token": "foo", "expires_in": 42},
21
+ )
22
+ fulltext_413 = responses.Response(
23
+ responses.POST,
24
+ url="https://ops.epo.org/3.2/rest-services/published-data/publication/docdb/fulltext",
25
+ status=413,
26
+ headers={"Content-Type": "application/xml"},
27
+ body=(
28
+ '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
29
+ ' <fault xmlns="http://ops.epo.org">\n'
30
+ " <code>CLIENT.AmbiguousRequest</code>\n"
31
+ " <message>The request was ambiguous</message>\n"
32
+ " <details>\n"
33
+ " <cause>Ambiguous input: publication/docdb/EP.0536425</cause>\n"
34
+ " <resolution>publication/docdb/EP.0536425.A1</resolution>\n"
35
+ " <resolution>publication/docdb/EP.0536425.A4</resolution>\n"
36
+ " <resolution>publication/docdb/EP.0536425.B1</resolution>\n"
37
+ " <resolution>publication/docdb/EP.0536425.B2</resolution>\n"
38
+ " </details>\n"
39
+ " </fault>"
40
+ ),
41
+ )
42
+ for response in [token, fulltext_413]:
43
+ responses.add(response)
44
+
45
+
46
+ def _issue_fulltext_request(client):
47
+ return client.published_data(
48
+ "publication",
49
+ Docdb("0536425", "EP", "B1"),
50
+ endpoint="fulltext",
51
+ )
52
+
53
+
54
+ @responses.activate
55
+ def test_413_raises_by_default(ops_backend_413):
56
+ client = Client("key", "secret", middlewares=[])
57
+ with raises(HTTPError):
58
+ _issue_fulltext_request(client)
59
+
60
+
61
+ @responses.activate
62
+ def test_413_returned_when_raise_for_status_disabled(ops_backend_413):
63
+ client = Client("key", "secret", middlewares=[], raise_for_status=False)
64
+ response = _issue_fulltext_request(client)
65
+ assert response.status_code == 413
66
+ assert "CLIENT.AmbiguousRequest" in response.text
67
+ assert "publication/docdb/EP.0536425.B1" in response.text