tumblrbot 1.9.6__tar.gz → 1.9.7__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.4
2
2
  Name: tumblrbot
3
- Version: 1.9.6
3
+ Version: 1.9.7
4
4
  Summary: An updated bot that posts to Tumblr, based on your very own blog!
5
5
  Requires-Python: >= 3.14
6
6
  Description-Content-Type: text/markdown
@@ -12,8 +12,10 @@ Requires-Dist: rich
12
12
  Requires-Dist: tenacity
13
13
  Requires-Dist: tiktoken
14
14
  Requires-Dist: tomlkit
15
+ Requires-Dist: pyinstaller ; extra == "dev"
15
16
  Project-URL: Funding, https://ko-fi.com/maidscientistizutsumimarin
16
17
  Project-URL: Source, https://github.com/MaidScientistIzutsumiMarin/tumblrbot
18
+ Provides-Extra: dev
17
19
 
18
20
  # tumblrbot
19
21
 
@@ -38,7 +40,6 @@ Project-URL: Source, https://github.com/MaidScientistIzutsumiMarin/tumblrbot
38
40
  [Tumblr]: https://tumblr.com
39
41
  [Tumblr Tokens]: https://tumblr.com/oauth/apps
40
42
  [Tumblr API Documentation on Blog Identifiers]: https://tumblr.com/docs/en/api/v2#blog-identifiers
41
- [Tumblr API Documentation on Rate Limits]: https://tumblr.com/docs/en/api/v2#rate-limits
42
43
 
43
44
  [Format String]: https://docs.python.org/3/library/string.html#format-string-syntax
44
45
 
@@ -92,10 +93,9 @@ Features:
92
93
  - You can use regular expressions to filter out training data in the [config][configurable]. This is more of a brute-force solution, but it can work if the other solutions do not.
93
94
  - You can try limiting your dataset by specifying fewer blogs to download from or limiting the number of posts taken from each one in the [config][configurable].
94
95
  - If all else fails, you can manually remove data from the examples file until it passes. It is unfortunately not a definitive resource, but it can help to read about what the [OpenAI moderation API flags][Flags].
95
- - Sometimes, you will get an error about the training file not being found when starting fine-tuning. We do not currently have a fix or workaround for this. You should instead use the online portal for fine-tuning if this continues to happen. Read more in [fine-tuning].
96
- - Post counts are incorrect when downloading posts. We are not certain what the cause of this is, but our tests suggest this is a [Tumblr] API problem that is giving inaccurate numbers.
97
- - During post downloading or post generation, you may receive a “Limit Exceeded” error message from the [Tumblr] API. This is caused by server-side rate-limiting by [Tumblr]. The only workaround is trying again or waiting for a period of time before retrying. In most cases, you either have to wait for a minute or an hour for the limits to reset. You can read more about the limits in the [Tumblr API documentation on rate limits].
98
- - Similar to the above issue, you may sometimes get a message saying your IP is blocked. This block is temporary and probably follows the same rules as previously described.
96
+ - Sometimes, you will get an error about the training file not being found when starting fine-tuning. We do not currently have a fix or workaround for this. You should instead use the online portal for fine-tuning if this continues to happen. Read more in [fine-tuning]
97
+ - *We are unsure if this is still happening.*
98
+ - Post counts are incorrect when downloading posts. Our tests suggest this is a [Tumblr] API problem that is giving inaccurate numbers, so treat them as estimates.
99
99
 
100
100
  **Please submit an issue or contact us for features you want added/reimplemented.**
101
101
 
@@ -21,7 +21,6 @@
21
21
  [Tumblr]: https://tumblr.com
22
22
  [Tumblr Tokens]: https://tumblr.com/oauth/apps
23
23
  [Tumblr API Documentation on Blog Identifiers]: https://tumblr.com/docs/en/api/v2#blog-identifiers
24
- [Tumblr API Documentation on Rate Limits]: https://tumblr.com/docs/en/api/v2#rate-limits
25
24
 
26
25
  [Format String]: https://docs.python.org/3/library/string.html#format-string-syntax
27
26
 
