crypticorn 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
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.
- crypticorn/common/auth.py +2 -2
- crypticorn/common/auth_client.py +60 -40
- crypticorn/common/scopes.py +8 -1
- {crypticorn-2.0.0.dist-info → crypticorn-2.0.1.dist-info}/METADATA +24 -6
- {crypticorn-2.0.0.dist-info → crypticorn-2.0.1.dist-info}/RECORD +7 -7
- {crypticorn-2.0.0.dist-info → crypticorn-2.0.1.dist-info}/WHEEL +0 -0
- {crypticorn-2.0.0.dist-info → crypticorn-2.0.1.dist-info}/top_level.txt +0 -0
crypticorn/common/auth.py
CHANGED
@@ -5,7 +5,7 @@ from enum import Enum
|
|
5
5
|
from fastapi import params
|
6
6
|
from fastapi.security import APIKeyHeader, HTTPBearer
|
7
7
|
|
8
|
-
from crypticorn.common import
|
8
|
+
from crypticorn.common import Scope
|
9
9
|
|
10
10
|
|
11
11
|
http_bearer = HTTPBearer(
|
@@ -26,7 +26,7 @@ def Security( # noqa: N802
|
|
26
26
|
Optional[Callable[..., Any]], Doc("A dependable callable (like a function).")
|
27
27
|
] = None,
|
28
28
|
scopes: Annotated[
|
29
|
-
Optional[list[
|
29
|
+
Optional[list[Scope]], # Optional[Sequence[Union[str, Scope]]],
|
30
30
|
Doc("OAuth2 scopes required for the *path operation*."),
|
31
31
|
] = None,
|
32
32
|
use_cache: Annotated[
|
crypticorn/common/auth_client.py
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
from fastapi import Depends, HTTPException, status
|
2
2
|
from fastapi.security import HTTPAuthorizationCredentials
|
3
3
|
from typing_extensions import Annotated, Doc
|
4
|
+
import json
|
4
5
|
|
5
6
|
from crypticorn.auth import AuthClient, Verify200Response
|
6
|
-
from crypticorn.auth.client.exceptions import
|
7
|
+
from crypticorn.auth.client.exceptions import ApiException
|
7
8
|
from crypticorn.common import (
|
8
9
|
ApiError,
|
9
|
-
|
10
|
+
Scope,
|
10
11
|
ApiVersion,
|
11
12
|
BaseURL,
|
12
13
|
Domain,
|
@@ -41,29 +42,17 @@ class AuthHandler:
|
|
41
42
|
self.whitelist = whitelist
|
42
43
|
self.auth_client = AuthClient(base_url=base_url, api_version=api_version)
|
43
44
|
|
44
|
-
self.invalid_scopes_exception = HTTPException(
|
45
|
-
status_code=status.HTTP_403_FORBIDDEN,
|
46
|
-
detail=ApiError.INSUFFICIENT_SCOPES.identifier,
|
47
|
-
)
|
48
45
|
self.no_credentials_exception = HTTPException(
|
49
46
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
50
47
|
detail=ApiError.NO_CREDENTIALS.identifier,
|
51
48
|
)
|
52
|
-
self.invalid_api_key_exception = HTTPException(
|
53
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
54
|
-
detail=ApiError.INVALID_API_KEY.identifier,
|
55
|
-
)
|
56
|
-
self.invalid_bearer_exception = HTTPException(
|
57
|
-
status_code=status.HTTP_401_UNAUTHORIZED,
|
58
|
-
detail=ApiError.INVALID_BEARER.identifier,
|
59
|
-
)
|
60
49
|
|
61
50
|
async def _verify_api_key(self, api_key: str) -> None:
|
62
51
|
"""
|
63
52
|
Verifies the API key.
|
64
53
|
"""
|
65
54
|
# TODO: Implement in auth service
|
66
|
-
|
55
|
+
raise NotImplementedError("API key verification not implemented")
|
67
56
|
|
68
57
|
async def _verify_bearer(
|
69
58
|
self, bearer: HTTPAuthorizationCredentials
|
@@ -74,32 +63,66 @@ class AuthHandler:
|
|
74
63
|
self.auth_client.config.access_token = bearer.credentials
|
75
64
|
return await self.auth_client.login.verify()
|
76
65
|
|
77
|
-
async def
|
78
|
-
self, api_scopes: list[
|
66
|
+
async def _validate_scopes(
|
67
|
+
self, api_scopes: list[Scope], user_scopes: list[Scope]
|
79
68
|
) -> bool:
|
80
69
|
"""
|
81
70
|
Checks if the user scopes are a subset of the API scopes.
|
82
71
|
"""
|
83
|
-
|
72
|
+
if not set(api_scopes).issubset(user_scopes):
|
73
|
+
raise HTTPException(
|
74
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
75
|
+
detail=ApiError.INSUFFICIENT_SCOPES.identifier,
|
76
|
+
)
|
84
77
|
|
78
|
+
async def _extract_message(self, e: ApiException) -> str:
|
79
|
+
'''
|
80
|
+
Tries to extract the message from the body of the exception.
|
81
|
+
'''
|
82
|
+
try:
|
83
|
+
load = json.loads(e.body)
|
84
|
+
except (json.JSONDecodeError, TypeError):
|
85
|
+
return e.body
|
86
|
+
else:
|
87
|
+
common_keys = ["message"]
|
88
|
+
for key in common_keys:
|
89
|
+
if key in load:
|
90
|
+
return load[key]
|
91
|
+
return load
|
92
|
+
|
93
|
+
async def _handle_exception(self, e: Exception) -> HTTPException:
|
94
|
+
'''
|
95
|
+
Handles exceptions and returns a HTTPException with the appropriate status code and detail.
|
96
|
+
'''
|
97
|
+
if isinstance(e, ApiException):
|
98
|
+
return HTTPException(
|
99
|
+
status_code=e.status,
|
100
|
+
detail=await self._extract_message(e),
|
101
|
+
)
|
102
|
+
elif isinstance(e, HTTPException):
|
103
|
+
return e
|
104
|
+
else:
|
105
|
+
return HTTPException(
|
106
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
107
|
+
)
|
108
|
+
|
85
109
|
async def api_key_auth(
|
86
110
|
self,
|
87
111
|
api_key: Annotated[str | None, Depends(apikey_header)] = None,
|
88
|
-
scopes: list[
|
112
|
+
scopes: list[Scope] = [],
|
89
113
|
) -> Verify200Response:
|
90
114
|
"""
|
91
115
|
Verifies the API key and checks if the user scopes are a subset of the API scopes.
|
92
116
|
"""
|
93
|
-
if not api_key:
|
94
|
-
raise self.no_credentials_exception
|
95
117
|
try:
|
118
|
+
if not api_key:
|
119
|
+
raise self.no_credentials_exception
|
120
|
+
|
96
121
|
res = await self._verify_api_key(api_key)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
raise self.invalid_scopes_exception
|
102
|
-
return res
|
122
|
+
await self._validate_scopes(scopes, [Scope.from_str(scope) for scope in res.scopes])
|
123
|
+
return res
|
124
|
+
except Exception as e:
|
125
|
+
raise await self._handle_exception(e)
|
103
126
|
|
104
127
|
async def bearer_auth(
|
105
128
|
self,
|
@@ -107,7 +130,7 @@ class AuthHandler:
|
|
107
130
|
HTTPAuthorizationCredentials | None,
|
108
131
|
Depends(http_bearer),
|
109
132
|
] = None,
|
110
|
-
scopes: list[
|
133
|
+
scopes: list[Scope] = [],
|
111
134
|
) -> Verify200Response:
|
112
135
|
"""
|
113
136
|
Verifies the bearer token and checks if the user scopes are a subset of the API scopes.
|
@@ -117,12 +140,11 @@ class AuthHandler:
|
|
117
140
|
|
118
141
|
try:
|
119
142
|
res = await self._verify_bearer(bearer)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
return res
|
143
|
+
await self._validate_scopes(scopes, [Scope.from_str(scope) for scope in res.scopes])
|
144
|
+
return res
|
145
|
+
except Exception as e:
|
146
|
+
raise await self._handle_exception(e)
|
147
|
+
|
126
148
|
|
127
149
|
async def combined_auth(
|
128
150
|
self,
|
@@ -130,7 +152,7 @@ class AuthHandler:
|
|
130
152
|
HTTPAuthorizationCredentials | None, Depends(http_bearer)
|
131
153
|
] = None,
|
132
154
|
api_key: Annotated[str | None, Depends(apikey_header)] = None,
|
133
|
-
scopes: list[
|
155
|
+
scopes: list[Scope] = [],
|
134
156
|
) -> Verify200Response:
|
135
157
|
"""
|
136
158
|
Verifies the bearer token and API key and checks if the user scopes are a subset of the API scopes.
|
@@ -151,13 +173,11 @@ class AuthHandler:
|
|
151
173
|
if res is None:
|
152
174
|
continue
|
153
175
|
if scopes:
|
154
|
-
|
155
|
-
if not valid_scopes:
|
156
|
-
raise self.invalid_scopes_exception
|
176
|
+
await self._validate_scopes(scopes, [Scope.from_str(scope) for scope in res.scopes])
|
157
177
|
return res
|
158
178
|
|
159
|
-
except
|
160
|
-
last_error = e
|
179
|
+
except Exception as e:
|
180
|
+
last_error = await self._handle_exception(e)
|
161
181
|
continue
|
162
182
|
|
163
183
|
raise last_error or self.no_credentials_exception
|
crypticorn/common/scopes.py
CHANGED
@@ -1,11 +1,15 @@
|
|
1
1
|
from enum import Enum
|
2
2
|
|
3
3
|
|
4
|
-
class
|
4
|
+
class Scope(str, Enum):
|
5
5
|
"""
|
6
6
|
The permission scopes for the API.
|
7
7
|
"""
|
8
8
|
|
9
|
+
@classmethod
|
10
|
+
def from_str(cls, value: str) -> "Scope":
|
11
|
+
return cls(value)
|
12
|
+
|
9
13
|
# Hive scopes
|
10
14
|
HIVE_MODEL_READ = "hive:model:read"
|
11
15
|
HIVE_DATA_READ = "hive:data:read"
|
@@ -27,3 +31,6 @@ class ApiScope(str, Enum):
|
|
27
31
|
TRADE_NOTIFICATIONS_WRITE = "trade:notifications:write"
|
28
32
|
TRADE_STRATEGIES_READ = "trade:strategies:read"
|
29
33
|
TRADE_STRATEGIES_WRITE = "trade:strategies:write"
|
34
|
+
|
35
|
+
# Read projections
|
36
|
+
READ_PREDICTIONS = "read:predictions"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: crypticorn
|
3
|
-
Version: 2.0.
|
3
|
+
Version: 2.0.1
|
4
4
|
Summary: Maximise Your Crypto Trading Profits with AI Predictions
|
5
5
|
Author-email: Crypticorn <timon@crypticorn.com>
|
6
6
|
Project-URL: Homepage, https://crypticorn.com
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Typing :: Typed
|
14
14
|
Requires-Python: >=3.10
|
15
15
|
Description-Content-Type: text/markdown
|
16
|
+
Requires-Dist: fastapi
|
16
17
|
Requires-Dist: urllib3<3.0.0,>=1.25.3
|
17
18
|
Requires-Dist: python_dateutil>=2.8.2
|
18
19
|
Requires-Dist: aiohttp>=3.8.4
|
@@ -33,10 +34,7 @@ Requires-Dist: pyflakes; extra == "dev"
|
|
33
34
|
Provides-Extra: test
|
34
35
|
Requires-Dist: pytest>=7.2.1; extra == "test"
|
35
36
|
Requires-Dist: pytest-cov>=2.8.1; extra == "test"
|
36
|
-
Requires-Dist:
|
37
|
-
Requires-Dist: flake8>=4.0.0; extra == "test"
|
38
|
-
Requires-Dist: types-python-dateutil>=2.8.19.14; extra == "test"
|
39
|
-
Requires-Dist: mypy>=1.5; extra == "test"
|
37
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
|
40
38
|
|
41
39
|
# What is Crypticorn?
|
42
40
|
|
@@ -50,9 +48,28 @@ Crypticorn offers AI-based solutions for both active and passive investors, incl
|
|
50
48
|
Use this API Client to contribute to the so-called Hive AI, a community driven AI Meta Model for predicting the
|
51
49
|
cryptocurrency market.
|
52
50
|
|
51
|
+
## Installation
|
52
|
+
|
53
|
+
You can install the latest stable version from PyPi:
|
54
|
+
```bash
|
55
|
+
pip install crypticorn
|
56
|
+
```
|
57
|
+
|
58
|
+
If you want a specific version, run:
|
59
|
+
```bash
|
60
|
+
pip install crypticorn==2.0.0
|
61
|
+
```
|
62
|
+
|
63
|
+
If you want the latest version, which could be a pre release, run:
|
64
|
+
```bash
|
65
|
+
pip install --pre crypticorn
|
66
|
+
```
|
67
|
+
|
53
68
|
## Usage
|
54
69
|
|
55
|
-
|
70
|
+
As of know the library is available in async mode only. There are two was of using it.
|
71
|
+
|
72
|
+
## With Async Context Protocol
|
56
73
|
```python
|
57
74
|
async with ApiClient(base_url="http://localhost", jwt=jwt) as client:
|
58
75
|
# json response
|
@@ -66,6 +83,7 @@ async with ApiClient(base_url="http://localhost", jwt=jwt) as client:
|
|
66
83
|
print(response)
|
67
84
|
```
|
68
85
|
|
86
|
+
## Without Async Context Protocol
|
69
87
|
Without the context you need to close the session manually.
|
70
88
|
```python
|
71
89
|
client = ApiClient(base_url="http://localhost", jwt=jwt)
|
@@ -50,10 +50,10 @@ crypticorn/auth/client/models/verify_wallet_request.py,sha256=b0DAocvhKzPXPjM62D
|
|
50
50
|
crypticorn/auth/client/models/wallet_verified200_response.py,sha256=QILnTLsCKdI-WdV_fsLBy1UH4ZZU-U-wWJ9ot8v08tI,2465
|
51
51
|
crypticorn/auth/client/models/whoami200_response.py,sha256=uehdq5epgeOphhrIR3tbrseflxcLAzGyKF-VW-o5cY8,2974
|
52
52
|
crypticorn/common/__init__.py,sha256=lY87VMTkIEqto6kcEjC1YWsUvT03QuPmXwxCaeWE854,196
|
53
|
-
crypticorn/common/auth.py,sha256=
|
54
|
-
crypticorn/common/auth_client.py,sha256=
|
53
|
+
crypticorn/common/auth.py,sha256=SncaDgteLLyypwiRGd1fEF_yvy2mxPy0YNkLgv30ARQ,1204
|
54
|
+
crypticorn/common/auth_client.py,sha256=uqf4IL0UfnxO8TxZHEhaZb9CzI4vUOKpU2wZXBud-30,6122
|
55
55
|
crypticorn/common/errors.py,sha256=jGAS7TONKhdaRfft7w-0wrJ3BBTzqS6u03susiqAF0w,12723
|
56
|
-
crypticorn/common/scopes.py,sha256=
|
56
|
+
crypticorn/common/scopes.py,sha256=PkInTfPGelXW3jXJlMiOOZsO5uPW-FDjKDediZh9xqQ,1144
|
57
57
|
crypticorn/common/urls.py,sha256=_NMhvhZXOsZpDBbgucqu0yboRFox6JVMlOoQq_Y5SGA,432
|
58
58
|
crypticorn/hive/__init__.py,sha256=hRfTlEzEql4msytdUC_04vfaHzVKG5CGZle1M-9QFgY,81
|
59
59
|
crypticorn/hive/main.py,sha256=RmCYSR0jwmfYWTK89dt79DuGPjEaip9XQs_LWNWr_tc,1036
|
@@ -220,7 +220,7 @@ crypticorn/trade/client/models/trading_action_type.py,sha256=jW0OsNz_ZNXlITxAfh9
|
|
220
220
|
crypticorn/trade/client/models/update_notification.py,sha256=B9QUuVRNpk1e5G8o0WFgIg3inm-OX7KJAJcjVnRzYx8,3046
|
221
221
|
crypticorn/trade/client/models/validation_error.py,sha256=uTkvsKrOAt-21UC0YPqCdRl_OMsuu7uhPtWuwRSYvv0,3228
|
222
222
|
crypticorn/trade/client/models/validation_error_loc_inner.py,sha256=22ql-H829xTBgfxNQZsqd8fS3zQt9tLW1pj0iobo0jY,5131
|
223
|
-
crypticorn-2.0.
|
224
|
-
crypticorn-2.0.
|
225
|
-
crypticorn-2.0.
|
226
|
-
crypticorn-2.0.
|
223
|
+
crypticorn-2.0.1.dist-info/METADATA,sha256=CCwYU2zydLX-poZzGLMXdQ1QIy6s2Oom8n2GP-O1i4k,3066
|
224
|
+
crypticorn-2.0.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
225
|
+
crypticorn-2.0.1.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
|
226
|
+
crypticorn-2.0.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|