tumblrbot 1.4.7__py3-none-any.whl → 1.5.0__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.
tumblrbot/__main__.py CHANGED
@@ -19,8 +19,12 @@ def main() -> None:
19
19
  if Confirm.ask("Download latest posts?", default=False):
20
20
  PostDownloader(openai=openai, tumblr=tumblr).main()
21
21
 
22
+ examples_writer = ExamplesWriter(openai=openai, tumblr=tumblr)
22
23
  if Confirm.ask("Create training data?", default=False):
23
- ExamplesWriter(openai=openai, tumblr=tumblr).main()
24
+ examples_writer.main()
25
+
26
+ 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):
27
+ examples_writer.filter_examples()
24
28
 
25
29
  fine_tuner = FineTuner(openai=openai, tumblr=tumblr)
26
30
  fine_tuner.print_estimates()
@@ -36,18 +36,18 @@ class PostDownloader(FlowClass):
36
36
  task_id = live.progress.add_task(f"Downloading posts from '{blog_identifier}'...", total=None, completed=completed)
37
37
 
38
38
  while True:
39
- response = self.tumblr.retrieve_published_posts(blog_identifier, after=after).json()["response"]
40
- live.progress.update(task_id, total=response["blog"]["posts"], completed=completed)
39
+ response = self.tumblr.retrieve_published_posts(blog_identifier, after=after)
40
+ live.progress.update(task_id, total=response.response.blog.posts, completed=completed)
41
41
 
42
- if posts := response["posts"]:
43
- for post in posts:
44
- dump(post, fp)
45
- fp.write("\n")
42
+ if not response.response.posts:
43
+ return
46
44
 
47
- model = Post.model_validate(post)
48
- after = model.timestamp
49
- live.custom_update(model)
45
+ for post in response.response.posts:
46
+ dump(post, fp)
47
+ fp.write("\n")
50
48
 
51
- completed += len(posts)
52
- else:
53
- return
49
+ model = Post.model_validate(post)
50
+ after = model.timestamp
51
+ live.custom_update(model)
52
+
53
+ completed += len(response.response.posts)
@@ -7,7 +7,6 @@ from typing import IO, override
7
7
 
8
8
  import rich
9
9
  from openai import BadRequestError
10
- from rich.prompt import Confirm
11
10
 
12
11
  from tumblrbot.utils.common import FlowClass, PreviewLive
13
12
  from tumblrbot.utils.models import Example, Post
@@ -26,7 +25,7 @@ class ExamplesWriter(FlowClass):
26
25
  fp,
27
26
  )
28
27
 