@@ -75,10 +74,9 @@ Features:
75
74
  - You can use regular expressions to filter out training data in the [config][configurable]. This is more of a brute-force solution, but it can work if the other solutions do not.
76
75
  - You can try limiting your dataset by specifying fewer blogs to download from or limiting the number of posts taken from each one in the [config][configurable].
77
76
  - If all else fails, you can manually remove data from the examples file until it passes. It is unfortunately not a definitive resource, but it can help to read about what the [OpenAI moderation API flags][Flags].
78
- - Sometimes, you will get an error about the training file not being found when starting fine-tuning. We do not currently have a fix or workaround for this. You should instead use the online portal for fine-tuning if this continues to happen. Read more in [fine-tuning].
79
- - Post counts are incorrect when downloading posts. We are not certain what the cause of this is, but our tests suggest this is a [Tumblr] API problem that is giving inaccurate numbers.
80
- - During post downloading or post generation, you may receive a “Limit Exceeded” error message from the [Tumblr] API. This is caused by server-side rate-limiting by [Tumblr]. The only workaround is trying again or waiting for a period of time before retrying. In most cases, you either have to wait for a minute or an hour for the limits to reset. You can read more about the limits in the [Tumblr API documentation on rate limits].
81
- - Similar to the above issue, you may sometimes get a message saying your IP is blocked. This block is temporary and probably follows the same rules as previously described.
77
+ - Sometimes, you will get an error about the training file not being found when starting fine-tuning. We do not currently have a fix or workaround for this. You should instead use the online portal for fine-tuning if this continues to happen. Read more in [fine-tuning]
78
+ - *We are unsure if this is still happening.*
79
+ - Post counts are incorrect when downloading posts. Our tests suggest this is a [Tumblr] API problem that is giving inaccurate numbers, so treat them as estimates.
82
80
 
83
81
  **Please submit an issue or contact us for features you want added/reimplemented.**
84
82
 
@@ -0,0 +1,8 @@
1
+ pyinstaller.exe `
2
+ --noconfirm `
3
+ --onefile `
4
+ --name tumblrbot `
5
+ --hidden-import tiktoken_ext.openai_public `
6
+ --optimize 2 `
7
+ src\tumblrbot\__main__.py &&
8
+ Remove-Item tumblrbot.spec
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tumblrbot"
3
- version = "1.9.6"
3
+ version = "1.9.7"
4
4
  description = "An updated bot that posts to Tumblr, based on your very own blog!"
5
5
  readme = "README.md"
6
6
  requires-python = ">= 3.14"
@@ -22,6 +22,9 @@ Source = "https://github.com/MaidScientistIzutsumiMarin/tumblrbot"
22
22
  [project.scripts]
23
23
  tumblrbot = "tumblrbot.__main__:main"
24
24
 
25
+ [project.optional-dependencies]
26
+ dev = ["pyinstaller"]
27
+
25
28
  [build-system]
26
29
  requires = ["flit_core"]
27
30
  build-backend = "flit_core.buildapi"
@@ -19,16 +19,16 @@ def main() -> None:
19
19
  tokens = Tokens.load()
20
20
  with OpenAI(api_key=tokens.openai_api_key) as openai, TumblrSession(tokens) as tumblr:
21
21
  if Confirm.ask("Download latest posts?", default=False):
22
- PostDownloader(openai=openai, tumblr=tumblr).main()
22
+ PostDownloader(openai, tumblr).main()
23
23
 
24
- examples_writer = ExamplesWriter(openai=openai, tumblr=tumblr)
24
+ examples_writer = ExamplesWriter(openai, tumblr)
25
25
  if Confirm.ask("Create training data?", default=False):
26
26
  examples_writer.main()
27
27
 
28
28
  if Confirm.ask("Remove training data flagged by the OpenAI moderation? [bold]This can sometimes resolve errors with fine-tuning validation, but is slow.", default=False):
29
29
  examples_writer.filter_examples()
30
30
 
31
- fine_tuner = FineTuner(openai=openai, tumblr=tumblr)
31
+ fine_tuner = FineTuner(openai, tumblr)
32
32
  fine_tuner.print_estimates()
33
33
 
