novelai-python 0.1.0__tar.gz → 0.1.1__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.
@@ -0,0 +1,75 @@
1
+ Metadata-Version: 2.1
2
+ Name: novelai-python
3
+ Version: 0.1.1
4
+ Summary: Novelai Python Binding With Pydantic
5
+ Author-Email: sudoskys <coldlando@hotmail.com>
6
+ License: Apache 2.0
7
+ Project-URL: Repository, https://github.com/LlmKira/novelai-python/
8
+ Project-URL: Issues, https://github.com/LlmKira/novelai-python/issues
9
+ Requires-Python: >=3.8
10
+ Requires-Dist: elara>=0.5.5
11
+ Requires-Dist: loguru>=0.7.2
12
+ Requires-Dist: pydantic-settings>=2.1.0
13
+ Requires-Dist: pydantic>=2.5.3
14
+ Requires-Dist: httpx>=0.26.0
15
+ Requires-Dist: shortuuid>=1.0.11
16
+ Requires-Dist: Pillow>=10.2.0
17
+ Requires-Dist: curl-cffi>=0.5.10
18
+ Description-Content-Type: text/markdown
19
+
20
+ # novelai-python
21
+
22
+ [![PyPI version](https://badge.fury.io/py/novelai_python.svg)](https://badge.fury.io/py/novelai_gen)
23
+ [![Downloads](https://pepy.tech/badge/novelai_python)](https://pepy.tech/project/novelai_python)
24
+
25
+ The goal of this repository is to use Pydantic to build legitimate requests to access the Novelai API service.
26
+
27
+ ### Roadmap 🚧
28
+
29
+ - [x] /ai/generate-image
30
+ - [ ] /ai/generate-image/suggest-tags
31
+ - [ ] /ai/annotate-image
32
+ - [ ] /ai/classify
33
+ - [ ] /ai/upscale
34
+ - [ ] /ai/generate-prompt
35
+ - [ ] /ai/generate
36
+ - [ ] /ai/generate-voice
37
+
38
+ ### Usage 🖥️
39
+
40
+ ```python
41
+ import asyncio
42
+ import os
43
+
44
+ from dotenv import load_dotenv
45
+ from pydantic import SecretStr
46
+
47
+ from novelai_python import GenerateImageInfer, ImageGenerateResp, JwtCredential
48
+
49
+ load_dotenv()
50
+
51
+ enhance = "year 2023,dynamic angle, best quality, amazing quality, very aesthetic, absurdres"
52
+
53
+
54
+ async def main():
55
+ globe_s = JwtCredential(jwt_token=SecretStr(os.getenv("NOVELAI_JWT")))
56
+ _res = await GenerateImageInfer.build(
57
+ prompt=f"1girl,{enhance}").generate(
58
+ session=globe_s)
59
+ _res: ImageGenerateResp
60
+ print(_res.meta)
61
+ file = _res.files[0]
62
+ with open(file[0], "wb") as f:
63
+ f.write(file[1])
64
+
65
+
66
+ loop = asyncio.get_event_loop()
67
+ loop.run_until_complete(main())
68
+
69
+ ```
70
+
71
+ ## Acknowledgements 🙏
72
+
73
+ [BackEnd](https://api.novelai.net/docs)
74
+ [novelai-api](https://github.com/Aedial/novelai-api)
75
+
@@ -0,0 +1,56 @@
1
+ # novelai-python
2
+
3
+ [![PyPI version](https://badge.fury.io/py/novelai_python.svg)](https://badge.fury.io/py/novelai_gen)
4
+ [![Downloads](https://pepy.tech/badge/novelai_python)](https://pepy.tech/project/novelai_python)
5
+
6
+ The goal of this repository is to use Pydantic to build legitimate requests to access the Novelai API service.
7
+
8
+ ### Roadmap 🚧
9
+
10
+ - [x] /ai/generate-image
11
+ - [ ] /ai/generate-image/suggest-tags
12
+ - [ ] /ai/annotate-image
13
+ - [ ] /ai/classify
14
+ - [ ] /ai/upscale
15
+ - [ ] /ai/generate-prompt
16
+ - [ ] /ai/generate
17
+ - [ ] /ai/generate-voice
18
+
19
+ ### Usage 🖥️
20
+
21
+ ```python
22
+ import asyncio
23
+ import os
24
+
25
+ from dotenv import load_dotenv
26
+ from pydantic import SecretStr
27
+
28
+ from novelai_python import GenerateImageInfer, ImageGenerateResp, JwtCredential
29
+
30
+ load_dotenv()
31
+
32
+ enhance = "year 2023,dynamic angle, best quality, amazing quality, very aesthetic, absurdres"
33
+
34
+
35
+ async def main():
36
+ globe_s = JwtCredential(jwt_token=SecretStr(os.getenv("NOVELAI_JWT")))
37
+ _res = await GenerateImageInfer.build(
38
+ prompt=f"1girl,{enhance}").generate(
39
+ session=globe_s)
40
+ _res: ImageGenerateResp
41
+ print(_res.meta)
42
+ file = _res.files[0]
43
+ with open(file[0], "wb") as f:
44
+ f.write(file[1])
45
+
46
+
47
+ loop = asyncio.get_event_loop()
48
+ loop.run_until_complete(main())
49
+
50
+ ```
51
+
52
+ ## Acknowledgements 🙏
53
+
54
+ [BackEnd](https://api.novelai.net/docs)
55
+ [novelai-api](https://github.com/Aedial/novelai-api)
56
+
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "novelai-python"
3
+ version = "0.1.1"
4
+ description = "Novelai Python Binding With Pydantic"
5
+ authors = [
6
+ { name = "sudoskys", email = "coldlando@hotmail.com" },
7
+ ]
8
+ dependencies = [
9
+ "elara>=0.5.5",
10
+ "loguru>=0.7.2",
11
+ "pydantic-settings>=2.1.0",
12
+ "pydantic>=2.5.3",
13
+ "httpx>=0.26.0",
14
+ "shortuuid>=1.0.11",
15
+ "Pillow>=10.2.0",
16
+ "curl-cffi>=0.5.10",
17
+ ]
18
+ requires-python = ">=3.8"
19
+ readme = "README.md"
20
+
21
+ [project.license]
22
+ text = "Apache 2.0"
23
+
24
+ [project.urls]
25
+ Repository = "https://github.com/LlmKira/novelai-python/"
26
+ Issues = "https://github.com/LlmKira/novelai-python/issues"
27
+
28
+ [build-system]
29
+ requires = [
30
+ "pdm-backend",
31
+ ]
32
+ build-backend = "pdm.backend"
33
+
34
+ [tool.pdm]
35
+ distribution = true
@@ -0,0 +1,8 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2023/11/18 上午12:18
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
6
+
7
+ from .credential import * # noqa: F401, F403
8
+ from .sdk import * # noqa: F401, F403
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午10:55
3
+ # @Author : sudoskys
4
+ # @File : _exceptions.py
5
+ # @Software: PyCharm
6
+ from typing import Any, Optional, Union
7
+ from typing import TypeGuard
8
+
9
+
10
+ def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
11
+ """
12
+ Check if the object is a dict.
13
+ :param obj: The object to check.
14
+ :return: True if the object is a dict, False otherwise.
15
+ """
16
+ return isinstance(obj, dict)
17
+
18
+
19
+ class NovelAiError(Exception):
20
+ """
21
+ NovelAiError is the base exception for all novelai_python errors.
22
+ """
23
+ pass
24
+
25
+
26
+ class APIError(NovelAiError):
27
+ """
28
+ APIError is raised when the API returns an error.
29
+ """
30
+ message: str
31
+ request: Any
32
+ code: Optional[str] = None
33
+ response: Union[dict[str, Any], str] = None
34
+
35
+ def __init__(self, message: str, request: Any, response: Any, status_code: str) -> None:
36
+ super().__init__(message)
37
+ self.request = request
38
+ self.message = message
39
+ self.code = status_code
40
+ self.response = response
41
+
42
+
43
+ class AuthError(APIError):
44
+ """
45
+ AuthError is raised when the API returns an error.
46
+ """
47
+
48
+ def __init__(self, message: str, request: Any, response: Any, status_code: str) -> None:
49
+ super().__init__(message, request, response, status_code)
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午10:51
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
6
+ from .ai.generate_image import ImageGenerateResp
7
+
8
+ __all__ = [
9
+ "ImageGenerateResp"
10
+ ]
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午11:20
3
+ # @Author : sudoskys
4
+ # @File : __init__.py
5
+ # @Software: PyCharm
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午11:16
3
+ # @Author : sudoskys
4
+ # @File : text2image.py
5
+ # @Software: PyCharm
6
+ from typing import Tuple, List
7
+
8
+ from pydantic import BaseModel
9
+
10
+
11
+ class ImageGenerateResp(BaseModel):
12
+ class RequestParams(BaseModel):
13
+ endpoint: str
14
+ raw_request: dict = None
15
+
16
+ meta: RequestParams
17
+ files: List[Tuple[str, bytes]] = None
18
+
19
+ def query_params(self, key: str, default=None):
20
+ if not isinstance(self.meta.raw_request.get("parameters"), dict):
21
+ raise Exception("Resp parameters is not dict")
22
+ return self.meta.raw_request.get("parameters").get(key, default)
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午11:04
3
+ # @Author : sudoskys
4
+ # @File : JwtToken.py
5
+ # @Software: PyCharm
6
+ from curl_cffi.requests import AsyncSession
7
+ from pydantic import BaseModel, SecretStr, Field
8
+
9
+
10
+ class JwtCredential(BaseModel):
11
+ """
12
+ JwtCredential is the base class for all credential.
13
+ """
14
+ jwt_token: SecretStr = Field(None, description="jwt token")
15
+ _session: AsyncSession = None
16
+
17
+ @property
18
+ def session(self, timeout: int = 180):
19
+ if not self._session:
20
+ self._session = AsyncSession(timeout=timeout, headers={
21
+ "Authorization": f"Bearer {self.jwt_token.get_secret_value()}",
22
+ "Content-Type": "application/json",
23
+ "Origin": "https://novelai.net",
24
+ "Referer": "https://novelai.net/",
25
+ }, impersonate="chrome110")
26
+ return self._session
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午10:51
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
6
+ from .JwtToken import JwtCredential
7
+
8
+ __all__ = [
9
+ "JwtCredential"
10
+ ]
@@ -0,0 +1,8 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午10:51
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
6
+
7
+ from .ai.generate_image import GenerateImageInfer, ImageGenerateResp # noqa 401
8
+
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午11:20
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
- # @Time : 2023/11/18 上午12:18
2
+ # @Time : 2024/1/26 上午11:21
3
3
  # @Author : sudoskys
4
- # @File : __init__.py.py
4
+ # @File : generate_image.py
5
5
  # @Software: PyCharm
6
6
  import json
7
7
  import random
@@ -10,40 +10,17 @@ from typing import Optional, Union
10
10
  from zipfile import ZipFile
11
11
 
12
12
  import httpx
13
- import shortuuid
14
13
  from curl_cffi.requests import AsyncSession
15
- from dotenv import load_dotenv
16
14
  from loguru import logger
17
- from pydantic import BaseModel, ConfigDict, PrivateAttr, field_validator, SecretStr
15
+ from pydantic import BaseModel, ConfigDict, PrivateAttr, field_validator
18
16
 
19
- from .error import ServerError, AuthError
20
- from .hash import NaiPic
21
- from .schema import NaiResult
17
+ from ..._exceptions import APIError, AuthError
18
+ from ..._response import ImageGenerateResp
19
+ from ...credential import JwtCredential
20
+ from ...utils import try_jsonfy
22
21
 
23
- load_dotenv()
24
22
 
25
-
26
- class CheckError(Exception):
27
- pass
28
-
29
-
30
- class CurlSession(BaseModel):
31
- jwt_token: SecretStr
32
- _session: AsyncSession = None
33
-
34
- @property
35
- def session(self):
36
- if not self._session:
37
- self._session = AsyncSession(timeout=180, headers={
38
- "Authorization": f"Bearer {self.jwt_token.get_secret_value()}",
39
- "Content-Type": "application/json",
40
- "Origin": "https://novelai.net",
41
- "Referer": "https://novelai.net/",
42
- }, impersonate="chrome110")
43
- return self._session
44
-
45
-
46
- class NovelAiInference(BaseModel):
23
+ class GenerateImageInfer(BaseModel):
47
24
  _endpoint: Optional[str] = PrivateAttr("https://api.novelai.net")
48
25
  _charge: bool = PrivateAttr(False)
49
26
 
@@ -174,11 +151,11 @@ class NovelAiInference(BaseModel):
174
151
 
175
152
  def validate_charge(self):
176
153
  if self.parameters.steps > 28 and not self.charge:
177
- raise CheckError("steps must be less than 28 for free users.")
154
+ raise ValueError("steps must be less than 28 for free users.")
178
155
  if (self.parameters.width, self.parameters.height) not in self.valid_wh() and not self.charge:
179
- raise CheckError("Invalid size, must be one of 832x1216, 1216x832, 1024x1024 for free users.")
156
+ raise ValueError("Invalid size, must be one of 832x1216, 1216x832, 1024x1024 for free users.")
180
157
  if self.parameters.n_samples != 1 and not self.charge:
181
- raise CheckError("n_samples must be 1 for free users.")
158
+ raise ValueError("n_samples must be 1 for free users.")
182
159
  return self
183
160
 
184
161
  @classmethod
@@ -235,60 +212,63 @@ class NovelAiInference(BaseModel):
235
212
  parameters=cls.Params(**param)
236
213
  )
237
214
 
238
- async def generate(self, session: Union[AsyncSession, CurlSession]) -> NaiResult:
239
- if isinstance(session, CurlSession):
215
+ async def generate(self, session: Union[AsyncSession, JwtCredential]) -> ImageGenerateResp:
216
+ if isinstance(session, JwtCredential):
240
217
  session = session.session
241
- self.validate_charge()
242
218
  request_data = self.model_dump()
243
- logger.info(f"Request Data: {request_data}")
219
+ logger.debug(f"Request Data: {request_data}")
244
220
  try:
221
+ assert hasattr(session, "post"), "session must have post method."
245
222
  response = await session.post(
246
223
  self.base_url,
247
224
  data=json.dumps(request_data).encode("utf-8")
248
225
  )
249
- if response.headers.get('Content-Type') in ['binary/octet-stream', 'application/x-zip-compressed']:
250
- zip_file = ZipFile(BytesIO(response.content))
251
- with zip_file as zf:
252
- file_list = zf.namelist()
253
- if not file_list:
254
- raise ServerError(msg="Nai Returned zip file is empty.")
255
- with zf.open(file_list[0]) as file:
256
- _img = BytesIO(file.read())
257
- else:
258
- logger.error(f"response: {response.text}")
259
- # 解析错误内容
226
+ if response.headers.get('Content-Type') not in ['binary/octet-stream', 'application/x-zip-compressed']:
260
227
  try:
261
228
  _msg = response.json()
262
- message = _msg["message"]
263
229
  except Exception:
264
- raise ServerError(msg=f"Unexpected content type: {response.headers.get('Content-Type')}")
265
- code = _msg.get("statusCode", 200)
266
- if message == "Error generating image":
267
- raise ServerError(msg=f"[Nai Server] Generate image error: {code}")
268
- raise AuthError(msg=f"[Nai Server]{message}")
269
- try:
270
- _img_exif = NaiPic.read_from_img(_img)
271
- except Exception as e:
272
- logger.debug(e)
273
- _img_exif = NaiPic.read_from_param(prompt=self.input,
274
- neg_prompt=self.parameters.negative_prompt,
275
- seed=self.parameters.seed,
276
- steps=self.parameters.steps,
277
- cfg_rescale=self.parameters.cfg_rescale,
278
- sampler=self.parameters.sampler,
279
- )
280
- _img = _img_exif.generate_to_img_io(img_io=_img)
281
- _img.seek(0)
282
- return NaiResult(
283
- meta=NaiResult.RequestParams(
230
+ raise APIError(
231
+ message=f"Unexpected content type: {response.headers.get('Content-Type')}",
232
+ request=request_data,
233
+ status_code=response.status_code,
234
+ response=try_jsonfy(response.content)
235
+ )
236
+ status_code = _msg.get("statusCode", response.status_code)
237
+ message = _msg.get("message", "Unknown error")
238
+ if status_code in [400, 401, 402]:
239
+ # 400 : validation error
240
+ # 401 : unauthorized
241
+ # 402 : payment required
242
+ # 409 : conflict
243
+ raise AuthError(message, request=request_data, status_code=status_code, response=message)
244
+ if status_code in [409]:
245
+ # conflict error
246
+ raise APIError(message, request=request_data, status_code=status_code, response=message)
247
+ raise APIError(message, request=request_data, status_code=status_code, response=message)
248
+ zip_file = ZipFile(BytesIO(response.content))
249
+ unzip_content = []
250
+ with zip_file as zf:
251
+ file_list = zf.namelist()
252
+ if not file_list:
253
+ raise APIError(
254
+ message="No file in zip",
255
+ request=request_data,
256
+ status_code=response.status_code,
257
+ response=try_jsonfy(response.content)
258
+ )
259
+ for filename in file_list:
260
+ unzip_content.append((filename, zip_file.read(filename)))
261
+ return ImageGenerateResp(
262
+ meta=ImageGenerateResp.RequestParams(
284
263
  endpoint=self.base_url,
285
264
  raw_request=request_data,
286
265
  ),
287
- files=(f"{str(self.input[:7])}-s{shortuuid.uuid()[:5]}.png", _img.getvalue())
266
+ files=unzip_content
288
267
  )
289
268
  except httpx.HTTPError as exc:
290
269
  raise RuntimeError(f"An HTTP error occurred: {exc}")
291
- except ServerError as e:
270
+ except APIError as e:
292
271
  raise e
293
272
  except Exception as e:
294
- raise RuntimeError(f"An error occurred: {e}")
273
+ logger.opt(exception=e).exception("An Unexpected error occurred")
274
+ raise e
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/1/26 上午10:51
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
6
+ import io
7
+ import json
8
+ from typing import Union
9
+
10
+ from .hash import NovelAiMetadata
11
+
12
+
13
+ def png_info_reset(img: Union[str, io.BytesIO]):
14
+ """
15
+ reset png info hash value
16
+ :param img: BytesIO 对象
17
+ :return:
18
+ """
19
+ _fixed = NovelAiMetadata.rehash(img_io=img)
20
+ return _fixed
21
+
22
+
23
+ def try_jsonfy(obj: Union[str, dict, list, tuple]):
24
+ """
25
+ try to jsonfy a object
26
+ :param obj:
27
+ :return:
28
+ """
29
+ try:
30
+ return json.loads(obj)
31
+ except json.JSONDecoder:
32
+ return f"Decode Error {type(obj)}"
@@ -23,28 +23,35 @@ def sign_message(message, key):
23
23
  return signed_hash
24
24
 
25
25
 
26
- class NaiPic(BaseModel):
26
+ class NovelAiMetadata(BaseModel):
27
27
  title: str = "AI generated image"
28
28
  description: str
29
29
  comment: Union[dict, str] = {}
30
30
 
31
- def generate_exif(self):
32
- self.comment["signed_hash"] = sign_message(json.dumps(self.description), "novalai-bot")
33
- self.comment = json.dumps(self.comment)
31
+ @property
32
+ def metadata(self):
33
+ if isinstance(self.comment, dict):
34
+ _comment = json.dumps(self.comment)
34
35
  return {"Title": self.title, "Description": self.description, "Comment": self.comment}
35
36
 
36
- def generate_to_img_io(self, img_io):
37
+ @staticmethod
38
+ def rehash(img_io):
39
+ cls = NaiPic.read_from_img(img_io)
40
+ cls.comment["signed_hash"] = sign_message(json.dumps(cls.description), "novalai-client")
41
+ cls.write_out(img_io=img_io)
42
+ return img_io
43
+
44
+ def write_out(self, img_io: BytesIO):
37
45
  with Image.open(img_io) as img:
38
46
  metadata = PngInfo()
39
- _info = self.generate_exif()
40
- for k, v in _info.items():
47
+ for k, v in self.metadata.items():
41
48
  metadata.add_text(k, v)
42
49
  _new_img = BytesIO()
43
50
  img.save(_new_img, format="PNG", pnginfo=metadata, quality=95, optimize=False)
44
51
  return _new_img
45
52
 
46
53
  @classmethod
47
- def read_from_img(cls, image_io):
54
+ def build_from_img(cls, image_io):
48
55
  with Image.open(image_io) as img:
49
56
  title = img.info.get("Title")
50
57
  if not title == 'AI generated image':
@@ -59,7 +66,7 @@ class NaiPic(BaseModel):
59
66
  return cls(title=title, description=prompt, comment=comment)
60
67
 
61
68
  @classmethod
62
- def read_from_param(cls, prompt, neg_prompt, **kwargs):
69
+ def build_from_param(cls, prompt, neg_prompt, **kwargs):
63
70
  _comment = {"uc": neg_prompt}
64
71
  _comment.update(kwargs)
65
72
  return cls(title="AI generated image", description=prompt, comment=_comment)
@@ -1,10 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: novelai-python
3
- Version: 0.1.0
4
- Summary: Novelai Python Binding With Pydantic
5
- Author-Email: sudoskys <coldlando@hotmail.com>
6
- License: Apache 2.0
7
- Requires-Python: >=3.8
8
- Description-Content-Type: text/markdown
9
-
10
- # novelai-python
@@ -1 +0,0 @@
1
- # novelai-python
@@ -1,22 +0,0 @@
1
- [project]
2
- name = "novelai-python"
3
- version = "0.1.0"
4
- description = "Novelai Python Binding With Pydantic"
5
- authors = [
6
- { name = "sudoskys", email = "coldlando@hotmail.com" },
7
- ]
8
- dependencies = []
9
- requires-python = ">=3.8"
10
- readme = "README.md"
11
-
12
- [project.license]
13
- text = "Apache 2.0"
14
-
15
- [build-system]
16
- requires = [
17
- "pdm-backend",
18
- ]
19
- build-backend = "pdm.backend"
20
-
21
- [tool.pdm]
22
- distribution = true
@@ -1,29 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # @Time : 2023/11/18 上午12:58
3
- # @Author : sudoskys
4
- # @File : error.py
5
- # @Software: PyCharm
6
-
7
-
8
- class ServerError(Exception):
9
- def __init__(self, msg: str = None):
10
- self.msg = msg
11
-
12
- def __str__(self):
13
- return f"ServerError: {self.msg}"
14
-
15
-
16
- class ValidationError(Exception):
17
- def __init__(self, msg: str = None):
18
- self.msg = msg
19
-
20
- def __str__(self):
21
- return f"ValidationError: {self.msg}"
22
-
23
-
24
- class AuthError(Exception):
25
- def __init__(self, msg: str = None):
26
- self.msg = msg
27
-
28
- def __str__(self):
29
- return f"AuthError: {self.msg}"
@@ -1,24 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # @Time : 2023/11/18 上午1:10
3
- # @Author : sudoskys
4
- # @File : schema.py
5
- # @Software: PyCharm
6
- from typing import Tuple
7
-
8
- from pydantic import BaseModel
9
-
10
-
11
- class NaiResult(BaseModel):
12
- class RequestParams(BaseModel):
13
- endpoint: str
14
- raw_request: dict = None
15
-
16
- meta: RequestParams
17
- files: Tuple[str, bytes] = None
18
-
19
- def query_params(self, key: str, default=None):
20
- if isinstance(self.meta.raw_request, str):
21
- return default
22
- if isinstance(self.meta.raw_request.get("parameters"), dict):
23
- return self.meta.raw_request.get("parameters").get(key, default)
24
- return default