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 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 ApiScope
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[ApiScope]], # Optional[Sequence[Union[str, APIScope]]],
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[
@@ -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 UnauthorizedException
7
+ from crypticorn.auth.client.exceptions import ApiException
7
8
  from crypticorn.common import (
8
9
  ApiError,
9
- ApiScope,
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
- return NotImplementedError()
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 _check_scopes(
78
- self, api_scopes: list[ApiScope], user_scopes: list[ApiScope]
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
- return set(api_scopes).issubset(user_scopes)
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[ApiScope] = [],
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
- except UnauthorizedException as e:
98
- raise self.invalid_api_key_exception
99
- valid_scopes = await self._check_scopes(scopes, res.scopes)
100
- if not valid_scopes:
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[ApiScope] = [],
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
- except UnauthorizedException as e:
121
- raise self.invalid_bearer_exception
122
- valid_scopes = await self._check_scopes(scopes, res.scopes)
123
- if not valid_scopes:
124
- raise self.invalid_scopes_exception
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[ApiScope] = [],
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
- valid_scopes = await self._check_scopes(scopes, res.scopes)
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 UnauthorizedException as e:
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
@@ -1,11 +1,15 @@
1
1
  from enum import Enum
2
2
 
3
3
 
4
- class ApiScope(str, Enum):
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.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: tox>=3.9.0; extra == "test"
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
- Within an asynchronous context the session is closed automatically.
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=pqmn5wG9CWql7wwAOs3p8zWlR2XyOzcoA17xgC6KsjA,1213
54
- crypticorn/common/auth_client.py,sha256=PlMBSQ_SyWGvZQsc97DlokoWLprwlo0r9-oftD89Rps,5564
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=60dLff6zADjkKLwsOrwfpgFoCQjh3fqBS5Mpe5PonaU,991
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.0.dist-info/METADATA,sha256=WEAVy4EuJjyFyULElXpwnA3eyiqt5yeX7eGrwk_HVI4,2804
224
- crypticorn-2.0.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
225
- crypticorn-2.0.0.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
226
- crypticorn-2.0.0.dist-info/RECORD,,
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,,