novelai-python 0.4.11__tar.gz → 0.4.12__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.
Files changed (87) hide show
  1. {novelai_python-0.4.11 → novelai_python-0.4.12}/PKG-INFO +1 -1
  2. {novelai_python-0.4.11 → novelai_python-0.4.12}/pyproject.toml +1 -1
  3. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/__init__.py +3 -0
  4. novelai_python-0.4.12/src/novelai_python/credential/ApiToken.py +41 -0
  5. novelai_python-0.4.12/src/novelai_python/credential/JwtToken.py +40 -0
  6. novelai_python-0.4.12/src/novelai_python/credential/UserAuth.py +47 -0
  7. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/__init__.py +1 -0
  8. novelai_python-0.4.12/src/novelai_python/sdk/ai/augment_image/__init__.py +248 -0
  9. novelai_python-0.4.12/src/novelai_python/sdk/ai/augment_image/_enum.py +43 -0
  10. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate/__init__.py +70 -73
  11. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate_image/__init__.py +89 -92
  12. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate_image/suggest_tags.py +1 -1
  13. novelai_python-0.4.12/src/novelai_python/sdk/ai/generate_stream.py +170 -0
  14. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate_voice/__init__.py +52 -55
  15. novelai_python-0.4.12/src/novelai_python/sdk/ai/upscale.py +198 -0
  16. novelai_python-0.4.12/src/novelai_python/sdk/user/information.py +112 -0
  17. novelai_python-0.4.12/src/novelai_python/sdk/user/login.py +122 -0
  18. novelai_python-0.4.12/src/novelai_python/sdk/user/subscription.py +120 -0
  19. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/image_metadata/__init__.py +12 -5
  20. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_generate_voice.py +11 -4
  21. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_upscale.py +14 -4
  22. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_user_information.py +15 -4
  23. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_user_login.py +28 -12
  24. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_user_subscription.py +15 -3
  25. novelai_python-0.4.11/src/novelai_python/credential/ApiToken.py +0 -41
  26. novelai_python-0.4.11/src/novelai_python/credential/JwtToken.py +0 -42
  27. novelai_python-0.4.11/src/novelai_python/credential/UserAuth.py +0 -43
  28. novelai_python-0.4.11/src/novelai_python/sdk/ai/generate_stream.py +0 -173
  29. novelai_python-0.4.11/src/novelai_python/sdk/ai/upscale.py +0 -201
  30. novelai_python-0.4.11/src/novelai_python/sdk/user/information.py +0 -115
  31. novelai_python-0.4.11/src/novelai_python/sdk/user/login.py +0 -132
  32. novelai_python-0.4.11/src/novelai_python/sdk/user/subscription.py +0 -122
  33. {novelai_python-0.4.11 → novelai_python-0.4.12}/LICENSE +0 -0
  34. {novelai_python-0.4.11 → novelai_python-0.4.12}/NOTICE.MD +0 -0
  35. {novelai_python-0.4.11 → novelai_python-0.4.12}/README.md +0 -0
  36. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_exceptions.py +0 -0
  37. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/__init__.py +0 -0
  38. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/ai/__init__.py +0 -0
  39. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/ai/generate.py +0 -0
  40. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/ai/generate_image.py +0 -0
  41. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/ai/generate_stream.py +0 -0
  42. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/ai/generate_voice.py +0 -0
  43. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/ai/upscale.py +0 -0
  44. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/schema.py +0 -0
  45. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/user/__init__.py +0 -0
  46. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/user/information.py +0 -0
  47. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/user/login.py +0 -0
  48. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/_response/user/subscription.py +0 -0
  49. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/credential/__init__.py +0 -0
  50. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/credential/_base.py +0 -0
  51. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/LICENSE +0 -0
  52. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/__init__.py +0 -0
  53. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate/_const.py +0 -0
  54. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate/_enum.py +0 -0
  55. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate/_preset.py +0 -0
  56. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate_image/_const.py +0 -0
  57. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate_image/_enum.py +0 -0
  58. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/ai/generate_voice/_enum.py +0 -0
  59. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/schema.py +0 -0
  60. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/sdk/user/__init__.py +0 -0
  61. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/server.py +0 -0
  62. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/NOTICE.MD +0 -0
  63. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/__init__.py +0 -0
  64. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/bpe_simple_vocab_16e6.txt.gz +0 -0
  65. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/gpt2-genji_tokenizer.json +0 -0
  66. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/gpt2_tokenizer.json +0 -0
  67. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/nerdstash_v1.model +0 -0
  68. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/nerdstash_v2.model +0 -0
  69. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/novelai.model +0 -0
  70. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/novelai_v2.model +0 -0
  71. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/pile_tokenizer.json +0 -0
  72. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tokenizer/simple_tokenizer.py +0 -0
  73. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/__init__.py +0 -0
  74. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/image_metadata/lsb_extractor.py +0 -0
  75. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/image_metadata/lsb_injector.py +0 -0
  76. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/paint_mask/__init__.py +0 -0
  77. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/random_prompt/__init__.py +0 -0
  78. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/random_prompt/tag.py +0 -0
  79. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/random_prompt/tag_character.py +0 -0
  80. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/tool/random_prompt/tag_nsfw.py +0 -0
  81. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/utils/__init__.py +0 -0
  82. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/utils/encode.py +0 -0
  83. {novelai_python-0.4.11 → novelai_python-0.4.12}/src/novelai_python/utils/useful.py +0 -0
  84. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/__init__.py +0 -0
  85. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_random_prompt.py +0 -0
  86. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_server.py +0 -0
  87. {novelai_python-0.4.11 → novelai_python-0.4.12}/tests/test_server_run.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: novelai-python