29
- for post in self.get_filtered_posts():
28
+ for post in self.get_valid_posts():
30
29
  self.write_example(
31
30
  self.config.user_message,
32
31
  post.get_content_text(),
@@ -54,38 +53,33 @@ class ExamplesWriter(FlowClass):
54
53
  data: dict[str, str] = loads(line)
55
54
  yield from data.items()
56
55
 
57
- def get_filtered_posts(self) -> Generator[Post]:
58
- posts = self.get_valid_posts()
56
+ def get_valid_posts(self) -> Generator[Post]:
57
+ for data_path in self.get_data_paths():
58
+ with data_path.open("rb") as fp:
59
+ for line in fp:
60
+ post = Post.model_validate_json(line)
61
+ if post.valid_text_post():
62
+ yield post
59
63
 
60
- if Confirm.ask("[gray62]Remove posts flagged by the OpenAI moderation? This can sometimes resolve errors with fine-tuning validation, but is slow.", default=False):
64
+ def filter_examples(self) -> None:
65
+ examples = self.config.examples_file.read_text("utf_8").splitlines()
66
+ with self.config.examples_file.open("w", encoding="utf_8") as fp:
61
67
  batch_size = self.get_moderation_batch_size()
62
- posts = list(posts)
63
68
  removed = 0
64
69
 
65
70
  with PreviewLive() as live:
66
71
  for batch in live.progress.track(
67
- batched(posts, batch_size, strict=False),
68
- ceil(len(posts) / batch_size),
72
+ batched(examples, batch_size, strict=False),
73
+ ceil(len(examples) / batch_size),
69
74
  description="Removing flagged posts...",
70
75
  ):
71
- response = self.openai.moderations.create(input=list(map(Post.get_content_text, batch)))
72
- for post, moderation in zip(batch, response.results, strict=True):
76
+ response = self.openai.moderations.create(input=list(batch))
77
+ for example, moderation in zip(batch, response.results, strict=True):
73
78
  if moderation.flagged:
74
79
  removed += 1
75
- live.custom_update(post)
76
80
  else:
77
- yield post
81
+ fp.write(f"{example}\n")
78
82
  rich.print(f"[red]Removed {removed} posts.\n")
79
- else:
80
- yield from posts
81
-
82
- def get_valid_posts(self) -> Generator[Post]:
83
- for data_path in self.get_data_paths():
84
- with data_path.open("rb") as fp:
85
- for line in fp:
86
- post = Post.model_validate_json(line)
87
- if post.valid_text_post():
88
- yield post
89
83
 
90
84
  def get_moderation_batch_size(self) -> int:
91
85
  try:
@@ -28,26 +28,27 @@ class DraftGenerator(FlowClass):
28
28
  rich.print(f":chart_increasing: [bold green]Generated {self.config.draft_count} draft(s).[/] {message}")
29
29
 
30
30
  def generate_post(self) -> Post:
31
- content = self.generate_content()
32
- post = Post(content=[content])
33
- if tags := self.generate_tags(content):
34
- post.tags = tags.tags
35
- return post
36
-
37
- def generate_content(self) -> Post.Block:
38
- content = self.openai.responses.create(
31
+ text = self.generate_text()
32
+ if tags := self.generate_tags(text):
33
+ tags = tags.tags
34
+ return Post(
35
+ content=[Post.Block(type="text", text=text)],
36
+ tags=tags or [],
37
+ state="draft",
38
+ )
39
+
40
+ def generate_text(self) -> str:
41
+ return self.openai.responses.create(
39
42
  input=self.config.user_message,
40
43
  instructions=self.config.developer_message,
41
44
  model=self.config.fine_tuned_model,
42
45
  ).output_text
43
46
 
44
- return Post.Block(text=content)
45
-
46
- def generate_tags(self, content: Post.Block) -> Post | None:
47
+ def generate_tags(self, text: str) -> Post | None:
47
48
  if random() < self.config.tags_chance: # noqa: S311
48
49
  return self.openai.responses.parse(
49
50
  text_format=Post,
50
- input=content.text,
51
+ input=text,
51
52
  instructions=self.config.tags_developer_message,
52
53
  model=self.config.base_model,
53
54
  ).output_parsed
tumblrbot/utils/models.py CHANGED
@@ -170,15 +170,26 @@ class Tokens(FileSyncSettings):
170
170
  return self
171
171
 
172
172
 
173
+ class ResponseModel(FullyValidatedModel):
174
+ class Response(FullyValidatedModel):
175
+ class Blog(FullyValidatedModel):
176
+ posts: int
177
+
178
+ blog: Blog = Blog(posts=0)
179
+ posts: list[Any] = []
180
+
181
+ response: Response
182
+
183
+
173
184
  class Post(FullyValidatedModel):
174
185
  class Block(FullyValidatedModel):
175
- type: str = "text"
186
+ type: str
176
187
  text: str = ""
177
188
  blocks: list[int] = []
178
189
 
179
190
  timestamp: SkipJsonSchema[int] = 0
180
- tags: Annotated[list[str], PlainSerializer(",".join)] = []
181
- state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "draft"
191
+ tags: Annotated[list[str], PlainSerializer(",".join)]
192
+ state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "published"
182
193
 
183
194
  content: SkipJsonSchema[list[Block]] = []
184
195
  layout: SkipJsonSchema[list[Block]] = []
tumblrbot/utils/tumblr.py CHANGED
@@ -3,7 +3,7 @@ from typing import Self
3
3
  from requests import HTTPError, Response
4
4
  from requests_oauthlib import OAuth1Session
5
5
 
6
- from tumblrbot.utils.models import Post, Tokens
6
+ from tumblrbot.utils.models import Post, ResponseModel, Tokens
7
7
 
8
8
 
9
9
  class TumblrSession(OAuth1Session):
@@ -22,8 +22,8 @@ class TumblrSession(OAuth1Session):
22
22
  error.add_note(response.text)
23
23
  raise
24
24
 
25
- def retrieve_published_posts(self, blog_identifier: str, after: int) -> Response:
26
- return self.get(
25
+ def retrieve_published_posts(self, blog_identifier: str, after: int) -> ResponseModel:
26
+ response = self.get(
27
27
  f"https://api.tumblr.com/v2/blog/{blog_identifier}/posts",
28
28
  params={
29
29
  "after": after,
@@ -31,9 +31,11 @@ class TumblrSession(OAuth1Session):
31
31
  "npf": True,
32
32
  },
33
33
  )
34
+ return ResponseModel.model_validate_json(response.content)
34
35
 
35
- def create_post(self, blog_identifier: str, post: Post) -> Response:
36
- return self.post(
36
+ def create_post(self, blog_identifier: str, post: Post) -> ResponseModel:
37
+ response = self.post(
37
38
  f"https://api.tumblr.com/v2/blog/{blog_identifier}/posts",
38
39
  json=post.model_dump(),
39
40
  )
41
+ return ResponseModel.model_validate_json(response.content)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tumblrbot
3
- Version: 1.4.7
3
+ Version: 1.5.0
4
4
  Summary: An updated bot that posts to Tumblr, based on your very own blog!
5
5
  Requires-Python: >= 3.13
6
6
  Description-Content-Type: text/markdown
@@ -13,6 +13,7 @@ Requires-Dist: requests-oauthlib
13
13
  Requires-Dist: rich
14
14
  Requires-Dist: tiktoken
15
15
  Requires-Dist: tomlkit
16
+ Project-URL: Funding, https://ko-fi.com/maidscientistizutsumimarin
16
17
  Project-URL: Source, https://github.com/MaidScientistIzutsumiMarin/tumblrbot
17
18
 
18
19
  # tumblrbot
@@ -30,8 +31,8 @@ Project-URL: Source, https://github.com/MaidScientistIzutsumiMarin/tumblrbot
30
31
  [OpenAI]: https://pypi.org/project/openai
31
32
  [OpenAI Pricing]: https://platform.openai.com/docs/pricing#fine-tuning
32
33
  [OpenAI Tokens]: https://platform.openai.com/settings/organization/api-keys
34
+ [OpenAI Moderation API]: https://platform.openai.com/docs/guides/moderation
33
35
  [Fine-Tuning Portal]: https://platform.openai.com/finetune
34
- [Moderation API]: https://platform.openai.com/docs/api-reference/moderations
35
36
 
36
37
  [Tumblr]: https://tumblr.com
37
38
  [Tumblr Tokens]: https://tumblr.com/oauth/apps
@@ -62,9 +63,8 @@ Features:
62
63
  - Shows progress and previews the current post.
63
64
  1. [Creates examples][Examples] to fine-tune the model from your posts.
64
65
  - Filters out posts that contain more than just text data.
65
- - Filters out any posts flagged by the [OpenAI] [Moderation API] (optional).
66
- - Shows progress and previews the current post.
67
66
  - Adds custom user messages and assistant responses to the dataset from the [configured][config] file.
67
+ 1. Filters out any posts flagged by the [OpenAI Moderation API].
68
68
  1. [Uploads examples][Fine-Tune] to [OpenAI] and begins the fine-tuning process.
69
69
  - Provides cost estimates if the currently saved examples are used to fine-tune the [configured][config] model.
70
70
  - Resumes monitoring the same fine-tuning process when restarted.
@@ -80,6 +80,7 @@ Features:
80
80
  **To-Do:**
81
81
 
82
82
  - Add code documentation.
83
+ - Add reblog generation logic.
83
84
 
84
85
  **Known Issues:**
85
86
 
@@ -0,0 +1,15 @@
1
+ tumblrbot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tumblrbot/__main__.py,sha256=XWSbOmI_y2MJVU9xpkgA-0zaF3HNwR5uF6_BZqtCQWY,1719
3
+ tumblrbot/flow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ tumblrbot/flow/download.py,sha256=PUs7eM-1kGOb2RKijy3lW0zyvfFDwbxzTGhVghrWIhc,2012
5
+ tumblrbot/flow/examples.py,sha256=MlukrVdzpIwk_-37PpRsBGV5eX-lLlNUUYvuozXC_vw,3726
6
+ tumblrbot/flow/fine_tune.py,sha256=YDukEwZNw3GveEAH4ORv6oylka5MQNLK_4iSmuAVPtg,5387
7
+ tumblrbot/flow/generate.py,sha256=cfIdmLFNuuKjUgk4Jtp0aTf2u86jOAUFuziq71zjDME,2148
8
+ tumblrbot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ tumblrbot/utils/common.py,sha256=RvICPORtBSqsN7VWADgStogJ8w4owzBfR1E2XbCQrfA,1795
10
+ tumblrbot/utils/models.py,sha256=wAS3ptbaQX3J6IlixAdhBD2wcs4BO64HD6JcCB7W6lg,9903
11
+ tumblrbot/utils/tumblr.py,sha256=AgrczLFyrxES66N4PwIrjxX3QcpGvh8HP-jw0lwtmc0,1427
12
+ tumblrbot-1.5.0.dist-info/entry_points.txt,sha256=lTiN7PxAbyGY1fpCWApEw6NUIUgobfcOKhvn6cu3IQA,53
13
+ tumblrbot-1.5.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
14
+ tumblrbot-1.5.0.dist-info/METADATA,sha256=-mGGhfyRyVcO1M0PtNAoVakPqqMw0sAevp2XkaYrfgw,10129
15
+ tumblrbot-1.5.0.dist-info/RECORD,,
@@ -1,15 +0,0 @@
1
- tumblrbot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- tumblrbot/__main__.py,sha256=BenjVNlVZDy-ZlSWukEIguGLa6qXvZjhYSSWMqa8-0Q,1447
3
- tumblrbot/flow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- tumblrbot/flow/download.py,sha256=wdXmtCnnArn8Zw7D2Hoa_KhH-k61j9w3cbYztgBkUlY,2036
5
- tumblrbot/flow/examples.py,sha256=Th6vgiu3D2VloOx7otZlk164h3ifkJEwDk21YHMEYP0,3976
6
- tumblrbot/flow/fine_tune.py,sha256=YDukEwZNw3GveEAH4ORv6oylka5MQNLK_4iSmuAVPtg,5387
7
- tumblrbot/flow/generate.py,sha256=Q6nUtmoj28-rGUCs4V0fuovJshvFMlmipyu9GGqnmzM,2147
8
- tumblrbot/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- tumblrbot/utils/common.py,sha256=RvICPORtBSqsN7VWADgStogJ8w4owzBfR1E2XbCQrfA,1795
10
- tumblrbot/utils/models.py,sha256=Z0k16qJsZEO8tfmPp7X3edz-RgGCDLRSm7HrSDLGh1Y,9663
11
- tumblrbot/utils/tumblr.py,sha256=6V9AjT-dyR2vuUkfqgqs52Ua5irhQJzhgQhV54xKyGM,1258
12
- tumblrbot-1.4.7.dist-info/entry_points.txt,sha256=lTiN7PxAbyGY1fpCWApEw6NUIUgobfcOKhvn6cu3IQA,53
13
- tumblrbot-1.4.7.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
14
- tumblrbot-1.4.7.dist-info/METADATA,sha256=q_PH1oU-d0PRdXDuL5Bjh-Y8gccZJVvLCCho2E_CLvc,10104
15
- tumblrbot-1.4.7.dist-info/RECORD,,