pbn-client 0.1.0__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.
- pbn_client-0.1.0/.github/workflows/ci.yml +31 -0
- pbn_client-0.1.0/.gitignore +27 -0
- pbn_client-0.1.0/.pre-commit-config.yaml +18 -0
- pbn_client-0.1.0/LICENSE +21 -0
- pbn_client-0.1.0/PKG-INFO +108 -0
- pbn_client-0.1.0/README.md +81 -0
- pbn_client-0.1.0/pyproject.toml +59 -0
- pbn_client-0.1.0/src/pbn_client/__init__.py +53 -0
- pbn_client-0.1.0/src/pbn_client/auth.py +96 -0
- pbn_client-0.1.0/src/pbn_client/client.py +113 -0
- pbn_client-0.1.0/src/pbn_client/conf/__init__.py +0 -0
- pbn_client-0.1.0/src/pbn_client/conf/settings.py +46 -0
- pbn_client-0.1.0/src/pbn_client/const.py +33 -0
- pbn_client-0.1.0/src/pbn_client/dict_utils.py +48 -0
- pbn_client-0.1.0/src/pbn_client/exceptions.py +141 -0
- pbn_client-0.1.0/src/pbn_client/mixins/__init__.py +22 -0
- pbn_client-0.1.0/src/pbn_client/mixins/conferences.py +20 -0
- pbn_client-0.1.0/src/pbn_client/mixins/dictionaries.py +16 -0
- pbn_client-0.1.0/src/pbn_client/mixins/institutions.py +156 -0
- pbn_client-0.1.0/src/pbn_client/mixins/journals.py +28 -0
- pbn_client-0.1.0/src/pbn_client/mixins/person.py +26 -0
- pbn_client-0.1.0/src/pbn_client/mixins/publications.py +32 -0
- pbn_client-0.1.0/src/pbn_client/mixins/publishers.py +25 -0
- pbn_client-0.1.0/src/pbn_client/mixins/search.py +10 -0
- pbn_client-0.1.0/src/pbn_client/pagination.py +44 -0
- pbn_client-0.1.0/src/pbn_client/reporting.py +136 -0
- pbn_client-0.1.0/src/pbn_client/statements.py +388 -0
- pbn_client-0.1.0/src/pbn_client/transport.py +377 -0
- pbn_client-0.1.0/src/pbn_client/utils.py +23 -0
- pbn_client-0.1.0/tests/fakes.py +31 -0
- pbn_client-0.1.0/tests/test_configuration.py +71 -0
- pbn_client-0.1.0/tests/test_institutions.py +26 -0
- pbn_client-0.1.0/tests/test_masking.py +28 -0
- pbn_client-0.1.0/tests/test_pbn_validation_error.py +154 -0
- pbn_client-0.1.0/tests/test_reporting.py +85 -0
- pbn_client-0.1.0/tests/test_transport_rollbar_scrub.py +56 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: uv run --isolated pytest -q
|
|
30
|
+
- name: Lint
|
|
31
|
+
run: uv run --isolated ruff check .
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.pytest_cache/
|
|
4
|
+
.ruff_cache/
|
|
5
|
+
.venv/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
|
|
10
|
+
# Secrets / local env
|
|
11
|
+
.env
|
|
12
|
+
.env.*
|
|
13
|
+
!.env.example
|
|
14
|
+
!.env.sample
|
|
15
|
+
*.pem
|
|
16
|
+
*.key
|
|
17
|
+
|
|
18
|
+
# IDE / editor
|
|
19
|
+
.idea/
|
|
20
|
+
.vscode/
|
|
21
|
+
*.sublime-workspace
|
|
22
|
+
|
|
23
|
+
# OS turds
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
desktop.ini
|
|
27
|
+
._*
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v5.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-toml
|
|
9
|
+
- id: check-added-large-files
|
|
10
|
+
- id: check-merge-conflict
|
|
11
|
+
- id: detect-private-key
|
|
12
|
+
|
|
13
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
14
|
+
rev: v0.14.0
|
|
15
|
+
hooks:
|
|
16
|
+
- id: ruff-check
|
|
17
|
+
args: [--fix]
|
|
18
|
+
- id: ruff-format
|
pbn_client-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2026 Michal Pasternak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pbn-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-independent client for the Polish Bibliography Network API
|
|
5
|
+
Project-URL: Homepage, https://github.com/iplweb/pbn-client
|
|
6
|
+
Project-URL: Repository, https://github.com/iplweb/pbn-client
|
|
7
|
+
Project-URL: Issues, https://github.com/iplweb/pbn-client/issues
|
|
8
|
+
Author-email: Michał Pasternak <michal.dtz@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api-client,bibliography,pbn,polish-bibliography-network
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: <3.15,>=3.10
|
|
24
|
+
Requires-Dist: requests<3,>=2.28
|
|
25
|
+
Requires-Dist: simplejson<5,>=3.19
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# pbn-client
|
|
29
|
+
|
|
30
|
+
[](https://github.com/iplweb/pbn-client/actions/workflows/ci.yml)
|
|
31
|
+
[](https://opensource.org/licenses/MIT)
|
|
32
|
+
|
|
33
|
+
`pbn-client` is a framework-independent Python client for communication with
|
|
34
|
+
the Polish Bibliography Network (PBN) API. It provides HTTP authentication,
|
|
35
|
+
pagination, dictionary and publication endpoints, and institution-profile
|
|
36
|
+
statement operations.
|
|
37
|
+
|
|
38
|
+
The package does not depend on Django or an external error-reporting service.
|
|
39
|
+
Projects that persist downloaded PBN data in Django should use the companion
|
|
40
|
+
`django-pbn-client` package.
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```console
|
|
45
|
+
pip install pbn-client
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Python 3.10 through 3.14 is supported.
|
|
49
|
+
|
|
50
|
+
## Basic usage
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from pbn_client import PBNClient, RequestsTransport
|
|
54
|
+
|
|
55
|
+
transport = RequestsTransport(
|
|
56
|
+
app_id="application-id",
|
|
57
|
+
app_token="application-token",
|
|
58
|
+
base_url="https://pbn-micro-alpha.opi.org.pl",
|
|
59
|
+
user_token="user-token",
|
|
60
|
+
timeout=(30, 120),
|
|
61
|
+
)
|
|
62
|
+
client = PBNClient(transport)
|
|
63
|
+
|
|
64
|
+
languages = list(client.get_languages())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Credentials should normally be supplied explicitly. The interactive command
|
|
68
|
+
helpers also understand `PBN_CLIENT_APP_ID`, `PBN_CLIENT_APP_TOKEN`,
|
|
69
|
+
`PBN_CLIENT_BASE_URL`, `PBN_CLIENT_USER_TOKEN`, and
|
|
70
|
+
`PBN_CLIENT_HTTP_TIMEOUT`. A timeout may be a single number or a
|
|
71
|
+
`connect,read` pair.
|
|
72
|
+
|
|
73
|
+
## Error reporting
|
|
74
|
+
|
|
75
|
+
The standalone default reporter is a no-op. An application can inject any
|
|
76
|
+
object implementing `ErrorReporter` when it constructs a transport:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import rollbar
|
|
80
|
+
|
|
81
|
+
from pbn_client import RequestsTransport
|
|
82
|
+
|
|
83
|
+
transport = RequestsTransport(
|
|
84
|
+
"application-id",
|
|
85
|
+
"application-token",
|
|
86
|
+
"https://pbn-micro-alpha.opi.org.pl",
|
|
87
|
+
reporter=rollbar,
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The reporter receives only scrubbed diagnostic metadata. Response bodies and
|
|
92
|
+
authentication header values are not sent to it. `LoggingReporter`,
|
|
93
|
+
`NullReporter`, and `set_default_reporter()` are also available for applications
|
|
94
|
+
that prefer a process-wide policy.
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
From the repository root:
|
|
99
|
+
|
|
100
|
+
```console
|
|
101
|
+
uv run --isolated pytest
|
|
102
|
+
uv run ruff check .
|
|
103
|
+
uv build
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
`pbn-client` is released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# pbn-client
|
|
2
|
+
|
|
3
|
+
[](https://github.com/iplweb/pbn-client/actions/workflows/ci.yml)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
`pbn-client` is a framework-independent Python client for communication with
|
|
7
|
+
the Polish Bibliography Network (PBN) API. It provides HTTP authentication,
|
|
8
|
+
pagination, dictionary and publication endpoints, and institution-profile
|
|
9
|
+
statement operations.
|
|
10
|
+
|
|
11
|
+
The package does not depend on Django or an external error-reporting service.
|
|
12
|
+
Projects that persist downloaded PBN data in Django should use the companion
|
|
13
|
+
`django-pbn-client` package.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```console
|
|
18
|
+
pip install pbn-client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Python 3.10 through 3.14 is supported.
|
|
22
|
+
|
|
23
|
+
## Basic usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from pbn_client import PBNClient, RequestsTransport
|
|
27
|
+
|
|
28
|
+
transport = RequestsTransport(
|
|
29
|
+
app_id="application-id",
|
|
30
|
+
app_token="application-token",
|
|
31
|
+
base_url="https://pbn-micro-alpha.opi.org.pl",
|
|
32
|
+
user_token="user-token",
|
|
33
|
+
timeout=(30, 120),
|
|
34
|
+
)
|
|
35
|
+
client = PBNClient(transport)
|
|
36
|
+
|
|
37
|
+
languages = list(client.get_languages())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Credentials should normally be supplied explicitly. The interactive command
|
|
41
|
+
helpers also understand `PBN_CLIENT_APP_ID`, `PBN_CLIENT_APP_TOKEN`,
|
|
42
|
+
`PBN_CLIENT_BASE_URL`, `PBN_CLIENT_USER_TOKEN`, and
|
|
43
|
+
`PBN_CLIENT_HTTP_TIMEOUT`. A timeout may be a single number or a
|
|
44
|
+
`connect,read` pair.
|
|
45
|
+
|
|
46
|
+
## Error reporting
|
|
47
|
+
|
|
48
|
+
The standalone default reporter is a no-op. An application can inject any
|
|
49
|
+
object implementing `ErrorReporter` when it constructs a transport:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import rollbar
|
|
53
|
+
|
|
54
|
+
from pbn_client import RequestsTransport
|
|
55
|
+
|
|
56
|
+
transport = RequestsTransport(
|
|
57
|
+
"application-id",
|
|
58
|
+
"application-token",
|
|
59
|
+
"https://pbn-micro-alpha.opi.org.pl",
|
|
60
|
+
reporter=rollbar,
|
|
61
|
+
)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The reporter receives only scrubbed diagnostic metadata. Response bodies and
|
|
65
|
+
authentication header values are not sent to it. `LoggingReporter`,
|
|
66
|
+
`NullReporter`, and `set_default_reporter()` are also available for applications
|
|
67
|
+
that prefer a process-wide policy.
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
From the repository root:
|
|
72
|
+
|
|
73
|
+
```console
|
|
74
|
+
uv run --isolated pytest
|
|
75
|
+
uv run ruff check .
|
|
76
|
+
uv build
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
`pbn-client` is released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pbn-client"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Framework-independent client for the Polish Bibliography Network API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10,<3.15"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Michał Pasternak", email = "michal.dtz@gmail.com" },
|
|
15
|
+
]
|
|
16
|
+
keywords = ["pbn", "bibliography", "polish-bibliography-network", "api-client"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"requests>=2.28,<3",
|
|
32
|
+
"simplejson>=3.19,<5",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/iplweb/pbn-client"
|
|
37
|
+
Repository = "https://github.com/iplweb/pbn-client"
|
|
38
|
+
Issues = "https://github.com/iplweb/pbn-client/issues"
|
|
39
|
+
|
|
40
|
+
[dependency-groups]
|
|
41
|
+
dev = [
|
|
42
|
+
"pytest>=9.1.1,<10",
|
|
43
|
+
"ruff>=0.14,<1",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/pbn_client"]
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
addopts = "-ra"
|
|
51
|
+
pythonpath = ["src", "tests"]
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 88
|
|
56
|
+
target-version = "py310"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "F", "I"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Klient API PBN — czysta, niezależna od frameworka warstwa protokołu.
|
|
2
|
+
|
|
3
|
+
Pakiet operuje wyłącznie na pojęciach PBN: tokeny, URL-e, PBN UID-y, JSON-y
|
|
4
|
+
i flagi bool. Nie zna modelu domenowego żadnej aplikacji-hosta, więc może
|
|
5
|
+
być używany przez dowolny projekt integrujący się z PBN.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .auth import OAuthMixin
|
|
9
|
+
from .client import PBNClient
|
|
10
|
+
from .mixins import (
|
|
11
|
+
ConferencesMixin,
|
|
12
|
+
DictionariesMixin,
|
|
13
|
+
InstitutionsMixin,
|
|
14
|
+
InstitutionsProfileMixin,
|
|
15
|
+
JournalsMixin,
|
|
16
|
+
PersonMixin,
|
|
17
|
+
PublicationsMixin,
|
|
18
|
+
PublishersMixin,
|
|
19
|
+
SearchMixin,
|
|
20
|
+
)
|
|
21
|
+
from .pagination import PageableResource
|
|
22
|
+
from .reporting import (
|
|
23
|
+
ErrorReporter,
|
|
24
|
+
LoggingReporter,
|
|
25
|
+
NullReporter,
|
|
26
|
+
get_default_reporter,
|
|
27
|
+
set_default_reporter,
|
|
28
|
+
)
|
|
29
|
+
from .transport import PBNClientTransport, RequestsTransport
|
|
30
|
+
from .utils import smart_content
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"PBNClient",
|
|
34
|
+
"OAuthMixin",
|
|
35
|
+
"ConferencesMixin",
|
|
36
|
+
"DictionariesMixin",
|
|
37
|
+
"InstitutionsMixin",
|
|
38
|
+
"InstitutionsProfileMixin",
|
|
39
|
+
"JournalsMixin",
|
|
40
|
+
"PersonMixin",
|
|
41
|
+
"PublicationsMixin",
|
|
42
|
+
"PublishersMixin",
|
|
43
|
+
"SearchMixin",
|
|
44
|
+
"PageableResource",
|
|
45
|
+
"ErrorReporter",
|
|
46
|
+
"LoggingReporter",
|
|
47
|
+
"NullReporter",
|
|
48
|
+
"get_default_reporter",
|
|
49
|
+
"set_default_reporter",
|
|
50
|
+
"PBNClientTransport",
|
|
51
|
+
"RequestsTransport",
|
|
52
|
+
"smart_content",
|
|
53
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""OAuth authentication mixin for PBN API."""
|
|
2
|
+
|
|
3
|
+
from urllib.parse import parse_qs, urlparse
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from pbn_client.conf import settings as pbn_settings
|
|
8
|
+
from pbn_client.exceptions import (
|
|
9
|
+
AuthenticationConfigurationError,
|
|
10
|
+
AuthenticationResponseError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OAuthMixin:
|
|
15
|
+
"""Mixin providing OAuth authentication functionality."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def get_auth_url(klass, base_url, app_id, state=None):
|
|
19
|
+
url = f"{base_url}/auth/pbn/api/registration/user/token/{app_id}"
|
|
20
|
+
if state:
|
|
21
|
+
from urllib.parse import quote
|
|
22
|
+
|
|
23
|
+
url += f"?state={quote(state)}"
|
|
24
|
+
return url
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_user_token(
|
|
28
|
+
klass,
|
|
29
|
+
base_url,
|
|
30
|
+
app_id,
|
|
31
|
+
app_token,
|
|
32
|
+
one_time_token,
|
|
33
|
+
*,
|
|
34
|
+
timeout=None,
|
|
35
|
+
):
|
|
36
|
+
headers = {
|
|
37
|
+
"X-App-Id": app_id,
|
|
38
|
+
"X-App-Token": app_token,
|
|
39
|
+
}
|
|
40
|
+
body = {"oneTimeToken": one_time_token}
|
|
41
|
+
url = f"{base_url}/auth/pbn/api/user/token"
|
|
42
|
+
response = requests.post(
|
|
43
|
+
url=url,
|
|
44
|
+
json=body,
|
|
45
|
+
headers=headers,
|
|
46
|
+
timeout=(
|
|
47
|
+
pbn_settings.PBN_CLIENT_HTTP_TIMEOUT
|
|
48
|
+
if timeout is None
|
|
49
|
+
else pbn_settings.parse_timeout(timeout)
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
try:
|
|
53
|
+
response.json()
|
|
54
|
+
except ValueError as e:
|
|
55
|
+
if response.content.startswith(b"Mismatched X-APP-TOKEN: "):
|
|
56
|
+
raise AuthenticationConfigurationError(
|
|
57
|
+
"Token aplikacji PBN nieprawidłowy. Poproś administratora "
|
|
58
|
+
"o skonfigurowanie prawidłowego tokena aplikacji PBN w "
|
|
59
|
+
"konfiguracji aplikacji. "
|
|
60
|
+
) from e
|
|
61
|
+
|
|
62
|
+
raise AuthenticationResponseError(response.content) from e
|
|
63
|
+
|
|
64
|
+
return response.json().get("X-User-Token")
|
|
65
|
+
|
|
66
|
+
def authorize(self, base_url, app_id, app_token):
|
|
67
|
+
if self.access_token:
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
self.access_token = pbn_settings.PBN_CLIENT_USER_TOKEN
|
|
71
|
+
if self.access_token:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
auth_url = OAuthMixin.get_auth_url(base_url, app_id)
|
|
75
|
+
|
|
76
|
+
print(
|
|
77
|
+
f"""I have launched a web browser with {auth_url} ,\nplease log-in,
|
|
78
|
+
then paste the redirected URL below. \n"""
|
|
79
|
+
)
|
|
80
|
+
import webbrowser
|
|
81
|
+
|
|
82
|
+
webbrowser.open(auth_url)
|
|
83
|
+
redirect_response = input("Paste the full redirect URL here:")
|
|
84
|
+
one_time_token = parse_qs(urlparse(redirect_response).query).get("ott")[0]
|
|
85
|
+
|
|
86
|
+
# NIE wypisujemy one_time_token ani access_token — to aktywne sekrety,
|
|
87
|
+
# które zostawałyby w terminalu/CI/przechwyconych logach (uwaga #4).
|
|
88
|
+
self.access_token = OAuthMixin.get_user_token(
|
|
89
|
+
base_url,
|
|
90
|
+
app_id,
|
|
91
|
+
app_token,
|
|
92
|
+
one_time_token,
|
|
93
|
+
timeout=getattr(self, "timeout", None),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return True
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Czysty klient protokołu PBN.
|
|
2
|
+
|
|
3
|
+
``PBNClient`` to kompozycja mixinów protokołu (słownikowo-CRUD + silnik
|
|
4
|
+
oświadczeń ``StatementsMixin``). Operuje na tokenach, PBN UID-ach, JSON-ach
|
|
5
|
+
i flagach bool — nie zna modelu domenowego aplikacji-hosta.
|
|
6
|
+
|
|
7
|
+
Orkiestracja synchronizacji specyficzna dla aplikacji (znająca jej rekordy
|
|
8
|
+
i konfigurację instytucji) powinna żyć w warstwie aplikacji, jako klasa
|
|
9
|
+
dziedzicząca po ``PBNClient``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
from pprint import pprint
|
|
15
|
+
|
|
16
|
+
from .mixins import (
|
|
17
|
+
ConferencesMixin,
|
|
18
|
+
DictionariesMixin,
|
|
19
|
+
InstitutionsMixin,
|
|
20
|
+
InstitutionsProfileMixin,
|
|
21
|
+
JournalsMixin,
|
|
22
|
+
PersonMixin,
|
|
23
|
+
PublicationsMixin,
|
|
24
|
+
PublishersMixin,
|
|
25
|
+
SearchMixin,
|
|
26
|
+
)
|
|
27
|
+
from .statements import StatementsMixin
|
|
28
|
+
from .transport import RequestsTransport
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class PBNClient(
|
|
32
|
+
ConferencesMixin,
|
|
33
|
+
DictionariesMixin,
|
|
34
|
+
InstitutionsMixin,
|
|
35
|
+
InstitutionsProfileMixin,
|
|
36
|
+
JournalsMixin,
|
|
37
|
+
PersonMixin,
|
|
38
|
+
PublicationsMixin,
|
|
39
|
+
PublishersMixin,
|
|
40
|
+
SearchMixin,
|
|
41
|
+
StatementsMixin,
|
|
42
|
+
):
|
|
43
|
+
"""Czysty klient protokołu PBN (bez orkiestracji specyficznej dla aplikacji)."""
|
|
44
|
+
|
|
45
|
+
_interactive = False
|
|
46
|
+
|
|
47
|
+
def __init__(self, transport: RequestsTransport):
|
|
48
|
+
self.transport = transport
|
|
49
|
+
|
|
50
|
+
def _get_command_function(self, cmd):
|
|
51
|
+
"""Get function to execute from command name."""
|
|
52
|
+
try:
|
|
53
|
+
return getattr(self, cmd[0])
|
|
54
|
+
except AttributeError as e:
|
|
55
|
+
if self._interactive:
|
|
56
|
+
print(f"No such command: {cmd}")
|
|
57
|
+
return None
|
|
58
|
+
raise e
|
|
59
|
+
|
|
60
|
+
def _extract_arguments(self, lst):
|
|
61
|
+
"""Extract positional and keyword arguments from command list."""
|
|
62
|
+
args = ()
|
|
63
|
+
kw = {}
|
|
64
|
+
for elem in lst:
|
|
65
|
+
if elem.find(":") >= 1:
|
|
66
|
+
k, n = elem.split(":", 1)
|
|
67
|
+
kw[k] = n
|
|
68
|
+
else:
|
|
69
|
+
args += (elem,)
|
|
70
|
+
return args, kw
|
|
71
|
+
|
|
72
|
+
def _print_non_interactive_result(self, res):
|
|
73
|
+
"""Print result in non-interactive mode."""
|
|
74
|
+
import json
|
|
75
|
+
|
|
76
|
+
print(json.dumps(res))
|
|
77
|
+
|
|
78
|
+
def _print_interactive_result(self, res):
|
|
79
|
+
"""Print result in interactive mode."""
|
|
80
|
+
if type(res) is dict:
|
|
81
|
+
pprint(res)
|
|
82
|
+
elif isinstance(res, Iterable):
|
|
83
|
+
if self._interactive and hasattr(res, "total_elements"):
|
|
84
|
+
print(
|
|
85
|
+
"Incoming data: no_elements=",
|
|
86
|
+
res.total_elements,
|
|
87
|
+
"no_pages=",
|
|
88
|
+
res.total_pages,
|
|
89
|
+
)
|
|
90
|
+
input("Press ENTER to continue> ")
|
|
91
|
+
for elem in res:
|
|
92
|
+
pprint(elem)
|
|
93
|
+
|
|
94
|
+
def exec(self, cmd):
|
|
95
|
+
fun = self._get_command_function(cmd)
|
|
96
|
+
if fun is None:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
args, kw = self._extract_arguments(cmd[1:])
|
|
100
|
+
res = fun(*args, **kw)
|
|
101
|
+
|
|
102
|
+
if not sys.stdout.isatty():
|
|
103
|
+
self._print_non_interactive_result(res)
|
|
104
|
+
else:
|
|
105
|
+
self._print_interactive_result(res)
|
|
106
|
+
|
|
107
|
+
def interactive(self):
|
|
108
|
+
self._interactive = True
|
|
109
|
+
while True:
|
|
110
|
+
cmd = input("cmd> ")
|
|
111
|
+
if cmd == "exit":
|
|
112
|
+
break
|
|
113
|
+
self.exec(cmd.split(" "))
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Environment defaults for the standalone PBN client.
|
|
2
|
+
|
|
3
|
+
Applications should normally pass credentials and timeouts to the transport
|
|
4
|
+
constructor. These variables remain available for command-line compatibility.
|
|
5
|
+
No framework settings object is imported here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
|
|
13
|
+
from pbn_client.const import DEFAULT_BASE_URL
|
|
14
|
+
|
|
15
|
+
Timeout = float | tuple[float, float]
|
|
16
|
+
DEFAULT_HTTP_TIMEOUT: Timeout = (30.0, 120.0)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_timeout(
|
|
20
|
+
raw: str | float | Sequence[float] | None,
|
|
21
|
+
default: Timeout = DEFAULT_HTTP_TIMEOUT,
|
|
22
|
+
) -> Timeout:
|
|
23
|
+
"""Parse a requests timeout from a scalar or ``connect,read`` value."""
|
|
24
|
+
|
|
25
|
+
if raw is None:
|
|
26
|
+
return default
|
|
27
|
+
if isinstance(raw, (int, float)):
|
|
28
|
+
return float(raw)
|
|
29
|
+
if isinstance(raw, Sequence) and not isinstance(raw, str):
|
|
30
|
+
values = tuple(float(value) for value in raw)
|
|
31
|
+
return values if len(values) == 2 else default
|
|
32
|
+
|
|
33
|
+
parts = [part.strip() for part in str(raw).split(",") if part.strip()]
|
|
34
|
+
if len(parts) == 1:
|
|
35
|
+
return float(parts[0])
|
|
36
|
+
if len(parts) == 2:
|
|
37
|
+
return (float(parts[0]), float(parts[1]))
|
|
38
|
+
return default
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
PBN_CLIENT_DEFAULT_BASE_URL = DEFAULT_BASE_URL
|
|
42
|
+
PBN_CLIENT_APP_ID = os.getenv("PBN_CLIENT_APP_ID")
|
|
43
|
+
PBN_CLIENT_APP_TOKEN = os.getenv("PBN_CLIENT_APP_TOKEN")
|
|
44
|
+
PBN_CLIENT_BASE_URL = os.getenv("PBN_CLIENT_BASE_URL", DEFAULT_BASE_URL)
|
|
45
|
+
PBN_CLIENT_USER_TOKEN = os.getenv("PBN_CLIENT_USER_TOKEN")
|
|
46
|
+
PBN_CLIENT_HTTP_TIMEOUT = parse_timeout(os.getenv("PBN_CLIENT_HTTP_TIMEOUT"))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
DELETED = "DELETED"
|
|
2
|
+
ACTIVE = "ACTIVE"
|
|
3
|
+
|
|
4
|
+
PBN_POST_PUBLICATIONS_URL = "/api/v1/publications"
|
|
5
|
+
|
|
6
|
+
PBN_POST_PUBLICATION_NO_STATEMENTS_URL = "/api/v1/repositorium/publications"
|
|
7
|
+
|
|
8
|
+
PBN_POST_PUBLICATION_FEE_URL = "/api/v1/institutionProfile/publications/fees/{id}"
|
|
9
|
+
|
|
10
|
+
PBN_GET_LANGUAGES_URL = "/api/v1/dictionary/languages"
|
|
11
|
+
PBN_SEARCH_PUBLICATIONS_URL = "/api/v1/search/publications"
|
|
12
|
+
|
|
13
|
+
PBN_GET_JOURNAL_BY_ID = "/api/v1/journals/{id}"
|
|
14
|
+
|
|
15
|
+
DEFAULT_BASE_URL = "https://pbn-micro-alpha.opi.org.pl"
|
|
16
|
+
|
|
17
|
+
NEEDS_PBN_AUTH_MSG = (
|
|
18
|
+
"W celu poprawnej autentykacji należy podać poprawny token użytkownika aplikacji."
|
|
19
|
+
)
|
|
20
|
+
PBN_DELETE_PUBLICATION_STATEMENT = (
|
|
21
|
+
"/api/v1/institutionProfile/publications/{publicationId}"
|
|
22
|
+
)
|
|
23
|
+
PBN_GET_PUBLICATION_BY_ID_URL = "/api/v1/publications/id/{id}"
|
|
24
|
+
|
|
25
|
+
PBN_GET_INSTITUTION_STATEMENTS = (
|
|
26
|
+
"/api/v1/institutionProfile/publications/page/statements"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
PBN_GET_DISCIPLINES_URL = "/api/v2/dictionary/disciplines"
|
|
30
|
+
|
|
31
|
+
PBN_POST_INSTITUTION_STATEMENTS_URL = "/api/v2/institution-profile/statements"
|
|
32
|
+
|
|
33
|
+
PBN_GET_INSTITUTION_PUBLICATIONS_V2 = "/api/v2/institution-profile/publications"
|