34
34
  message = "Resume monitoring the previous fine-tuning process?" if FlowClass.config.job_id else "Upload data to OpenAI for fine-tuning?"
@@ -36,7 +36,7 @@ def main() -> None:
36
36
  fine_tuner.main()
37
37
 
38
38
  if Confirm.ask("Generate drafts?", default=False):
39
- DraftGenerator(openai=openai, tumblr=tumblr).main()
39
+ DraftGenerator(openai, tumblr).main()
40
40
 
41
41
 
42
42
  if __name__ == "__main__":
@@ -1,8 +1,8 @@
1
+ from dataclasses import dataclass
1
2
  from functools import cache
2
3
  from random import choice, random, sample
3
4
  from typing import TYPE_CHECKING, override
4
5
 
5
- from pydantic import ConfigDict
6
6
  from rich import print as rich_print
7
7
  from rich.prompt import IntPrompt
8
8
 
@@ -13,9 +13,8 @@ if TYPE_CHECKING:
13
13
  from collections.abc import Iterable
14
14
 
15
15
 
16
+ @dataclass(frozen=True)
16
17
  class DraftGenerator(FlowClass):
17
- model_config = ConfigDict(frozen=True) # Makes this class hashable.
18
-
19
18
  @override
20
19
  def main(self) -> None:
21
20
  self.config.draft_count = IntPrompt.ask("How many drafts should be generated?", default=self.config.draft_count)
@@ -48,7 +47,7 @@ class DraftGenerator(FlowClass):
48
47
  tags = tags.tags
49
48
 
