jamlib 0.0.2a0__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.
- jamlib-0.0.2a0/LICENSE.md +23 -0
- jamlib-0.0.2a0/PKG-INFO +37 -0
- jamlib-0.0.2a0/README.md +18 -0
- jamlib-0.0.2a0/jam/__init__.py +3 -0
- jamlib-0.0.2a0/jam/config.py +28 -0
- jamlib-0.0.2a0/jam/jwt/__errors__.py +16 -0
- jamlib-0.0.2a0/jam/jwt/__init__.py +1 -0
- jamlib-0.0.2a0/jam/jwt/tools.py +275 -0
- jamlib-0.0.2a0/jam/jwt/types.py +16 -0
- jamlib-0.0.2a0/pyproject.toml +79 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Adrian Makridenko
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
jamlib-0.0.2a0/PKG-INFO
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: jamlib
|
|
3
|
+
Version: 0.0.2a0
|
|
4
|
+
Summary: Simple and univirsal library for authorization
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Makridenko Adrian
|
|
7
|
+
Author-email: adrianmakridenko@duck.com
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: pydantic (>=2.10.5,<3.0.0)
|
|
13
|
+
Requires-Dist: pydantic-settings (>=2.7.1,<3.0.0)
|
|
14
|
+
Project-URL: Homepage, https://github.com/lyaguxafrog/jam
|
|
15
|
+
Project-URL: Issues, https://github.com/lyaguxafrog/jam/issues
|
|
16
|
+
Project-URL: Repository, https://github.com/lyaguxafrog/jam
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# jam
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
 
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
> [!CAUTION]
|
|
26
|
+
> In active development! Cannot be used in real projects!
|
|
27
|
+
>
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
- [ ] JWT Making
|
|
32
|
+
- [ ] Another crypt alghorutms
|
|
33
|
+
- [ ] White/Black lists
|
|
34
|
+
- [ ] Session Maker
|
|
35
|
+
- [ ] Integration with Django, FastAPI, Strawberry
|
|
36
|
+
- [ ] OAuth2
|
|
37
|
+
|
jamlib-0.0.2a0/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# jam
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
 
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
> [!CAUTION]
|
|
8
|
+
> In active development! Cannot be used in real projects!
|
|
9
|
+
>
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
- [ ] JWT Making
|
|
14
|
+
- [ ] Another crypt alghorutms
|
|
15
|
+
- [ ] White/Black lists
|
|
16
|
+
- [ ] Session Maker
|
|
17
|
+
- [ ] Integration with Django, FastAPI, Strawberry
|
|
18
|
+
- [ ] OAuth2
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# -*- coding: utf -*-
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from pydantic_settings import BaseSettings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JAMConfig(BaseSettings):
|
|
9
|
+
JWT_ACCESS_SECRET_KEY: str | None
|
|
10
|
+
JWT_REFRESH_SECRET_KEY: str | None
|
|
11
|
+
JWT_ALGORITHM: Literal[
|
|
12
|
+
"HS256",
|
|
13
|
+
"HS384",
|
|
14
|
+
"HS512",
|
|
15
|
+
"RS256",
|
|
16
|
+
"RS384",
|
|
17
|
+
"RS512",
|
|
18
|
+
"ES256",
|
|
19
|
+
"ES384",
|
|
20
|
+
"ES512",
|
|
21
|
+
"PS256",
|
|
22
|
+
"PS384",
|
|
23
|
+
"PS512",
|
|
24
|
+
] = "HS256"
|
|
25
|
+
|
|
26
|
+
JWT_ACCESS_EXP: int = 3600
|
|
27
|
+
JWT_REFRESH_EXP: int = JWT_ACCESS_EXP
|
|
28
|
+
JWT_HEADERS: dict = {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class JamNullJWTSecret(Exception):
|
|
5
|
+
def __init__(self, message="Secret keys cannot be Null") -> None:
|
|
6
|
+
self.message = message
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JamJWTMakingError(Exception):
|
|
10
|
+
def __init__(self, message) -> None:
|
|
11
|
+
self.message = message
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class JamInvalidSignature(Exception):
|
|
15
|
+
def __init__(self, message="Invalid signature") -> None:
|
|
16
|
+
self.message = message
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import hashlib
|
|
5
|
+
import hmac
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import secrets
|
|
9
|
+
import time
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
from jam.config import JAMConfig
|
|
13
|
+
from jam.jwt.__errors__ import JamInvalidSignature as InvalidSignature
|
|
14
|
+
from jam.jwt.__errors__ import JamJWTMakingError as JWTError
|
|
15
|
+
from jam.jwt.__errors__ import JamNullJWTSecret as NullSecret
|
|
16
|
+
from jam.jwt.types import Tokens
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def __check_secrets__(config: JAMConfig) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
Private tool for check secrets in confg
|
|
22
|
+
|
|
23
|
+
:param config: Base jam config
|
|
24
|
+
:type config: jam.config.JAMConfig
|
|
25
|
+
|
|
26
|
+
:returns: True if secrets in config
|
|
27
|
+
:rtype: bool
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
if not config.JWT_ACCESS_SECRET_KEY or not config.JWT_REFRESH_SECRET_KEY:
|
|
31
|
+
raise NullSecret
|
|
32
|
+
|
|
33
|
+
else:
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __gen_access_token__(config: JAMConfig, payload: dict) -> str:
|
|
38
|
+
"""
|
|
39
|
+
Private tool for generating access token
|
|
40
|
+
|
|
41
|
+
:param config: Standart jam confg
|
|
42
|
+
:type config: jam.config.JAMConfig
|
|
43
|
+
:param payload: Custom user payload
|
|
44
|
+
:type payload: dict
|
|
45
|
+
|
|
46
|
+
:returns: Returns access token by string
|
|
47
|
+
:rtype: str
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if not config.JWT_ACCESS_SECRET_KEY:
|
|
51
|
+
raise NullSecret(message="JWT_ACCESS_SECRET_KEY is null")
|
|
52
|
+
|
|
53
|
+
__payload__: dict = {
|
|
54
|
+
"data": payload,
|
|
55
|
+
"exp": int(time.time()) + config.JWT_ACCESS_EXP,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
encoded_header: str = (
|
|
59
|
+
base64.urlsafe_b64encode(
|
|
60
|
+
json.dumps({"alg": config.JWT_ALGORITHM, "typ": "JWT"}).encode()
|
|
61
|
+
)
|
|
62
|
+
.decode()
|
|
63
|
+
.rstrip("=")
|
|
64
|
+
)
|
|
65
|
+
encoded_payload: str = (
|
|
66
|
+
base64.urlsafe_b64encode(json.dumps(__payload__).encode())
|
|
67
|
+
.decode()
|
|
68
|
+
.rstrip("=")
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
__signature__: bytes = hmac.new(
|
|
72
|
+
config.JWT_ACCESS_SECRET_KEY.encode(),
|
|
73
|
+
f"{encoded_header}.{encoded_payload}".encode(),
|
|
74
|
+
hashlib.sha256,
|
|
75
|
+
).digest()
|
|
76
|
+
encoded_signature: str = (
|
|
77
|
+
base64.urlsafe_b64encode(__signature__).decode().rstrip("=")
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
access_token: str = (
|
|
81
|
+
f"{encoded_header}.{encoded_payload}.{encoded_signature}"
|
|
82
|
+
)
|
|
83
|
+
return access_token
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def __gen_refresh_token__(config: JAMConfig, payload: dict) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Private tool for generating refresh token
|
|
89
|
+
|
|
90
|
+
:param config: Standart jam config
|
|
91
|
+
:type config: jam.config.JAMConfig
|
|
92
|
+
:param payload: Custom user payload
|
|
93
|
+
:type payload: dict
|
|
94
|
+
|
|
95
|
+
:returns: Returns refresh roken by string
|
|
96
|
+
:type: str
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
if not config.JWT_REFRESH_SECRET_KEY:
|
|
100
|
+
raise NullSecret(message="JWT_REFRESH_TOKEN is null")
|
|
101
|
+
|
|
102
|
+
__payload__: dict = {
|
|
103
|
+
"data": payload,
|
|
104
|
+
"exp": int(time.time()) + config.JWT_REFRESH_EXP,
|
|
105
|
+
"jti": secrets.token_hex(16),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
encoded_header: str = (
|
|
109
|
+
base64.urlsafe_b64encode(
|
|
110
|
+
json.dumps({"alg": config.JWT_ALGORITHM, "typ": "JWT"}).encode()
|
|
111
|
+
)
|
|
112
|
+
.decode()
|
|
113
|
+
.rstrip("=")
|
|
114
|
+
)
|
|
115
|
+
encoded_payload: str = (
|
|
116
|
+
base64.urlsafe_b64encode(json.dumps(__payload__).encode())
|
|
117
|
+
.decode()
|
|
118
|
+
.rstrip("=")
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
__signature__: bytes = hmac.new(
|
|
122
|
+
config.JWT_REFRESH_SECRET_KEY.encode(),
|
|
123
|
+
f"{encoded_header}.{encoded_payload}".encode(),
|
|
124
|
+
hashlib.sha256,
|
|
125
|
+
).digest()
|
|
126
|
+
encoded_signature: str = (
|
|
127
|
+
base64.urlsafe_b64encode(__signature__).decode().rstrip("=")
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
refresh_token: str = (
|
|
131
|
+
f"{encoded_header}.{encoded_payload}.{encoded_signature}"
|
|
132
|
+
)
|
|
133
|
+
return refresh_token
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def gen_jwt_tokens(*, config: JAMConfig, payload: dict = {}) -> Tokens:
|
|
137
|
+
"""
|
|
138
|
+
Service for generating JWT tokens
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
```
|
|
142
|
+
config = JAMConfig(
|
|
143
|
+
JWT_ACCESS_SECRET_KEY="SOME_SUPER_SECRET_KEY",
|
|
144
|
+
JWT_REFRESH_SECRET_KEY="ANOTHER_SECRET_KEY"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
payload: dict = {
|
|
148
|
+
"id": 1,
|
|
149
|
+
"username": "lyaguxafrog"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
tokens = gen_jwt_tokens(config=config, payload=payload)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
:param config: Standart jam config
|
|
156
|
+
:type config: jam.config.JAMConfig
|
|
157
|
+
:param payload: Custom user payload
|
|
158
|
+
:type payload: dict
|
|
159
|
+
|
|
160
|
+
:returns: Base model with access and refresh tokens
|
|
161
|
+
:rtype: jam.jwt.types.Tokens
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
access: str = __gen_access_token__(config, payload)
|
|
166
|
+
refresh: str = __gen_refresh_token__(config, payload)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
raise JWTError(message=e)
|
|
170
|
+
|
|
171
|
+
return Tokens(access=access, refresh=refresh)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def check_jwt_signature(
|
|
175
|
+
*, config: JAMConfig, token_type: Literal["access", "refresh"], token: str
|
|
176
|
+
) -> bool:
|
|
177
|
+
"""
|
|
178
|
+
Service for checking JWT signature
|
|
179
|
+
|
|
180
|
+
:param config: Base jam config
|
|
181
|
+
:type config: jam.config.JAMConfig
|
|
182
|
+
:param token: JWT token
|
|
183
|
+
:type token: str
|
|
184
|
+
:param key_type: Type of JWT ( access token or refresh token )
|
|
185
|
+
:type key_type: str
|
|
186
|
+
|
|
187
|
+
:returns: Bool with signature status
|
|
188
|
+
:rtype: bool
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
if token_type == "access":
|
|
192
|
+
secret_key: str | None = config.JWT_ACCESS_SECRET_KEY
|
|
193
|
+
|
|
194
|
+
elif token_type == "refresh":
|
|
195
|
+
secret_key: str | None = config.JWT_REFRESH_SECRET_KEY
|
|
196
|
+
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError("Invalid key type. Must be 'access' or 'refresh'.")
|
|
199
|
+
|
|
200
|
+
if not secret_key:
|
|
201
|
+
raise NullSecret("The specified secret key is missing.")
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
header, payload, signature = token.split(".")
|
|
205
|
+
except ValueError:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
"Invalid token format. Token must have three parts separated by '.'"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
data_to_sign = f"{header}.{payload}".encode("utf-8")
|
|
211
|
+
|
|
212
|
+
expected_signature = (
|
|
213
|
+
base64.urlsafe_b64encode(
|
|
214
|
+
hmac.new(
|
|
215
|
+
secret_key.encode("utf-8"), data_to_sign, hashlib.sha256
|
|
216
|
+
).digest()
|
|
217
|
+
)
|
|
218
|
+
.decode("utf-8")
|
|
219
|
+
.rstrip("=")
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return expected_signature == signature
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def decode_token(
|
|
226
|
+
*,
|
|
227
|
+
config: JAMConfig,
|
|
228
|
+
token: str,
|
|
229
|
+
checksum: bool = False,
|
|
230
|
+
checksum_token_type: Literal["access", "refresh"] | None = None,
|
|
231
|
+
) -> dict:
|
|
232
|
+
"""
|
|
233
|
+
Service for decoding JWT token
|
|
234
|
+
|
|
235
|
+
:param config: Base jam config
|
|
236
|
+
:type config: jam.config.JAMConfig
|
|
237
|
+
:param token: Some jwt token
|
|
238
|
+
:type token: str
|
|
239
|
+
:param checksum: Use `check_jwt_signature` in decode?
|
|
240
|
+
:type checksum: bool
|
|
241
|
+
:param checksum_token_type: Type of JWT ( access or refresh )
|
|
242
|
+
:type checksum_token_type: str | None
|
|
243
|
+
|
|
244
|
+
:retutns: Dict with information in token
|
|
245
|
+
:rtype: dict
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
if checksum:
|
|
249
|
+
sum: bool = check_jwt_signature(
|
|
250
|
+
config=config, token_type=checksum_token_type, token=token # type: ignore
|
|
251
|
+
)
|
|
252
|
+
if not sum:
|
|
253
|
+
raise InvalidSignature
|
|
254
|
+
else:
|
|
255
|
+
logging.info("Signature valid")
|
|
256
|
+
pass
|
|
257
|
+
else:
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
if not config.JWT_ACCESS_SECRET_KEY or not config.JWT_REFRESH_SECRET_KEY:
|
|
261
|
+
raise NullSecret
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
header, payload, signature = token.split(".")
|
|
265
|
+
except ValueError:
|
|
266
|
+
raise ValueError(
|
|
267
|
+
"Invalid token format. Token must have three parts separated by '.'"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
padding: str = "=" * (4 - len(payload) % 4)
|
|
272
|
+
decoded_payload: bytes = base64.urlsafe_b64decode(payload + padding)
|
|
273
|
+
return json.loads(decoded_payload)
|
|
274
|
+
except (ValueError, json.JSONDecodeError) as e:
|
|
275
|
+
raise ValueError("Failed to decode the payload: " + str(e))
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "jamlib"
|
|
3
|
+
version = "0.0.2-alpha"
|
|
4
|
+
description = "Simple and univirsal library for authorization"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Makridenko Adrian",email = "adrianmakridenko@duck.com"}
|
|
7
|
+
]
|
|
8
|
+
license = {text = "MIT"}
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pydantic (>=2.10.5,<3.0.0)",
|
|
13
|
+
"pydantic-settings (>=2.7.1,<3.0.0)"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/lyaguxafrog/jam"
|
|
18
|
+
Repository = "https://github.com/lyaguxafrog/jam"
|
|
19
|
+
Issues = "https://github.com/lyaguxafrog/jam/issues"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|
|
25
|
+
|
|
26
|
+
[tool.poetry]
|
|
27
|
+
packages = [
|
|
28
|
+
{ include = "jam" },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.poetry.group.dev.dependencies]
|
|
32
|
+
pretty-errors = "^1.2.25"
|
|
33
|
+
icecream = "^2.1.4"
|
|
34
|
+
pre-commit = "^4.1.0"
|
|
35
|
+
flake8 = "^7.1.1"
|
|
36
|
+
isort = "^5.13.2"
|
|
37
|
+
black = "^24.10.0"
|
|
38
|
+
mypy = "^1.14.1"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
[tool.poetry.group.tests.dependencies]
|
|
42
|
+
pytest = "^8.3.4"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[tool.poetry.group.docs.dependencies]
|
|
46
|
+
mkdocs = "^1.6.1"
|
|
47
|
+
mkdocs-material = "^9.5.50"
|
|
48
|
+
mkdocstrings = "^0.27.0"
|
|
49
|
+
mkdocstrings-python = "^1.13.0"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
[tool.flake8]
|
|
53
|
+
ignore = ["D203", "E501"]
|
|
54
|
+
exclude = [".git", "__pycache__", "__init__.py", "docs/"]
|
|
55
|
+
max-complexity = 18
|
|
56
|
+
max-line-length = 80
|
|
57
|
+
|
|
58
|
+
[tool.isort]
|
|
59
|
+
profile = "black"
|
|
60
|
+
default_section = "THIRDPARTY"
|
|
61
|
+
balanced_wrapping = true
|
|
62
|
+
known_first_party = "src"
|
|
63
|
+
line_length = 80
|
|
64
|
+
lines_after_imports = 2
|
|
65
|
+
lines_between_sections = 1
|
|
66
|
+
multi_line_output = 3
|
|
67
|
+
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
|
|
68
|
+
skip = ["env", ".env", ".env.example"]
|
|
69
|
+
|
|
70
|
+
[tool.black]
|
|
71
|
+
line-length = 80
|
|
72
|
+
skip-string-normalization = false
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
disable_error_code = ["no-redef", "import-not-found"]
|
|
76
|
+
|
|
77
|
+
[tool.pytest]
|
|
78
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
79
|
+
python_files = ["test*.py"]
|