bling-jwt-auth 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.
- bling_jwt_auth-0.1.0/.gitignore +20 -0
- bling_jwt_auth-0.1.0/LICENSE +21 -0
- bling_jwt_auth-0.1.0/PKG-INFO +176 -0
- bling_jwt_auth-0.1.0/README.md +144 -0
- bling_jwt_auth-0.1.0/examples/__init__.py +1 -0
- bling_jwt_auth-0.1.0/examples/authenticated_request.py +91 -0
- bling_jwt_auth-0.1.0/examples/oauth_flow.py +43 -0
- bling_jwt_auth-0.1.0/pyproject.toml +129 -0
- bling_jwt_auth-0.1.0/scripts/check.sh +34 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/__init__.py +31 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/config.py +59 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/constants.py +14 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/exceptions.py +25 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/headers.py +13 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/manager.py +55 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/models/__init__.py +5 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/models/token.py +69 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/oauth/__init__.py +5 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/oauth/client.py +148 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/storage/__init__.py +8 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/storage/base.py +22 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/storage/factory.py +20 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/storage/file.py +62 -0
- bling_jwt_auth-0.1.0/src/bling_jwt_auth/storage/sqlite.py +82 -0
- bling_jwt_auth-0.1.0/tests/__init__.py +1 -0
- bling_jwt_auth-0.1.0/tests/conftest.py +35 -0
- bling_jwt_auth-0.1.0/tests/test_headers.py +11 -0
- bling_jwt_auth-0.1.0/tests/test_oauth_client.py +151 -0
- bling_jwt_auth-0.1.0/tests/test_storage_factory.py +68 -0
- bling_jwt_auth-0.1.0/tests/test_storage_file.py +51 -0
- bling_jwt_auth-0.1.0/tests/test_storage_sqlite.py +43 -0
- bling_jwt_auth-0.1.0/tests/test_token_manager.py +130 -0
- bling_jwt_auth-0.1.0/tests/url_utils.py +12 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Testing / tooling
|
|
13
|
+
.pytest_cache/
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
|
|
16
|
+
# Local secrets and token files
|
|
17
|
+
.env
|
|
18
|
+
.env.*
|
|
19
|
+
*.db
|
|
20
|
+
token.json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mercanatu contributors
|
|
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,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bling-jwt-auth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OAuth 2.0 / JWT authentication helpers for the Bling API v3
|
|
5
|
+
Project-URL: Homepage, https://github.com/tempont/bling-jwt-auth-python
|
|
6
|
+
Project-URL: Documentation, https://github.com/tempont/bling-jwt-auth-python#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/tempont/bling-jwt-auth-python
|
|
8
|
+
Project-URL: Issues, https://github.com/tempont/bling-jwt-auth-python/issues
|
|
9
|
+
Author: tempont
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: api,authentication,bling,erp,jwt,oauth2
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.14
|
|
21
|
+
Requires-Dist: httpx>=0.28.0
|
|
22
|
+
Requires-Dist: pydantic-settings>=2.0.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: basedpyright>=1.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pre-commit>=4.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# bling-jwt-auth-python
|
|
34
|
+
|
|
35
|
+
Repositório: [github.com/tempont/bling-jwt-auth-python](https://github.com/tempont/bling-jwt-auth-python) · PyPI: [`bling-jwt-auth`](https://pypi.org/project/bling-jwt-auth/)
|
|
36
|
+
|
|
37
|
+
Biblioteca Python simples para autenticar na **API v3 do Bling** usando OAuth 2.0 e tokens JWT.
|
|
38
|
+
|
|
39
|
+
Ela ajuda a:
|
|
40
|
+
|
|
41
|
+
- gerar a URL de autorização do Bling;
|
|
42
|
+
- trocar o `code` recebido por tokens;
|
|
43
|
+
- salvar os tokens em arquivo ou SQLite;
|
|
44
|
+
- renovar o access token automaticamente quando necessário;
|
|
45
|
+
- montar os headers corretos para chamar a API do Bling.
|
|
46
|
+
|
|
47
|
+
Versão em inglês: [docs/README.en.md](docs/README.en.md)
|
|
48
|
+
|
|
49
|
+
## Requisitos
|
|
50
|
+
|
|
51
|
+
- Python 3.14 ou superior
|
|
52
|
+
- Uma aplicação OAuth cadastrada no Bling
|
|
53
|
+
|
|
54
|
+
## Instalação
|
|
55
|
+
|
|
56
|
+
Instale pelo `pip`:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install bling-jwt-auth
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Ou instale direto do GitHub:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install "git+https://github.com/tempont/bling-jwt-auth-python.git"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Para desenvolvimento local neste repositório, use:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv sync --extra dev
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Configuração
|
|
75
|
+
|
|
76
|
+
Copie o arquivo de exemplo:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cp .env.example .env
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Edite o `.env` com os dados da sua aplicação no Bling:
|
|
83
|
+
|
|
84
|
+
```env
|
|
85
|
+
BLING_CLIENT_ID=seu_client_id
|
|
86
|
+
BLING_CLIENT_SECRET=seu_client_secret
|
|
87
|
+
BLING_REDIRECT_URI=https://seu-dominio.com/oauth/callback
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Por padrão, os tokens são salvos em SQLite. Se quiser salvar em JSON:
|
|
91
|
+
|
|
92
|
+
```env
|
|
93
|
+
BLING_TOKEN_STORE=file
|
|
94
|
+
BLING_TOKEN_STORE_PATH=./token.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Como usar
|
|
98
|
+
|
|
99
|
+
### 1. Autorizar a conta Bling
|
|
100
|
+
|
|
101
|
+
Rode o exemplo de OAuth:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
uv run python examples/oauth_flow.py
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
O script vai mostrar uma URL. Abra essa URL no navegador, autorize o acesso no Bling e cole no terminal o `code` recebido no callback.
|
|
108
|
+
|
|
109
|
+
Se quiser que o script tente abrir o navegador automaticamente:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv run python examples/oauth_flow.py --open
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 2. Testar uma chamada autenticada
|
|
116
|
+
|
|
117
|
+
Depois de salvar o token, rode:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run python examples/authenticated_request.py
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Esse exemplo usa o token salvo, renova se necessário e chama um endpoint de homologação do Bling.
|
|
124
|
+
|
|
125
|
+
### 3. Usar no seu código
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
import httpx
|
|
129
|
+
from bling_jwt_auth import (
|
|
130
|
+
BlingAuthSettings,
|
|
131
|
+
OAuthClient,
|
|
132
|
+
TokenManager,
|
|
133
|
+
bling_api_headers,
|
|
134
|
+
create_token_store,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
settings = BlingAuthSettings()
|
|
138
|
+
store = create_token_store(settings)
|
|
139
|
+
|
|
140
|
+
with OAuthClient(settings) as oauth:
|
|
141
|
+
manager = TokenManager(oauth, store, settings)
|
|
142
|
+
access_token = manager.get_access_token()
|
|
143
|
+
|
|
144
|
+
headers = bling_api_headers(access_token)
|
|
145
|
+
|
|
146
|
+
response = httpx.get(
|
|
147
|
+
"https://api.bling.com.br/Api/v3/produtos",
|
|
148
|
+
headers=headers,
|
|
149
|
+
)
|
|
150
|
+
response.raise_for_status()
|
|
151
|
+
print(response.json())
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Comandos úteis para desenvolvimento
|
|
155
|
+
|
|
156
|
+
Rodar lint, checagem de tipos e testes:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
make check
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Ou:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
bash scripts/check.sh
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Rodar apenas os testes:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
uv run --extra dev pytest
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Licença
|
|
175
|
+
|
|
176
|
+
MIT. Veja [LICENSE](LICENSE).
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# bling-jwt-auth-python
|
|
2
|
+
|
|
3
|
+
Repositório: [github.com/tempont/bling-jwt-auth-python](https://github.com/tempont/bling-jwt-auth-python) · PyPI: [`bling-jwt-auth`](https://pypi.org/project/bling-jwt-auth/)
|
|
4
|
+
|
|
5
|
+
Biblioteca Python simples para autenticar na **API v3 do Bling** usando OAuth 2.0 e tokens JWT.
|
|
6
|
+
|
|
7
|
+
Ela ajuda a:
|
|
8
|
+
|
|
9
|
+
- gerar a URL de autorização do Bling;
|
|
10
|
+
- trocar o `code` recebido por tokens;
|
|
11
|
+
- salvar os tokens em arquivo ou SQLite;
|
|
12
|
+
- renovar o access token automaticamente quando necessário;
|
|
13
|
+
- montar os headers corretos para chamar a API do Bling.
|
|
14
|
+
|
|
15
|
+
Versão em inglês: [docs/README.en.md](docs/README.en.md)
|
|
16
|
+
|
|
17
|
+
## Requisitos
|
|
18
|
+
|
|
19
|
+
- Python 3.14 ou superior
|
|
20
|
+
- Uma aplicação OAuth cadastrada no Bling
|
|
21
|
+
|
|
22
|
+
## Instalação
|
|
23
|
+
|
|
24
|
+
Instale pelo `pip`:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install bling-jwt-auth
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Ou instale direto do GitHub:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install "git+https://github.com/tempont/bling-jwt-auth-python.git"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Para desenvolvimento local neste repositório, use:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv sync --extra dev
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuração
|
|
43
|
+
|
|
44
|
+
Copie o arquivo de exemplo:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cp .env.example .env
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Edite o `.env` com os dados da sua aplicação no Bling:
|
|
51
|
+
|
|
52
|
+
```env
|
|
53
|
+
BLING_CLIENT_ID=seu_client_id
|
|
54
|
+
BLING_CLIENT_SECRET=seu_client_secret
|
|
55
|
+
BLING_REDIRECT_URI=https://seu-dominio.com/oauth/callback
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Por padrão, os tokens são salvos em SQLite. Se quiser salvar em JSON:
|
|
59
|
+
|
|
60
|
+
```env
|
|
61
|
+
BLING_TOKEN_STORE=file
|
|
62
|
+
BLING_TOKEN_STORE_PATH=./token.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Como usar
|
|
66
|
+
|
|
67
|
+
### 1. Autorizar a conta Bling
|
|
68
|
+
|
|
69
|
+
Rode o exemplo de OAuth:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv run python examples/oauth_flow.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
O script vai mostrar uma URL. Abra essa URL no navegador, autorize o acesso no Bling e cole no terminal o `code` recebido no callback.
|
|
76
|
+
|
|
77
|
+
Se quiser que o script tente abrir o navegador automaticamente:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv run python examples/oauth_flow.py --open
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 2. Testar uma chamada autenticada
|
|
84
|
+
|
|
85
|
+
Depois de salvar o token, rode:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv run python examples/authenticated_request.py
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Esse exemplo usa o token salvo, renova se necessário e chama um endpoint de homologação do Bling.
|
|
92
|
+
|
|
93
|
+
### 3. Usar no seu código
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import httpx
|
|
97
|
+
from bling_jwt_auth import (
|
|
98
|
+
BlingAuthSettings,
|
|
99
|
+
OAuthClient,
|
|
100
|
+
TokenManager,
|
|
101
|
+
bling_api_headers,
|
|
102
|
+
create_token_store,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
settings = BlingAuthSettings()
|
|
106
|
+
store = create_token_store(settings)
|
|
107
|
+
|
|
108
|
+
with OAuthClient(settings) as oauth:
|
|
109
|
+
manager = TokenManager(oauth, store, settings)
|
|
110
|
+
access_token = manager.get_access_token()
|
|
111
|
+
|
|
112
|
+
headers = bling_api_headers(access_token)
|
|
113
|
+
|
|
114
|
+
response = httpx.get(
|
|
115
|
+
"https://api.bling.com.br/Api/v3/produtos",
|
|
116
|
+
headers=headers,
|
|
117
|
+
)
|
|
118
|
+
response.raise_for_status()
|
|
119
|
+
print(response.json())
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Comandos úteis para desenvolvimento
|
|
123
|
+
|
|
124
|
+
Rodar lint, checagem de tipos e testes:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
make check
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Ou:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
bash scripts/check.sh
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Rodar apenas os testes:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
uv run --extra dev pytest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Licença
|
|
143
|
+
|
|
144
|
+
MIT. Veja [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Example scripts."""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Call the Bling homologation endpoint to verify OAuth + stored tokens.
|
|
2
|
+
|
|
3
|
+
Loads secrets from `./.env` (see `env_file` on `BlingAuthSettings`) and/or exported
|
|
4
|
+
`BLING_*` environment variables — run from the repo root next to `.env`.
|
|
5
|
+
|
|
6
|
+
Uses the same token store as `oauth_flow.py` (see `BLING_TOKEN_STORE` and
|
|
7
|
+
`BLING_TOKEN_STORE_PATH`).
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
uv sync
|
|
11
|
+
uv run python examples/oauth_flow.py
|
|
12
|
+
uv run python examples/authenticated_request.py
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from typing import Any, cast
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
23
|
+
from bling_jwt_auth import (
|
|
24
|
+
BlingAuthSettings,
|
|
25
|
+
OAuthClient,
|
|
26
|
+
TokenManager,
|
|
27
|
+
TokenNotFoundError,
|
|
28
|
+
bling_api_headers,
|
|
29
|
+
create_token_store,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
HOMOLOGACAO_PRODUTOS_URL = "https://api.bling.com.br/Api/v3/homologacao/produtos"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _summarize_body(text: str, *, max_len: int = 800) -> str:
|
|
36
|
+
if len(text) <= max_len:
|
|
37
|
+
return text
|
|
38
|
+
return text[: max_len - 3] + "..."
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main() -> None:
|
|
42
|
+
"""GET homologação produtos using the access token from the configured store."""
|
|
43
|
+
settings = BlingAuthSettings.load()
|
|
44
|
+
store = create_token_store(settings)
|
|
45
|
+
print(f"Token store: {settings.token_store.value} at {store.path}")
|
|
46
|
+
|
|
47
|
+
with OAuthClient(settings) as oauth:
|
|
48
|
+
manager = TokenManager(oauth, store, settings)
|
|
49
|
+
try:
|
|
50
|
+
access_token = manager.get_access_token()
|
|
51
|
+
except TokenNotFoundError:
|
|
52
|
+
print(
|
|
53
|
+
"No token found. Run `uv run python examples/oauth_flow.py` first "
|
|
54
|
+
f"using the same BLING_TOKEN_STORE ({settings.token_store.value}).",
|
|
55
|
+
file=sys.stderr,
|
|
56
|
+
)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
|
|
59
|
+
headers = bling_api_headers(access_token)
|
|
60
|
+
with httpx.Client(timeout=30.0) as client:
|
|
61
|
+
response = client.get(HOMOLOGACAO_PRODUTOS_URL, headers=headers)
|
|
62
|
+
|
|
63
|
+
if not response.is_success:
|
|
64
|
+
print(
|
|
65
|
+
f"Request failed: HTTP {response.status_code}\n"
|
|
66
|
+
f"{_summarize_body(response.text)}",
|
|
67
|
+
file=sys.stderr,
|
|
68
|
+
)
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
parsed: Any = response.json()
|
|
73
|
+
except json.JSONDecodeError:
|
|
74
|
+
print("Unexpected response: not JSON", file=sys.stderr)
|
|
75
|
+
print(_summarize_body(response.text), file=sys.stderr)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
if not isinstance(parsed, dict):
|
|
79
|
+
print("Unexpected response: JSON must be an object", file=sys.stderr)
|
|
80
|
+
sys.exit(1)
|
|
81
|
+
body: dict[str, Any] = cast("dict[str, Any]", parsed)
|
|
82
|
+
data: Any | None = body.get("data")
|
|
83
|
+
preview_payload: Any = body if data is None else data
|
|
84
|
+
preview = json.dumps(preview_payload, indent=2, ensure_ascii=False)
|
|
85
|
+
print(f"HTTP {response.status_code} OK")
|
|
86
|
+
print(preview)
|
|
87
|
+
print("Token from store is valid for the Bling API (homologação GET).")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Minimal manual OAuth walkthrough.
|
|
2
|
+
|
|
3
|
+
Loads secrets from `./.env` (see `env_file` on `BlingAuthSettings`) and/or exported
|
|
4
|
+
`BLING_*` environment variables — run from the repo root next to `.env`.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
uv sync
|
|
8
|
+
uv run python examples/oauth_flow.py
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import webbrowser
|
|
16
|
+
|
|
17
|
+
from bling_jwt_auth import BlingAuthSettings, OAuthClient, TokenManager, create_token_store
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> None:
|
|
21
|
+
"""Prompt for OAuth code interactively after printing the authorize URL."""
|
|
22
|
+
settings = BlingAuthSettings.load()
|
|
23
|
+
store = create_token_store(settings)
|
|
24
|
+
with OAuthClient(settings) as oauth:
|
|
25
|
+
manager = TokenManager(oauth, store, settings)
|
|
26
|
+
|
|
27
|
+
auth_url = oauth.build_authorization_url(state=os.urandom(16).hex())
|
|
28
|
+
print("Open this URL in a browser, approve access, then copy the `code` query param:")
|
|
29
|
+
print(auth_url)
|
|
30
|
+
if "--open" in sys.argv:
|
|
31
|
+
webbrowser.open(auth_url)
|
|
32
|
+
|
|
33
|
+
code = input("Paste authorization code: ").strip()
|
|
34
|
+
manager.save_from_code(code)
|
|
35
|
+
token = manager.get_access_token()
|
|
36
|
+
print("Access token acquired (truncated):", token[:24], "...")
|
|
37
|
+
print("Use HTTP headers:")
|
|
38
|
+
print(" Authorization: Bearer", "<token>")
|
|
39
|
+
print(" enable-jwt: 1")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
main()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.24"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bling-jwt-auth"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "OAuth 2.0 / JWT authentication helpers for the Bling API v3"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.14"
|
|
12
|
+
authors = [{ name = "tempont" }]
|
|
13
|
+
keywords = ["bling", "oauth2", "jwt", "authentication", "erp", "api"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.28.0",
|
|
25
|
+
"pydantic>=2.0.0",
|
|
26
|
+
"pydantic-settings>=2.0.0",
|
|
27
|
+
"python-dotenv>=1.0.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/tempont/bling-jwt-auth-python"
|
|
32
|
+
Documentation = "https://github.com/tempont/bling-jwt-auth-python#readme"
|
|
33
|
+
Repository = "https://github.com/tempont/bling-jwt-auth-python"
|
|
34
|
+
Issues = "https://github.com/tempont/bling-jwt-auth-python/issues"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"basedpyright>=1.0.0",
|
|
39
|
+
"pre-commit>=4.0.0",
|
|
40
|
+
"pytest>=8.0.0",
|
|
41
|
+
"pytest-httpx>=0.30.0",
|
|
42
|
+
"ruff>=0.8.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/bling_jwt_auth"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = [
|
|
50
|
+
"/scripts",
|
|
51
|
+
"/src",
|
|
52
|
+
"/tests",
|
|
53
|
+
"/examples",
|
|
54
|
+
"/README.md",
|
|
55
|
+
"/LICENSE",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
|
|
61
|
+
[tool.ruff]
|
|
62
|
+
target-version = "py314"
|
|
63
|
+
line-length = 100
|
|
64
|
+
indent-width = 4
|
|
65
|
+
exclude = [".git", ".ruff_cache", ".venv"]
|
|
66
|
+
show-fixes = true
|
|
67
|
+
|
|
68
|
+
[tool.ruff.format]
|
|
69
|
+
indent-style = "space"
|
|
70
|
+
line-ending = "lf"
|
|
71
|
+
quote-style = "double"
|
|
72
|
+
docstring-code-format = true
|
|
73
|
+
|
|
74
|
+
[tool.ruff.lint]
|
|
75
|
+
select = ["ALL"]
|
|
76
|
+
ignore = [
|
|
77
|
+
"YTT",
|
|
78
|
+
"CPY",
|
|
79
|
+
"FA",
|
|
80
|
+
"TD",
|
|
81
|
+
"C90",
|
|
82
|
+
"PGH",
|
|
83
|
+
"COM812",
|
|
84
|
+
"COM819",
|
|
85
|
+
"D206",
|
|
86
|
+
"D300",
|
|
87
|
+
"E111",
|
|
88
|
+
"E114",
|
|
89
|
+
"E117",
|
|
90
|
+
"E501",
|
|
91
|
+
"Q000",
|
|
92
|
+
"Q001",
|
|
93
|
+
"Q002",
|
|
94
|
+
"Q003",
|
|
95
|
+
"W191",
|
|
96
|
+
]
|
|
97
|
+
task-tags = ["TODO", "FIXME", "XXX", "HACK"]
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.per-file-ignores]
|
|
100
|
+
"examples/**" = ["T201"]
|
|
101
|
+
"tests/**" = [
|
|
102
|
+
"ANN001",
|
|
103
|
+
"ANN201",
|
|
104
|
+
"PLR2004",
|
|
105
|
+
"S101",
|
|
106
|
+
"S105",
|
|
107
|
+
"S106",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.ruff.lint.pydocstyle]
|
|
111
|
+
convention = "google"
|
|
112
|
+
|
|
113
|
+
[tool.pyright]
|
|
114
|
+
pythonVersion = "3.14"
|
|
115
|
+
typeCheckingMode = "strict"
|
|
116
|
+
failOnWarnings = true
|
|
117
|
+
venvPath = "."
|
|
118
|
+
venv = ".venv"
|
|
119
|
+
extraPaths = ["src"]
|
|
120
|
+
exclude = [
|
|
121
|
+
"**/.venv",
|
|
122
|
+
".venv",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
[dependency-groups]
|
|
126
|
+
dev = [
|
|
127
|
+
"build>=1.5.0",
|
|
128
|
+
"twine>=6.2.0",
|
|
129
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Run the same checks as CI / pre-commit (ruff, basedpyright, pytest).
|
|
3
|
+
# Usage: from repo root — make check or bash scripts/check.sh
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
cd "$ROOT"
|
|
8
|
+
|
|
9
|
+
run_checks() {
|
|
10
|
+
python -m ruff check .
|
|
11
|
+
python -m basedpyright
|
|
12
|
+
python -m pytest
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if command -v uv >/dev/null 2>&1; then
|
|
16
|
+
# Resolves the project env (creates .venv / syncs deps if needed).
|
|
17
|
+
uv run --extra dev python -m ruff check .
|
|
18
|
+
uv run --extra dev python -m basedpyright
|
|
19
|
+
uv run --extra dev python -m pytest
|
|
20
|
+
exit 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [[ -d .venv ]]; then
|
|
24
|
+
# shellcheck source=/dev/null
|
|
25
|
+
source "${ROOT}/.venv/bin/activate"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if ! python -c "import bling_jwt_auth" 2>/dev/null; then
|
|
29
|
+
echo "error: bling_jwt_auth not importable — run: uv sync --extra dev" >&2
|
|
30
|
+
echo " (or: pip install -e '.[dev]')" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
run_checks
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Bling API v3 OAuth helpers (JWT-ready)."""
|
|
2
|
+
|
|
3
|
+
from bling_jwt_auth.config import BlingAuthSettings, TokenStoreKind
|
|
4
|
+
from bling_jwt_auth.exceptions import BlingAuthError, OAuthRequestError, TokenNotFoundError
|
|
5
|
+
from bling_jwt_auth.headers import bling_api_headers
|
|
6
|
+
from bling_jwt_auth.manager import TokenManager
|
|
7
|
+
from bling_jwt_auth.models.token import StoredToken, TokenResponse
|
|
8
|
+
from bling_jwt_auth.oauth.client import OAuthClient
|
|
9
|
+
from bling_jwt_auth.storage.base import TokenStore
|
|
10
|
+
from bling_jwt_auth.storage.factory import create_token_store
|
|
11
|
+
from bling_jwt_auth.storage.file import FileTokenStore
|
|
12
|
+
from bling_jwt_auth.storage.sqlite import SQLiteTokenStore
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BlingAuthError",
|
|
16
|
+
"BlingAuthSettings",
|
|
17
|
+
"FileTokenStore",
|
|
18
|
+
"OAuthClient",
|
|
19
|
+
"OAuthRequestError",
|
|
20
|
+
"SQLiteTokenStore",
|
|
21
|
+
"StoredToken",
|
|
22
|
+
"TokenManager",
|
|
23
|
+
"TokenNotFoundError",
|
|
24
|
+
"TokenResponse",
|
|
25
|
+
"TokenStore",
|
|
26
|
+
"TokenStoreKind",
|
|
27
|
+
"bling_api_headers",
|
|
28
|
+
"create_token_store",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|