aioe621 0.2.2__tar.gz → 0.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: aioe621
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: A simple asynchronous httpx+pydantic wrapper over the E621's API
5
5
  Keywords: async,httpx,e621
6
6
  Author: penggrin12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aioe621"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "A simple asynchronous httpx+pydantic wrapper over the E621's API"
5
5
  license = { file = "LICENSE" }
6
6
  readme = "README.md"
@@ -1,7 +1,8 @@
1
1
  import typing
2
+ from typing import overload
2
3
 
3
4
  import httpx
4
- from pydantic import BaseModel
5
+ from pydantic import BaseModel, TypeAdapter
5
6
 
6
7
  from aioe621 import endpoints
7
8
  from aioe621.exceptions import (
@@ -78,18 +79,31 @@ class Client:
78
79
  raise self._find_error(response)
79
80
 
80
81
  _MT = typing.TypeVar("_MT", bound="BaseModel")
82
+ _T = typing.TypeVar("_T")
83
+
84
+ @overload
85
+ async def _request_model(
86
+ self, model: TypeAdapter[_T], method: str, url: str, **kwargs
87
+ ) -> _T: ...
88
+
89
+ @overload
90
+ async def _request_model(
91
+ self, model: type[_MT], method: str, url: str, **kwargs
92
+ ) -> _MT: ...
81
93
 
82
94
  async def _request_model(
83
95
  self,
84
- model: type[_MT],
96
+ model: type[_MT] | TypeAdapter[_T],
85
97
  method: str,
86
98
  url: str,
87
99
  **kwargs,
88
- ) -> _MT:
89
- return model.model_validate_json(
90
- await self._request(
91
- method=method,
92
- url=url,
93
- **kwargs,
94
- )
100
+ ) -> _MT | _T:
101
+ json: str = await self._request(
102
+ method=method,
103
+ url=url,
104
+ **kwargs,
95
105
  )
106
+ if isinstance(model, TypeAdapter):
107
+ return model.validate_json(json, strict=True)
108
+ else:
109
+ return model.model_validate_json(json, strict=True)
@@ -1,7 +1,9 @@
1
1
  from typing import Iterable
2
2
 
3
+ from pydantic import TypeAdapter
4
+
3
5
  from aioe621.endpoints.endpoint import Endpoint
4
- from aioe621.schemas import Post, _PostsListResponse, _PostsOnePostResponse
6
+ from aioe621.schemas import Post
5
7
 
6
8
 
7
9
  class Posts(Endpoint):
@@ -14,16 +16,16 @@ class Posts(Endpoint):
14
16
  if not isinstance(tags, str):
15
17
  tags = " ".join(tags)
16
18
 
17
- return (
18
- await self._request_model(
19
- _PostsListResponse,
20
- "GET",
21
- "/posts.json",
22
- tags=tags,
23
- limit=limit,
24
- page=page,
25
- )
26
- ).posts
19
+ return await self._request_model(
20
+ TypeAdapter(tuple[Post, ...]),
21
+ "GET",
22
+ "/posts.json",
23
+ tags=tags,
24
+ limit=limit,
25
+ page=page,
26
+ v2=True,
27
+ mode="extended",
28
+ )
27
29
 
28
30
  search = list
29
31
 
@@ -37,21 +39,21 @@ class Posts(Endpoint):
37
39
  """
38
40
  if id <= 0:
39
41
  raise ValueError("The post ID must not be less than or equal to zero.")
40
- return (
41
- await self._request_model(
42
- _PostsOnePostResponse,
43
- "GET",
44
- f"/posts/{id}.json",
45
- )
46
- ).post
42
+ return await self._request_model(
43
+ Post,
44
+ "GET",
45
+ f"/posts/{id}.json",
46
+ v2=True,
47
+ mode="extended",
48
+ )
47
49
 
48
50
  async def random(
49
51
  self,
50
52
  ) -> Post:
51
- return (
52
- await self._request_model(
53
- _PostsOnePostResponse,
54
- "GET",
55
- "/posts/random.json",
56
- )
57
- ).post
53
+ return await self._request_model(
54
+ Post,
55
+ "GET",
56
+ "/posts/random.json",
57
+ v2=True,
58
+ mode="extended",
59
+ )
@@ -1,13 +1,18 @@
1
1
  from datetime import datetime
2
2
  from enum import Enum
3
3
  from functools import cached_property
4
- from typing import Literal, Mapping, NamedTuple
4
+ from typing import Literal, NamedTuple
5
5
 
6
6
  from pydantic import BaseModel, ConfigDict, Field
7
7
 
8
8
 
9
9
  class APIModel(BaseModel):
10
- model_config = ConfigDict(frozen=True, ignored_types=(cached_property,))
10
+ model_config = ConfigDict(
11
+ frozen=True,
12
+ ignored_types=(cached_property,),
13
+ strict=True,
14
+ extra="forbid",
15
+ )
11
16
 
12
17
 
13
18
  class Auth(NamedTuple):
@@ -15,41 +20,26 @@ class Auth(NamedTuple):
15
20
  api_key: str
16
21
 
17
22
 
18
- class BaseFile(APIModel):
23
+ class FileDimensions(APIModel):
19
24
  width: int
20
25
  height: int
21
- url: str | None = None # Safest to make URL optional (deleted posts hide URLs)
22
26
 
23
27
 
24
- class File(BaseFile):
25
- ext: str
26
- size: int
27
- md5: str
28
+ class BaseFile(FileDimensions):
29
+ url: str | None
28
30
 
29
31
 
30
- class PreviewFile(BaseFile):
31
- alt: str | None = None
32
+ class PreviewFile(FileDimensions):
33
+ jpg: str | None
34
+ webp: str | None
32
35
 
33
36
 
34
- class VideoAlternate(APIModel):
35
- fps: int
36
- codec: str
37
+ class FilesMeta(APIModel):
38
+ md5: str
39
+ ext: str
37
40
  size: int
38
- width: int
39
- height: int
40
- url: str | None = None
41
-
42
-
43
- class Alternates(APIModel):
44
- has: bool = False
45
- original: VideoAlternate | None = None
46
- variants: Mapping[str, VideoAlternate] = Field(default_factory=dict)
47
- samples: Mapping[str, VideoAlternate] = Field(default_factory=dict)
48
-
49
-
50
- class SampleFile(PreviewFile):
51
- has: bool
52
- alternates: Alternates
41
+ duration: float | None
42
+ has_sample: bool
53
43
 
54
44
 
55
45
  class PostScore(APIModel):
@@ -93,58 +83,84 @@ class PostFlags(APIModel):
93
83
  deleted: bool
94
84
 
95
85
 
96
- class Rating(str, Enum):
86
+ class PostRating(str, Enum):
97
87
  SAFE = "s"
98
88
  QUESTIONABLE = "q"
99
89
  EXPLICIT = "e"
100
90
 
101
91
 
102
- class Relationships(APIModel):
103
- parent_id: int | None = None
104
- has_children: bool
105
- has_active_children: bool
106
- children: tuple[int, ...]
92
+ class VideoFile(BaseFile):
93
+ fps: float
94
+ codec: str # TODO: "vp9" or "avc1.4D401E" or ...?
95
+ size: int
107
96
 
108
97
 
109
- class Post(APIModel):
110
- id: int
111
- created_at: datetime
112
- updated_at: datetime
98
+ class VideoVariants(APIModel):
99
+ mp4: VideoFile | None = None
100
+ # TODO: any more?
101
+
102
+
103
+ class VideoSamples(APIModel):
104
+ p480: VideoFile | None = Field(alias="480p", default=None)
105
+ p720: VideoFile | None = Field(alias="720p", default=None)
106
+
107
+
108
+ class Video(APIModel):
109
+ has: Literal[True]
110
+ original: VideoFile
111
+ variants: VideoVariants
112
+ samples: VideoSamples
113
113
 
114
- file: File
114
+
115
+ class PostFiles(APIModel):
116
+ meta: FilesMeta
117
+ original: BaseFile
115
118
  preview: PreviewFile
116
- sample: SampleFile
119
+ sample: PreviewFile
120
+ video: Video | None = None
117
121
 
118
- score: PostScore
119
- tags: PostTags
120
- locked_tags: tuple[str, ...]
121
- change_seq: int
122
- flags: PostFlags
123
- rating: Rating
124
122
 
123
+ class PostStats(APIModel):
124
+ score: PostScore
125
125
  fav_count: int
126
- sources: tuple[str, ...]
127
- pools: tuple[int, ...]
128
- relationships: Relationships
129
-
130
- approver_id: int | None = None
131
- uploader_id: int
132
- uploader_name: str
133
- description: str
134
- comment_count: int
135
126
  is_favorited: bool
136
-
137
127
  vote: int
138
- has_notes: bool
139
- duration: float | None = None
128
+ comment_count: int
129
+ hotness: float
140
130
 
141
131
 
142
- class _PostsListResponse(APIModel):
143
- posts: tuple[Post, ...]
132
+ class PostHas(APIModel):
133
+ parent: bool
134
+ children: bool
135
+ active_children: bool
136
+ notes: bool
137
+ sample: bool
138
+
139
+
140
+ class PostRelationships(APIModel):
141
+ parent_id: int | None
142
+ children: tuple[int, ...]
144
143
 
145
144
 
146
- class _PostsOnePostResponse(APIModel):
147
- post: Post
145
+ class Post(APIModel):
146
+ id: int
147
+ created_at: datetime
148
+ updated_at: datetime
149
+ change_seq: int
150
+ files: PostFiles
151
+ uploader_id: int
152
+ uploader_name: str
153
+ approver_id: int | None
154
+ stats: PostStats
155
+ flags: PostFlags
156
+ has: PostHas
157
+ relationships: PostRelationships
158
+ pools: tuple[int, ...]
159
+ rating: PostRating
160
+ locked_tags: tuple[str, ...]
161
+ sources: tuple[str, ...]
162
+ description: str
163
+ tags: PostTags
148
164
 
149
165
 
150
166
  class _ErrorResponse(APIModel):
File without changes
File without changes
File without changes
File without changes