3
- Version: 0.4.11
3
+ Version: 0.4.12
4
4
  Summary: NovelAI Python Binding With Pydantic
5
5
  Author-Email: sudoskys <coldlando@hotmail.com>
6
6
  License: Apache License 2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "novelai-python"
3
- version = "0.4.11"
3
+ version = "0.4.12"
4
4
  description = "NovelAI Python Binding With Pydantic"
5
5
  authors = [
6
6
  { name = "sudoskys", email = "coldlando@hotmail.com" },
@@ -11,6 +11,7 @@ from ._exceptions import (
11
11
  SessionHttpError
12
12
  )
13
13
  from .credential import JwtCredential, LoginCredential, ApiCredential
14
+ from .sdk import AugmentImageInfer
14
15
  from .sdk import GenerateImageInfer, ImageGenerateResp
15
16
  from .sdk import Information, InformationResp
16
17
  from .sdk import LLM, LLMResp
@@ -31,6 +32,8 @@ __all__ = [
31
32
  "GenerateImageInfer",
32
33
  "ImageGenerateResp",
33
34
 
35
+ "AugmentImageInfer",
36
+
34
37
  "VoiceGenerate",
35
38
  "VoiceResponse",
36
39
 
@@ -0,0 +1,41 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/2/8 下午3:05
3
+ # @Author : sudoskys
4
+ # @File : ApiToken.py
5
+ # @Software: PyCharm
6
+
7
+ from curl_cffi.requests import AsyncSession
8
+ from loguru import logger
9
+ from pydantic import SecretStr, Field, field_validator
10
+
11
+ from ._base import CredentialBase, FAKE_UA
12
+
13
+
14
+ class ApiCredential(CredentialBase):
15
+ """
16
+ ApiCredential is the base class for all credential.
17
+ """
18
+ api_token: SecretStr = Field(None, description="api token")
19
+
20
+ async def get_session(self, timeout: int = 180, update_headers: dict = None):
21
+ headers = {
22
+ "Accept": "*/*",
23
+ "Accept-Encoding": "gzip, deflate, br",
24
+ "User-Agent": FAKE_UA.edge,
25
+ "Authorization": f"Bearer {self.api_token.get_secret_value()}",
26
+ "Content-Type": "application/json",
27
+ "Origin": "https://novelai.net",
28
+ "Referer": "https://novelai.net/",
29
+ }
30
+
31
+ if update_headers:
32
+ assert isinstance(update_headers, dict), "update_headers must be a dict"
33
+ headers.update(update_headers)
34
+
35
+ return AsyncSession(timeout=timeout, headers=headers, impersonate="edge101")
36
+
37
+ @field_validator('api_token')
38
+ def check_api_token(cls, v: SecretStr):
39
+ if not v.get_secret_value().startswith("pst"):
40
+ logger.warning("api token should start with pst-")
41
+ return v
@@ -0,0 +1,40 @@
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 loguru import logger
8
+ from pydantic import SecretStr, Field, field_validator
9
+
10
+ from ._base import CredentialBase, FAKE_UA
11
+
12
+
13
+ class JwtCredential(CredentialBase):
14
+ """
15
+ JwtCredential is the base class for all credential.
16
+ """
17
+ jwt_token: SecretStr = Field(None, description="jwt token")
18
+
19
+ async def get_session(self, timeout: int = 180, update_headers: dict = None):
20
+ headers = {
21
+ "Accept": "*/*",
22
+ "User-Agent": FAKE_UA.edge,
23
+ "Accept-Encoding": "gzip, deflate, br",
24
+ "Authorization": f"Bearer {self.jwt_token.get_secret_value()}",
25
+ "Content-Type": "application/json",
26
+ "Origin": "https://novelai.net",
27
+ "Referer": "https://novelai.net/",
28
+ }
29
+
30
+ if update_headers:
31
+ assert isinstance(update_headers, dict), "update_headers must be a dict"
32
+ headers.update(update_headers)
33
+
34
+ return AsyncSession(timeout=timeout, headers=headers, impersonate="edge101")
35
+
36
+ @field_validator('jwt_token')
37
+ def check_jwt_token(cls, v: SecretStr):
38
+ if not v.get_secret_value().startswith("ey"):
39
+ logger.warning("jwt_token should start with ey")
40
+ return v
@@ -0,0 +1,47 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/2/7 下午12:14
3
+ # @Author : sudoskys
4
+ # @File : UserAuth.py
5
+ import time
6
+ from typing import Optional
7
+
8
+ from curl_cffi.requests import AsyncSession
9
+ from pydantic import SecretStr, Field
10
+
11
+ from ._base import CredentialBase, FAKE_UA
12
+
13
+
14
+ class LoginCredential(CredentialBase):
15
+ """
16
+ JwtCredential is the base class for all credential.
17
+ """
18
+ username: str = Field(None, description="username")
19
+ password: SecretStr = Field(None, description="password")
20
+ _session_headers: dict = {}
21
+ _update_at: Optional[int] = None
22
+
23
+ async def get_session(self, timeout: int = 180, update_headers: dict = None):
24
+ headers = {
25
+ "Accept": "*/*",
26
+ "User-Agent": FAKE_UA.edge,
27
+ "Accept-Encoding": "gzip, deflate, br",
28
+ "Authorization": "Bearer ",
29
+ "Content-Type": "application/json",
30
+ "Origin": "https://novelai.net",
31
+ "Referer": "https://novelai.net/",
32
+ }
33
+
34
+ # 30 天有效期
35
+ if not self._session_headers or int(time.time()) - self._update_at > 29 * 24 * 60 * 60:
36
+ from ..sdk import Login
37
+ resp = await Login.build(user_name=self.username, password=self.password.get_secret_value()).request()
38
+ headers["Authorization"] = f"Bearer {resp.accessToken}"
39
+ self._session_headers = headers
40
+ self._update_at = int(time.time())
41
+ else:
42
+ headers.update(self._session_headers)
43
+
44
+ if update_headers:
45
+ headers.update(update_headers)
46
+
47
+ return AsyncSession(timeout=timeout, headers=headers, impersonate="edge101")
@@ -4,6 +4,7 @@
4
4
  # @File : __init__.py.py
5
5
  # @Software: PyCharm
6
6
 
7
+ from .ai.augment_image import AugmentImageInfer # noqa 401
7
8
  from .ai.generate import LLM, LLMResp # noqa 401
8
9
  from .ai.generate_image import GenerateImageInfer, ImageGenerateResp # noqa 401
9
10
  from .ai.generate_image.suggest_tags import SuggestTags, SuggestTagsResp # noqa 401
@@ -0,0 +1,248 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/2/13 下午8:08
3
+ # @Author : sudoskys
4
+ # @File : __init__.py.py
5
+ # @Software: PyCharm
6
+ import base64
7
+ import io
8
+ import json
9
+ import pathlib
10
+ from copy import deepcopy
11
+ from io import BytesIO
12
+ from typing import Optional, Union, IO
13
+ from urllib.parse import urlparse
14
+ from zipfile import ZipFile
15
+
16
+ import curl_cffi
17
+ import httpx
18
+ from PIL import Image
19
+ from curl_cffi.requests import AsyncSession
20
+ from loguru import logger
21
+ from pydantic import ConfigDict, PrivateAttr, model_validator, Field
22
+ from tenacity import retry, stop_after_attempt, wait_random, retry_if_exception
23
+
24
+ from ._enum import ReqType, Moods
25
+ from ...schema import ApiBaseModel
26
+ from ...._exceptions import APIError, AuthError, ConcurrentGenerationError, SessionHttpError
27
+ from ...._response.ai.generate_image import ImageGenerateResp, RequestParams
28
+ from ....credential import CredentialBase
29
+ from ....utils import try_jsonfy
30
+
31
+
32
+ class AugmentImageInfer(ApiBaseModel):
33
+ _endpoint: str = PrivateAttr("https://image.novelai.net")
34
+
35
+ @property
36
+ def endpoint(self):
37
+ return self._endpoint
38
+
39
+ @endpoint.setter
40
+ def endpoint(self, value):
41
+ self._endpoint = value
42
+
43
+ req_type: ReqType = Field(..., description="Type of augmentation")
44
+ width: int = Field(..., description="Width of the image")
45
+ height: int = Field(..., description="Height of the image")
46
+ image: Union[str, bytes] = Field(..., description="Base64 encoded image")
47
+ prompt: Optional[str] = None
48
+ defry: Optional[int] = Field(0, ge=0, le=5, multiple_of=1)
49
+ model_config = ConfigDict(extra="ignore")
50
+
51
+ @model_validator(mode="after")
52
+ def image_validator(self):
53
+ if isinstance(self.image, bytes):
54
+ self.image = base64.b64encode(self.image).decode("utf-8")
55
+ if isinstance(self.image, str) and self.image.startswith("data:"):
56
+ raise ValueError("Invalid `image` format, must be base64 encoded directly.")
57
+ if isinstance(self.image, str) and self.image.startswith("+vv"):
58
+ raise ValueError("Invalid `image` format, must be encoded correctly.")
59
+ return self
60
+
61
+ @property
62
+ def base_url(self):
63
+ return f"{self.endpoint.strip('/')}/ai/augment-image"
64
+
65
+ def calculate_cost(self, is_opus: bool = False) -> float:
66
+ raise NotImplementedError("This method is not implemented yet")
67
+
68
+ @staticmethod
69
+ def _to_bytes_io(image: Union[bytes, IO, pathlib.Path]) -> io.BytesIO:
70
+ if isinstance(image, bytes):
71
+ return io.BytesIO(image)
72
+ elif isinstance(image, IO):
73
+ return image
74
+ elif isinstance(image, pathlib.Path):
75
+ with open(image, 'rb') as f:
76
+ return io.BytesIO(f.read())
77
+ else:
78
+ raise ValueError("Invalid image input type. Expected bytes, IO, or pathlib.Path.")
79
+
80
+ @classmethod
81
+ def build(cls,
82
+ image: Union[bytes, IO, pathlib.Path],
83
+ req_type: ReqType = ReqType.SKETCH,
84
+ *,
85
+ defry: Optional[int] = None,
86
+ prompt: Optional[str] = None,
87
+ mood: Optional[Moods] = None,
88
+ ):
89
+ """
90
+ Build the request data
91
+ :param req_type: Request type
92
+ :param image: Image data
93
+ :param defry: Defry level
94
+ :param prompt: Prompt
95
+ :param mood: Mood
96
+ :raises ValueError: When image is not valid
97
+ :return: AugmentImageInfer
98
+ """
99
+ try:
100
+ image_io = cls._to_bytes_io(image)
101
+ with Image.open(image_io) as img:
102
+ width, height = img.size
103
+ image_io.seek(0)
104
+ image_data = image_io.read()
105
+ except Exception as e:
106
+ raise ValueError("Error when opening image") from e
107
+
108
+ return cls(
109
+ req_type=req_type,
110
+ defry=defry,
111
+ image=image_data,
112
+ width=width,
113
+ height=height,
114
+ prompt=f"{mood.value};;{prompt}" if mood else prompt,
115
+ )
116
+
117
+ async def necessary_headers(self, request_data) -> dict:
118
+ """
119
+ :param request_data:
120
+ :return:
121
+ """
122
+ return {
123
+ "Host": urlparse(self.endpoint).netloc,
124
+ "Accept": "*/*",
125
+ "Accept-Encoding": "gzip, deflate, br",
126
+ "Referer": "https://novelai.net/",
127
+ "Content-Type": "application/json",
128
+ "Origin": "https://novelai.net",
129
+ "Content-Length": str(len(json.dumps(request_data).encode("utf-8"))),
130
+ "Connection": "keep-alive",
131
+ "Sec-Fetch-Dest": "empty",
132
+ "Sec-Fetch-Mode": "cors",
133
+ "Sec-Fetch-Site": "same-site",
134
+ "Pragma": "no-cache",
135
+ "Cache-Control": "no-cache",
136
+ 'priority': "u=1, i"
137
+ }
138
+
139
+ @retry(
140
+ wait=wait_random(min=1, max=3),
141
+ stop=stop_after_attempt(3),
142
+ retry=retry_if_exception(lambda e: hasattr(e, "code") and str(e.code) == "500"),
143
+ reraise=True
144
+ )
145
+ async def request(self,
146
+ session: Union[AsyncSession, "CredentialBase"],
147
+ *,
148
+ override_headers: Optional[dict] = None,
149
+ ) -> ImageGenerateResp:
150
+ """
151
+ 生成图片
152
+ :param override_headers: the headers to override
153
+ :param session: session
154
+ :return:
155
+ """
156
+ # Data Build
157
+ request_data = self.model_dump(mode="json", exclude_none=True)
158
+ async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
159
+ # Header
160
+ sess.headers.update(await self.necessary_headers(request_data))
161
+ if override_headers:
162
+ sess.headers.clear()
163
+ sess.headers.update(override_headers)
164
+ try:
165
+ _log_data = deepcopy(request_data)
166
+ _log_data.update({
167
+ "image": "base64 data"
168
+ })
169
+ logger.debug(f"Request Data: {_log_data}")
170
+ del _log_data
171
+ except Exception as e:
172
+ logger.warning(f"Error when print log data: {e}")
173
+ try:
174
+ assert hasattr(sess, "post"), "session must have post method."
175
+ response = await sess.post(
176
+ self.base_url,
177
+ data=json.dumps(request_data).encode("utf-8")
178
+ )
179
+ if response.headers.get('Content-Type') not in ['binary/octet-stream', 'application/x-zip-compressed']:
180
+ logger.warning(
181
+ f"Error with content type: {response.headers.get('Content-Type')} and code: {response.status_code}"
182
+ )
183
+ try:
184
+ _msg = response.json()
185
+ except Exception as e:
186
+ logger.warning(e)
187
+ if not isinstance(response.content, str) and len(response.content) < 50:
188
+ raise APIError(
189
+ message=f"Unexpected content type: {response.headers.get('Content-Type')}",
190
+ request=request_data,
191
+ code=response.status_code,
192
+ response=try_jsonfy(response.content)
193
+ )
194
+ else:
195
+ _msg = {"statusCode": response.status_code, "message": response.content}
196
+ status_code = _msg.get("statusCode", response.status_code)
197
+ message = _msg.get("message", "Unknown error")
198
+ if status_code in [400, 401, 402]:
199
+ # 400 : validation error
200
+ # 401 : unauthorized
201
+ # 402 : payment required
202
+ # 409 : conflict
203
+ raise AuthError(message, request=request_data, code=status_code, response=_msg)
204
+ if status_code in [409]:
205
+ # conflict error
206
+ raise APIError(message, request=request_data, code=status_code, response=_msg)
207
+ if status_code in [429]:
208
+ # concurrent error
209
+ raise ConcurrentGenerationError(
210
+ message=message,
211
+ request=request_data,
212
+ code=status_code,
213
+ response=_msg
214
+ )
215
+ raise APIError(message, request=request_data, code=status_code, response=_msg)
216
+ zip_file = ZipFile(BytesIO(response.content))
217
+ unzip_content = []
218
+ with zip_file as zf:
219
+ file_list = zf.namelist()
220
+ if not file_list:
221
+ raise APIError(
222
+ message="No file in zip",
223
+ request=request_data,
224
+ code=response.status_code,
225
+ response=try_jsonfy(response.content)
226
+ )
227
+ for filename in file_list:
228
+ data = zip_file.read(filename)
229
+ unzip_content.append((filename, data))
230
+ return ImageGenerateResp(
231
+ meta=RequestParams(
232
+ endpoint=self.base_url,
233
+ raw_request=request_data,
234
+ ),
235
+ files=unzip_content
236
+ )
237
+ except curl_cffi.requests.errors.RequestsError as exc:
238
+ logger.exception(exc)
239
+ raise SessionHttpError(
240
+ "An AsyncSession RequestsError occurred, maybe SSL error. Try again later!") from exc
241
+ except httpx.HTTPError as exc:
242
+ logger.exception(exc)
243
+ raise SessionHttpError("An HTTPError occurred, maybe SSL error. Try again later!") from exc
244
+ except APIError as e:
245
+ raise e
246
+ except Exception as e:
247
+ logger.opt(exception=e).exception("An Unexpected error occurred")
248
+ raise e
@@ -0,0 +1,43 @@
1
+ from enum import Enum
2
+
3
+
4
+ class ReqType(Enum):
5
+ """
6
+ typing.Literal["bg-removal", "colorize", "lineart", "sketch", "emotion", "declutter"]
7
+ """
8
+ BG_REMOVAL = "bg-removal"
9
+ COLORIZE = "colorize"
10
+ LINEART = "lineart"
11
+ SKETCH = "sketch"
12
+ EMOTION = "emotion"
13
+ DECLUTTER = "declutter"
14
+
15
+
16
+ class Moods(Enum):
17
+ """
18
+ The mood of the character in the image
19
+ """
20
+ Neutral = "neutral"
21
+ Happy = "happy"
22
+ Saf = "sad"
23
+ Angry = "angry"
24
+ Scared = "scared"
25
+ Surprised = "surprised"
26
+ Tired = "tired"
27
+ Excited = "excited"
28
+ Nervous = "nervous"
29
+ Thinking = "thinking"
30
+ Confused = "confused"
31
+ Shy = "shy"
32
+ Disgusted = "disgusted"
33
+ Smug = "smug"
34
+ Bored = "bored"
35
+ Laughing = "laughing"
36
+ Irritated = "irritated"
37
+ Aroused = "aroused"
38
+ Embarrassed = "embarrassed"
39
+ Worried = "worried"
40
+ Love = "love"
41
+ Determined = "determined"
42
+ Hurt = "hurt"
43
+ Playful = "playful"
@@ -156,80 +156,77 @@ class LLM(ApiBaseModel):
156
156
  request_data = self.model_dump(mode="json", exclude_none=True)
157
157
  request_data["order"] = generate_order(request_data) or []
158
158
  assert request_data.get("parameters"), "Parameters is required"
159
- # Header
160
- if isinstance(session, AsyncSession):
161
- session.headers.update(await self.necessary_headers(request_data))
162
- elif isinstance(session, CredentialBase):
163
- update_header = await self.necessary_headers(request_data)
164
- session = await session.get_session(update_headers=update_header)
165
- if override_headers:
166
- session.headers.clear()
167
- session.headers.update(override_headers)
168
- # Log
169
- logger.debug(f"LLM request data: {request_data}")
170
- # Request
171
- try:
172
- assert hasattr(session, "post"), "session must have post method."
173
- response = await session.post(
174
- self.base_url,
175
- json=request_data,
176
- stream=False
177
- )
178
- header_type = response.headers.get('Content-Type')
179
- if "application/json" not in header_type:
180
- try:
181
- _msg = response.json()
182
- except Exception as e:
183
- _msg = {"statusCode": response.status_code, "message": response.content}
184
- status_code = _msg.get("statusCode", response.status_code)
185
- message = _msg.get("message", "Unknown error")
186
- if status_code == 400:
187
- raise APIError(
188
- f"A validation error occured. {message}",
189
- request=request_data, code=status_code, response=_msg
190
- )
159
+ async with session if isinstance(session, AsyncSession) else await session.get_session() as sess:
160
+ # Header
161
+ sess.headers.update(await self.necessary_headers(request_data))
162
+ if override_headers:
163
+ sess.headers.clear()
164
+ sess.headers.update(override_headers)
165
+ # Log
166
+ logger.debug(f"LLM request data: {request_data}")
167
+ # Request
168
+ try:
169
+ assert hasattr(sess, "post"), "session must have post method."
170
+ response = await sess.post(
171
+ self.base_url,
172
+ json=request_data,
173
+ stream=False
174
+ )
175
+ header_type = response.headers.get('Content-Type')
176
+ if "application/json" not in header_type:
177
+ try:
178
+ _msg = response.json()
179
+ except Exception as e:
180
+ _msg = {"statusCode": response.status_code, "message": response.content}
181
+ status_code = _msg.get("statusCode", response.status_code)
182
+ message = _msg.get("message", "Unknown error")
183
+ if status_code == 400:
184
+ raise APIError(
185
+ f"A validation error occured. {message}",
186
+ request=request_data, code=status_code, response=_msg
187
+ )
191
188
 
192
- elif status_code == 401:
193
- raise APIError(
194
- f"Access Token is incorrect. {message}",
195
- request=request_data, code=status_code, response=_msg
196
- )
197
- elif status_code == 402:
198
- raise APIError(
199
- f"An active subscription is required to access this endpoint. {message}",
200
- request=request_data, code=status_code, response=_msg
201
- )
202
- elif status_code == 409:
203
- raise APIError(
204
- f"A conflict error occured. {message}",
205
- request=request_data, code=status_code, response=_msg
206
- )
189
+ elif status_code == 401:
190
+ raise APIError(
191
+ f"Access Token is incorrect. {message}",
192
+ request=request_data, code=status_code, response=_msg
193
+ )
194
+ elif status_code == 402:
195
+ raise APIError(
196
+ f"An active subscription is required to access this endpoint. {message}",
197
+ request=request_data, code=status_code, response=_msg
198
+ )
199
+ elif status_code == 409:
200
+ raise APIError(
201
+ f"A conflict error occured. {message}",
202
+ request=request_data, code=status_code, response=_msg
203
+ )
204
+ else:
205
+ raise APIError(
206
+ f"An unknown error occured. {response.status_code} {message}",
207
+ request=request_data, code=status_code, response=_msg
208
+ )
207
209
  else:
208
- raise APIError(
209
- f"An unknown error occured. {response.status_code} {message}",
210
- request=request_data, code=status_code, response=_msg
210
+ output = response.json().get("output", None)
211
+ assert output, APIError("No Content Returned",
212
+ request=request_data, code=response.status_code, response=response.content
213
+ )
214
+ return LLMResp(
215
+ output=output,
216
+ text=LLMResp.decode_token(model=self.model, token_str=output)
211
217
  )
212
- else:
213
- output = response.json().get("output", None)
214
- assert output, APIError("No Content Returned",
215
- request=request_data, code=response.status_code, response=response.content
216
- )
217
- return LLMResp(
218
- output=output,
219
- text=LLMResp.decode_token(model=self.model, token_str=output)
218
+ except curl_cffi.requests.errors.RequestsError as exc:
219
+ logger.exception(exc)
220
+ raise SessionHttpError(
221
+ "An AsyncSession RequestsError occurred, perhaps it's an SSL error. Please try again later!"
222
+ )
223
+ except httpx.HTTPError as exc:
224
+ logger.exception(exc)
225
+ raise SessionHttpError(
226
+ "An HTTPError occurred, perhaps it's an SSL error. Please try again later!"
220
227
  )
221
- except curl_cffi.requests.errors.RequestsError as exc:
222
- logger.exception(exc)
223
- raise SessionHttpError(
224
- "An AsyncSession RequestsError occurred, perhaps it's an SSL error. Please try again later!"
225
- )
226
- except httpx.HTTPError as exc:
227
- logger.exception(exc)
228
- raise SessionHttpError(
229
- "An HTTPError occurred, perhaps it's an SSL error. Please try again later!"
230
- )
231
- except APIError as e:
232
- raise e
233
- except Exception as e:
234
- logger.opt(exception=e).exception("An unexpected error occurred")
235
- raise e
228
+ except APIError as e:
229
+ raise e
230
+ except Exception as e:
231
+ logger.opt(exception=e).exception("An unexpected error occurred")
232
+ raise e