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.
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/CHANGELOG.md +9 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/Makefile +1 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/PKG-INFO +16 -11
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/README.md +5 -3
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/authors.md +5 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/release.md +1 -1
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/api.py +19 -12
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/models.py +8 -12
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/pyproject.toml +52 -67
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/PKG-INFO +16 -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
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/requires.txt +7 -6
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/setup.py +13 -10
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_api.py +8 -0
- python_epo_ops_client-4.2.2/tests/test_raise_for_status.py +67 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/LICENSE +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/MANIFEST.in +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/backlog.md +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/contributing.md +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/docs/sandbox.md +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/__init__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/__version__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/exceptions.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/__init__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/__init__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/dogpile/__init__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/dogpile/dogpile.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/dogpile/helpers.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/middleware.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/__init__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/storages/__init__.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/storages/sqlite.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/storages/storage.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/throttler.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/utils.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/utils.py +0 -0
- {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
- {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
- {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
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/setup.cfg +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_models.py +0 -0
- {python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/tests/test_ops_quota.py +0 -0
- {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.
|
|
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.
|
|
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:
|
|
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<
|
|
41
|
+
Requires-Dist: pytest<10; extra == "test"
|
|
40
42
|
Requires-Dist: pytest-cache<2; extra == "test"
|
|
41
|
-
Requires-Dist: pytest-cov<
|
|
42
|
-
Requires-Dist: python-dotenv<1.
|
|
43
|
-
Requires-Dist: responses<0.
|
|
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.
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
@@ -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__(
|
|
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=
|
|
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
|
-
|
|
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
|
|
115
|
-
|
|
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
|
-
#
|
|
39
|
-
"
|
|
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",
|
|
55
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
110
|
-
|
|
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*)$"
|
{python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/python_epo_ops_client.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-epo-ops-client
|
|
3
|
-
Version: 4.2.
|
|
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.
|
|
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:
|
|
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<
|
|
41
|
+
Requires-Dist: pytest<10; extra == "test"
|
|
40
42
|
Requires-Dist: pytest-cache<2; extra == "test"
|
|
41
|
-
Requires-Dist: pytest-cov<
|
|
42
|
-
Requires-Dist: python-dotenv<1.
|
|
43
|
-
Requires-Dist: responses<0.
|
|
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.
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
dogpile.cache<1.
|
|
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.
|
|
15
|
+
ruff<0.16
|
|
15
16
|
|
|
16
17
|
[test]
|
|
17
|
-
pytest<
|
|
18
|
+
pytest<10
|
|
18
19
|
pytest-cache<2
|
|
19
|
-
pytest-cov<
|
|
20
|
-
python-dotenv<1.
|
|
21
|
-
responses<0.
|
|
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=
|
|
27
|
-
|
|
27
|
+
packages=find_packages(
|
|
28
|
+
include=["epo_ops*"],
|
|
29
|
+
),
|
|
28
30
|
include_package_data=True,
|
|
29
31
|
install_requires=[
|
|
30
|
-
"dogpile.cache<1.
|
|
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
|
-
"
|
|
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<
|
|
46
|
+
"pytest<10",
|
|
44
47
|
"pytest-cache<2",
|
|
45
|
-
"pytest-cov<
|
|
46
|
-
"python-dotenv<1.
|
|
47
|
-
"responses<0.
|
|
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
|
|
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
|
{python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/cache/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/middleware.py
RENAMED
|
File without changes
|
{python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_epo_ops_client-4.2.0 → python_epo_ops_client-4.2.2}/epo_ops/middlewares/throttle/utils.py
RENAMED
|
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
|