50
49
  return Post(
51
- content=[Block(type="text", text=text)],
50
+ content=[Block(text=text)],
52
51
  tags=tags or [],
53
52
  parent_tumblelog_uuid=original.blog.uuid,
54
53
  parent_post_id=original.id,
@@ -1,15 +1,15 @@
1
1
  from abc import abstractmethod
2
+ from dataclasses import dataclass
2
3
  from random import choice
3
- from typing import TYPE_CHECKING, ClassVar, Self, override
4
+ from typing import TYPE_CHECKING, ClassVar
4
5
 
5
6
  from openai import OpenAI # noqa: TC002
6
- from pydantic import ConfigDict
7
7
  from rich._spinners import SPINNERS
8
8
  from rich.live import Live
9
9
  from rich.progress import MofNCompleteColumn, Progress, SpinnerColumn, TimeElapsedColumn
10
10
  from rich.table import Table
11
11
 
12
- from tumblrbot.utils.models import Config, FullyValidatedModel
12
+ from tumblrbot.utils.models import Config
13
13
  from tumblrbot.utils.tumblr import TumblrSession # noqa: TC001
14
14
 
15
15
  if TYPE_CHECKING:
@@ -18,9 +18,8 @@ if TYPE_CHECKING:
18
18
  from rich.console import RenderableType
19
19
 
20
20
 
21
- class FlowClass(FullyValidatedModel):
22
- model_config = ConfigDict(arbitrary_types_allowed=True)
23
-
21
+ @dataclass(frozen=True)
22
+ class FlowClass:
24
23
  config: ClassVar = Config.load()
25
24
 
26
25
  openai: OpenAI
@@ -51,11 +50,6 @@ class PreviewLive(Live):
51
50
 
52
51
  self.custom_update()
53
52
 
54
- @override
55
- def __enter__(self) -> Self:
56
- super().__enter__()
57
- return self
58
-
59
53
  def custom_update(self, *renderables: RenderableType | None) -> None:
60
54
  table = Table.grid()
61
55
  table.add_row(self.progress)
@@ -112,6 +112,16 @@ class Tokens(FileSyncSettings):
112
112
  openai_api_key: str = ""
113
113
  tumblr: Tumblr = Tumblr()
114
114
 
115
+ @staticmethod
116
+ def online_token_prompt(url: str, *tokens: str) -> Generator[str]:
117
+ formatted_token_string = " and ".join(f"[cyan]{token}[/]" for token in tokens)
118
+
119
+ rich_print(f"Retrieve your {formatted_token_string} from: {url}")
120
+ for token in tokens:
121
+ yield getpass(f"Enter your {token} (masked): ", echo_char="*").strip()
122
+
123
+ rich_print()
124
+
115
125
  @override
116
126
  def model_post_init(self, context: object) -> None:
117
127
  super().model_post_init(context)
@@ -126,38 +136,18 @@ class Tokens(FileSyncSettings):
126
136
  # This is the whole OAuth 1.0 process.
127
137
  # https://requests-oauthlib.readthedocs.io/en/latest/examples/tumblr.html
128
138
  # We tried setting up OAuth 2.0, but the token refresh process is far too unreliable for this sort of program.
129
- with OAuth1Session(
130
- self.tumblr.client_key,
131
- self.tumblr.client_secret,
132
- ) as oauth_session:
133
- fetch_response = oauth_session.fetch_request_token("http://tumblr.com/oauth/request_token") # pyright: ignore[reportUnknownMemberType]
134
- full_authorize_url = oauth_session.authorization_url("http://tumblr.com/oauth/authorize") # pyright: ignore[reportUnknownMemberType]
135
- (redirect_response,) = self.online_token_prompt(full_authorize_url, "full redirect URL")
136
- oauth_response = oauth_session.parse_authorization_response(redirect_response)
137
-
138
- with OAuth1Session(
139
- self.tumblr.client_key,
140
- self.tumblr.client_secret,
141
- *self.get_oauth_tokens(fetch_response),
142
- verifier=oauth_response["oauth_verifier"],
143
- ) as oauth_session:
144
- oauth_tokens = oauth_session.fetch_access_token("http://tumblr.com/oauth/access_token") # pyright: ignore[reportUnknownMemberType]
145
-
146
- self.tumblr.resource_owner_key, self.tumblr.resource_owner_secret = self.get_oauth_tokens(oauth_tokens)
147
-
148
- @staticmethod
149
- def online_token_prompt(url: str, *tokens: str) -> Generator[str]:
150
- formatted_token_string = " and ".join(f"[cyan]{token}[/]" for token in tokens)
139
+ with OAuth1Session(**self.tumblr.model_dump()) as session:
140
+ session.fetch_request_token("http://tumblr.com/oauth/request_token") # pyright: ignore[reportUnknownMemberType]
151
141
 
152
- rich_print(f"Retrieve your {formatted_token_string} from: {url}")
153
- for token in tokens:
154
- yield getpass(f"Enter your {token} (masked): ", echo_char="*").strip()
142
+ rich_print("Open the link below in your browser, and authorize this application.\nAfter authorizing, copy and paste the URL of the page you are redirected to below.")
143
+ authorization_url = session.authorization_url("http://tumblr.com/oauth/authorize") # pyright: ignore[reportUnknownMemberType]
144
+ (authorization_response,) = self.online_token_prompt(authorization_url, "full redirect URL")
145
+ session.parse_authorization_response(authorization_response)
155
146
 
156
- rich_print()
147
+ access_token = session.fetch_access_token("http://tumblr.com/oauth/access_token") # pyright: ignore[reportUnknownMemberType]
157
148
 
158
- @staticmethod
159
- def get_oauth_tokens(token: dict[str, str]) -> tuple[str, str]:
160
- return token["oauth_token"], token["oauth_token_secret"]
149
+ self.tumblr.resource_owner_key = access_token["oauth_token"]
150
+ self.tumblr.resource_owner_secret = access_token["oauth_token_secret"]
161
151
 
162
152
 
163
153
  class Blog(FullyValidatedModel):
@@ -166,35 +156,34 @@ class Blog(FullyValidatedModel):
166
156
  uuid: str = ""
167
157
 
168
158
 
169
- class Response(FullyValidatedModel):
170
- blog: Blog = Blog()
171
- posts: list[Any] = []
172
-
173
-
174
159
  class ResponseModel(FullyValidatedModel):
160
+ class Response(FullyValidatedModel):
161
+ blog: Blog = Blog()
162
+ posts: list[Any] = []
163
+
175
164
  response: Response
176
165
 
177
166
 
178
167
  class Block(FullyValidatedModel):
179
- type: str = ""
168
+ type: str = "text"
180
169
  text: str = ""
181
170
  blocks: list[int] = []
182
171
 
183
172
 
184
173
  class Post(FullyValidatedModel):
185
- blog: SkipJsonSchema[Blog] = Blog()
186
- id: SkipJsonSchema[int] = 0
187
- parent_tumblelog_uuid: SkipJsonSchema[str] = ""
188
- parent_post_id: SkipJsonSchema[int] = 0
189
- reblog_key: SkipJsonSchema[str] = ""
174
+ blog: Blog = Blog()
175
+ id: int = 0
176
+ parent_tumblelog_uuid: str = ""
177
+ parent_post_id: int = 0
178
+ reblog_key: str = ""
190
179
 
191
- timestamp: SkipJsonSchema[int] = 0
180
+ timestamp: int = 0
192
181
  tags: Annotated[list[str], PlainSerializer(",".join)] = []
193
- state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "draft"
182
+ state: Literal["published", "queued", "draft", "private", "unapproved"] = "draft"
194
183
 
195
- content: SkipJsonSchema[list[Block]] = []
196
- layout: SkipJsonSchema[list[Block]] = []
197
- trail: SkipJsonSchema[list[Self]] = []
184
+ content: list[Block] = []
185
+ layout: list[Block] = []
186
+ trail: list[Self] = []
198
187
 
199
188
  is_submission: SkipJsonSchema[bool] = False
200
189
 
@@ -1,7 +1,5 @@
1
- from typing import Self
2
-
3
- from requests import HTTPError, Response
4
- from requests_oauthlib import OAuth1Session
1
+ from requests import HTTPError, Response, Session
2
+ from requests_oauthlib import OAuth1
5
3
  from rich import print as rich_print
6
4
  from tenacity import retry, retry_if_exception_message, stop_after_attempt, wait_random_exponential
7
5
 
@@ -16,25 +14,30 @@ rate_limit_retry = retry(
16
14
  )
17
15
 
18
16
 
19
- class TumblrSession(OAuth1Session):
17
+ class TumblrSession(Session):
20
18
  def __init__(self, tokens: Tokens) -> None:
21
- super().__init__(**tokens.tumblr.model_dump()) # pyright: ignore[reportUnknownMemberType]
19
+ super().__init__()
20
+ self.auth = OAuth1(**tokens.tumblr.model_dump())
22
21
  self.hooks["response"].append(self.response_hook)
23
22
 
24
- def __enter__(self) -> Self:
25
- super().__enter__()
26
- return self
23
+ self.api_key = tokens.tumblr.client_key
27
24
 
28
25
  def response_hook(self, response: Response, *_args: object, **_kwargs: object) -> None:
29
26
  try:
30
27
  response.raise_for_status()
31
28
  except HTTPError as error:
32
- error.add_note(response.text)
29
+ for error_msg in response.json()["errors"]:
30
+ error.add_note(f"{error_msg['code']}: {error_msg['detail']}")
33
31
  raise
34
32
 
35
33
  @rate_limit_retry
36
34
  def retrieve_blog_info(self, blog_identifier: str) -> ResponseModel:
37
- response = self.get(f"https://api.tumblr.com/v2/blog/{blog_identifier}/info")
35
+ response = self.get(
36
+ f"https://api.tumblr.com/v2/blog/{blog_identifier}/info",
37
+ params={
38
+ "api_key": self.api_key,
39
+ },
40
+ )
38
41
  return ResponseModel.model_validate_json(response.text)
39
42
 
40
43
  @rate_limit_retry
@@ -47,6 +50,7 @@ class TumblrSession(OAuth1Session):
47
50
  response = self.get(
48
51
  f"https://api.tumblr.com/v2/blog/{blog_identifier}/posts",
49
52
  params={
53
+ "api_key": self.api_key,
50
54
  "offset": offset,
51
55
  "after": after,
52
56
  "sort": "asc",
tumblrbot-1.9.6/build.ps1 DELETED
@@ -1 +0,0 @@
1
- ..\..\Powershell\build.ps1 -ExtraArgs '--collect-all tiktoken_ext'
File without changes
File without changes
File without changes