zapostit-api 0.2.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.
@@ -0,0 +1,37 @@
1
+ name: publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.13"
16
+
17
+ - run: python -m pip install --upgrade build
18
+ - run: python -m build
19
+
20
+ - uses: actions/upload-artifact@v4
21
+ with:
22
+ name: python-package-distributions
23
+ path: dist/
24
+
25
+ publish:
26
+ needs: build
27
+ runs-on: ubuntu-latest
28
+ environment: pypi
29
+ permissions:
30
+ id-token: write
31
+ steps:
32
+ - uses: actions/download-artifact@v4
33
+ with:
34
+ name: python-package-distributions
35
+ path: dist/
36
+
37
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ .mypy_cache/
3
+ .ruff_cache/
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.egg-info/
7
+ build/
8
+ dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 yaroslav
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.5
2
+ Name: zapostit-api
3
+ Version: 0.2.0
4
+ Summary: Typed Python client for zapostit.com
5
+ Project-URL: Repository, https://github.com/yar2slav/zapostit-api
6
+ Project-URL: Documentation, https://github.com/yar2slav/zapostit-api#readme
7
+ Project-URL: Issues, https://github.com/yar2slav/zapostit-api/issues
8
+ Author: yaroslav
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: httpx<1,>=0.27
13
+ Requires-Dist: pydantic<3,>=2.7
14
+ Provides-Extra: dev
15
+ Requires-Dist: mypy>=1.11; extra == 'dev'
16
+ Requires-Dist: ruff>=0.6; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # zapostit-api
20
+
21
+ [русская версия](README.ru.md)
22
+
23
+ typed python client for zapostit.com with sync and async apis
24
+
25
+ it reads authors, posts, comments, replies, polls and media links. authenticated sessions can create comments, reply, toggle reactions and delete their own comments
26
+
27
+ ## installation
28
+
29
+ python 3.11 or newer is required
30
+
31
+ ```bash
32
+ pip install "zapostit-api @ git+ssh://git@github.com/yar2slav/zapostit-api.git@v0.2.0"
33
+ ```
34
+
35
+ local editable install:
36
+
37
+ ```bash
38
+ git clone git@github.com:yar2slav/zapostit-api.git
39
+ cd zapostit-api
40
+ pip install -e ".[dev]"
41
+ ```
42
+
43
+ ## basic usage
44
+
45
+ ```python
46
+ from zapostit import Zapostit
47
+
48
+
49
+ with Zapostit() as api:
50
+ author = api.get_author("slivach")
51
+ print(author.name, api.get_post_count(author.id))
52
+
53
+ for post in api.iter_posts(author.slug, max_posts=20):
54
+ print(post.created_at, post.url)
55
+ print(post.text_with_links)
56
+
57
+ for media in post.media:
58
+ print(media.kind, media.url)
59
+ ```
60
+
61
+ `post.text` contains the original text. `post.text_with_links` shows hidden hyperlink targets next to the linked text
62
+
63
+ ## pages and search
64
+
65
+ ```python
66
+ page = api.get_posts_page("slivach", limit=10)
67
+
68
+ for post in page.items:
69
+ print(post.id)
70
+
71
+ next_page = api.get_posts_page(
72
+ "slivach",
73
+ cursor=page.next_cursor,
74
+ limit=10,
75
+ )
76
+ ```
77
+
78
+ `iter_posts()` handles cursors and duplicate ids automatically
79
+
80
+ ```python
81
+ for post in api.search_posts("pozdnyakov", "Черногория"):
82
+ print(post.url, post.text_with_links)
83
+ ```
84
+
85
+ ## comments
86
+
87
+ ```python
88
+ for comment in api.iter_comments(post_id=104459, sort="newest"):
89
+ print(comment.author.name, comment.text_with_links)
90
+ ```
91
+
92
+ comments can also be returned as a tree:
93
+
94
+ ```python
95
+ threads = api.get_comment_threads(104459)
96
+
97
+ for thread in threads:
98
+ print(thread.comment.text)
99
+ for reply in thread.replies:
100
+ print(" ", reply.comment.text)
101
+ ```
102
+
103
+ ## media
104
+
105
+ the library returns public urls and metadata without downloading files
106
+
107
+ ```python
108
+ for media in post.media:
109
+ print(media.kind, media.url, media.thumbnail_url)
110
+
111
+ for variant in media.variants:
112
+ print(variant.type, variant.width, variant.height, variant.url)
113
+ ```
114
+
115
+ photos, albums, videos, voice messages, audio, documents and polls are supported
116
+
117
+ ## polling
118
+
119
+ ```python
120
+ with Zapostit() as api:
121
+ for post in api.watch_posts("slivach", interval=30):
122
+ print("new post:", post.url)
123
+ ```
124
+
125
+ the first request creates a baseline. pass `emit_existing=True` to receive the current first page immediately
126
+
127
+ ## authenticated actions
128
+
129
+ an existing session token can be passed through an environment variable:
130
+
131
+ ```python
132
+ import os
133
+
134
+ from zapostit import Zapostit
135
+
136
+
137
+ with Zapostit(session_token=os.environ["ZAPOSTIT_SESSION_TOKEN"]) as api:
138
+ print(api.get_session().user.login)
139
+
140
+ comment = api.create_comment(104459, "hello")
141
+ reply = api.reply_to_comment(104459, comment.id, "reply")
142
+
143
+ api.toggle_reaction(104459, "post", emoji_id=6)
144
+ api.toggle_reaction(comment.id, "comment", emoji_id=0)
145
+
146
+ api.delete_comment(reply.id)
147
+ ```
148
+
149
+ mutation requests are sent once and are not retried automatically
150
+
151
+ ## async usage
152
+
153
+ `AsyncZapostit` has the same method names. iterators become async iterators
154
+
155
+ ```python
156
+ import asyncio
157
+
158
+ from zapostit import AsyncZapostit
159
+
160
+
161
+ async def main() -> None:
162
+ async with AsyncZapostit() as api:
163
+ async for post in api.iter_posts("slivach", max_posts=20):
164
+ print(post.url)
165
+
166
+
167
+ asyncio.run(main())
168
+ ```
169
+
170
+ ## main methods
171
+
172
+ - `get_authors()`, `get_author()`, `get_post_count()`
173
+ - `get_posts_page()`, `iter_posts()`, `search_posts()`, `iter_all_posts()`
174
+ - `get_comments_page()`, `get_replies_page()`, `iter_comments()`
175
+ - `get_comment_threads()`, `get_poll_results()`
176
+ - `watch_posts()`
177
+ - `login()`, `get_session()`
178
+ - `create_comment()`, `reply_to_comment()`, `delete_comment()`
179
+ - `toggle_reaction()`
180
+
181
+ models are frozen pydantic models. use `model_dump()` or `model_dump_json()` for serialization. unknown upstream fields are kept in `raw`
182
+
183
+ ## errors
184
+
185
+ all library errors inherit from `ZapostitError`
186
+
187
+ - `ApiError`
188
+ - `AuthenticationError`
189
+ - `RateLimitError`
190
+ - `DecodeError`
191
+ - `NotFoundError`
192
+
193
+ read requests use bounded retries for network errors, `429` and `5xx` responses
194
+
195
+ ## development
196
+
197
+ ```bash
198
+ ruff format --check src
199
+ ruff check src
200
+ mypy --strict src/zapostit
201
+ ```
202
+
203
+ ## license
204
+
205
+ [mit](LICENSE). commercial use, modification, distribution, sublicensing and private use are allowed
@@ -0,0 +1,187 @@
1
+ # zapostit-api
2
+
3
+ [русская версия](README.ru.md)
4
+
5
+ typed python client for zapostit.com with sync and async apis
6
+
7
+ it reads authors, posts, comments, replies, polls and media links. authenticated sessions can create comments, reply, toggle reactions and delete their own comments
8
+
9
+ ## installation
10
+
11
+ python 3.11 or newer is required
12
+
13
+ ```bash
14
+ pip install "zapostit-api @ git+ssh://git@github.com/yar2slav/zapostit-api.git@v0.2.0"
15
+ ```
16
+
17
+ local editable install:
18
+
19
+ ```bash
20
+ git clone git@github.com:yar2slav/zapostit-api.git
21
+ cd zapostit-api
22
+ pip install -e ".[dev]"
23
+ ```
24
+
25
+ ## basic usage
26
+
27
+ ```python
28
+ from zapostit import Zapostit
29
+
30
+
31
+ with Zapostit() as api:
32
+ author = api.get_author("slivach")
33
+ print(author.name, api.get_post_count(author.id))
34
+
35
+ for post in api.iter_posts(author.slug, max_posts=20):
36
+ print(post.created_at, post.url)
37
+ print(post.text_with_links)
38
+
39
+ for media in post.media:
40
+ print(media.kind, media.url)
41
+ ```
42
+
43
+ `post.text` contains the original text. `post.text_with_links` shows hidden hyperlink targets next to the linked text
44
+
45
+ ## pages and search
46
+
47
+ ```python
48
+ page = api.get_posts_page("slivach", limit=10)
49
+
50
+ for post in page.items:
51
+ print(post.id)
52
+
53
+ next_page = api.get_posts_page(
54
+ "slivach",
55
+ cursor=page.next_cursor,
56
+ limit=10,
57
+ )
58
+ ```
59
+
60
+ `iter_posts()` handles cursors and duplicate ids automatically
61
+
62
+ ```python
63
+ for post in api.search_posts("pozdnyakov", "Черногория"):
64
+ print(post.url, post.text_with_links)
65
+ ```
66
+
67
+ ## comments
68
+
69
+ ```python
70
+ for comment in api.iter_comments(post_id=104459, sort="newest"):
71
+ print(comment.author.name, comment.text_with_links)
72
+ ```
73
+
74
+ comments can also be returned as a tree:
75
+
76
+ ```python
77
+ threads = api.get_comment_threads(104459)
78
+
79
+ for thread in threads:
80
+ print(thread.comment.text)
81
+ for reply in thread.replies:
82
+ print(" ", reply.comment.text)
83
+ ```
84
+
85
+ ## media
86
+
87
+ the library returns public urls and metadata without downloading files
88
+
89
+ ```python
90
+ for media in post.media:
91
+ print(media.kind, media.url, media.thumbnail_url)
92
+
93
+ for variant in media.variants:
94
+ print(variant.type, variant.width, variant.height, variant.url)
95
+ ```
96
+
97
+ photos, albums, videos, voice messages, audio, documents and polls are supported
98
+
99
+ ## polling
100
+
101
+ ```python
102
+ with Zapostit() as api:
103
+ for post in api.watch_posts("slivach", interval=30):
104
+ print("new post:", post.url)
105
+ ```
106
+
107
+ the first request creates a baseline. pass `emit_existing=True` to receive the current first page immediately
108
+
109
+ ## authenticated actions
110
+
111
+ an existing session token can be passed through an environment variable:
112
+
113
+ ```python
114
+ import os
115
+
116
+ from zapostit import Zapostit
117
+
118
+
119
+ with Zapostit(session_token=os.environ["ZAPOSTIT_SESSION_TOKEN"]) as api:
120
+ print(api.get_session().user.login)
121
+
122
+ comment = api.create_comment(104459, "hello")
123
+ reply = api.reply_to_comment(104459, comment.id, "reply")
124
+
125
+ api.toggle_reaction(104459, "post", emoji_id=6)
126
+ api.toggle_reaction(comment.id, "comment", emoji_id=0)
127
+
128
+ api.delete_comment(reply.id)
129
+ ```
130
+
131
+ mutation requests are sent once and are not retried automatically
132
+
133
+ ## async usage
134
+
135
+ `AsyncZapostit` has the same method names. iterators become async iterators
136
+
137
+ ```python
138
+ import asyncio
139
+
140
+ from zapostit import AsyncZapostit
141
+
142
+
143
+ async def main() -> None:
144
+ async with AsyncZapostit() as api:
145
+ async for post in api.iter_posts("slivach", max_posts=20):
146
+ print(post.url)
147
+
148
+
149
+ asyncio.run(main())
150
+ ```
151
+
152
+ ## main methods
153
+
154
+ - `get_authors()`, `get_author()`, `get_post_count()`
155
+ - `get_posts_page()`, `iter_posts()`, `search_posts()`, `iter_all_posts()`
156
+ - `get_comments_page()`, `get_replies_page()`, `iter_comments()`
157
+ - `get_comment_threads()`, `get_poll_results()`
158
+ - `watch_posts()`
159
+ - `login()`, `get_session()`
160
+ - `create_comment()`, `reply_to_comment()`, `delete_comment()`
161
+ - `toggle_reaction()`
162
+
163
+ models are frozen pydantic models. use `model_dump()` or `model_dump_json()` for serialization. unknown upstream fields are kept in `raw`
164
+
165
+ ## errors
166
+
167
+ all library errors inherit from `ZapostitError`
168
+
169
+ - `ApiError`
170
+ - `AuthenticationError`
171
+ - `RateLimitError`
172
+ - `DecodeError`
173
+ - `NotFoundError`
174
+
175
+ read requests use bounded retries for network errors, `429` and `5xx` responses
176
+
177
+ ## development
178
+
179
+ ```bash
180
+ ruff format --check src
181
+ ruff check src
182
+ mypy --strict src/zapostit
183
+ ```
184
+
185
+ ## license
186
+
187
+ [mit](LICENSE). commercial use, modification, distribution, sublicensing and private use are allowed
@@ -0,0 +1,187 @@
1
+ # zapostit-api
2
+
3
+ [english version](README.md)
4
+
5
+ типизированный python-клиент для zapostit.com с синхронным и асинхронным api
6
+
7
+ умеет получать авторов, посты, комментарии, ответы, опросы и ссылки на медиа. после авторизации можно писать комментарии, отвечать, ставить реакции и удалять свои комментарии
8
+
9
+ ## установка
10
+
11
+ нужен python 3.11 или новее
12
+
13
+ ```bash
14
+ pip install "zapostit-api @ git+ssh://git@github.com/yar2slav/zapostit-api.git@v0.2.0"
15
+ ```
16
+
17
+ локальная установка для разработки:
18
+
19
+ ```bash
20
+ git clone git@github.com:yar2slav/zapostit-api.git
21
+ cd zapostit-api
22
+ pip install -e ".[dev]"
23
+ ```
24
+
25
+ ## базовый пример
26
+
27
+ ```python
28
+ from zapostit import Zapostit
29
+
30
+
31
+ with Zapostit() as api:
32
+ author = api.get_author("slivach")
33
+ print(author.name, api.get_post_count(author.id))
34
+
35
+ for post in api.iter_posts(author.slug, max_posts=20):
36
+ print(post.created_at, post.url)
37
+ print(post.text_with_links)
38
+
39
+ for media in post.media:
40
+ print(media.kind, media.url)
41
+ ```
42
+
43
+ `post.text` содержит исходный текст. `post.text_with_links` показывает адреса скрытых гиперссылок рядом с текстом, на котором они стоят
44
+
45
+ ## страницы и поиск
46
+
47
+ ```python
48
+ page = api.get_posts_page("slivach", limit=10)
49
+
50
+ for post in page.items:
51
+ print(post.id)
52
+
53
+ next_page = api.get_posts_page(
54
+ "slivach",
55
+ cursor=page.next_cursor,
56
+ limit=10,
57
+ )
58
+ ```
59
+
60
+ `iter_posts()` сам обрабатывает курсоры и убирает повторяющиеся id
61
+
62
+ ```python
63
+ for post in api.search_posts("pozdnyakov", "Черногория"):
64
+ print(post.url, post.text_with_links)
65
+ ```
66
+
67
+ ## комментарии
68
+
69
+ ```python
70
+ for comment in api.iter_comments(post_id=104459, sort="newest"):
71
+ print(comment.author.name, comment.text_with_links)
72
+ ```
73
+
74
+ комментарии можно получить готовым деревом:
75
+
76
+ ```python
77
+ threads = api.get_comment_threads(104459)
78
+
79
+ for thread in threads:
80
+ print(thread.comment.text)
81
+ for reply in thread.replies:
82
+ print(" ", reply.comment.text)
83
+ ```
84
+
85
+ ## медиа
86
+
87
+ библиотека возвращает публичные ссылки и метаданные без скачивания файлов
88
+
89
+ ```python
90
+ for media in post.media:
91
+ print(media.kind, media.url, media.thumbnail_url)
92
+
93
+ for variant in media.variants:
94
+ print(variant.type, variant.width, variant.height, variant.url)
95
+ ```
96
+
97
+ поддерживаются фото, альбомы, видео, голосовые сообщения, аудио, документы и опросы
98
+
99
+ ## поллинг
100
+
101
+ ```python
102
+ with Zapostit() as api:
103
+ for post in api.watch_posts("slivach", interval=30):
104
+ print("новый пост:", post.url)
105
+ ```
106
+
107
+ первый запрос создаёт исходную точку. `emit_existing=True` сразу вернёт текущую первую страницу
108
+
109
+ ## действия после авторизации
110
+
111
+ готовый токен сессии можно передать через переменную окружения:
112
+
113
+ ```python
114
+ import os
115
+
116
+ from zapostit import Zapostit
117
+
118
+
119
+ with Zapostit(session_token=os.environ["ZAPOSTIT_SESSION_TOKEN"]) as api:
120
+ print(api.get_session().user.login)
121
+
122
+ comment = api.create_comment(104459, "hello")
123
+ reply = api.reply_to_comment(104459, comment.id, "reply")
124
+
125
+ api.toggle_reaction(104459, "post", emoji_id=6)
126
+ api.toggle_reaction(comment.id, "comment", emoji_id=0)
127
+
128
+ api.delete_comment(reply.id)
129
+ ```
130
+
131
+ запросы на изменение данных отправляются один раз и автоматически не повторяются
132
+
133
+ ## асинхронное использование
134
+
135
+ `AsyncZapostit` использует те же названия методов. обычные итераторы становятся асинхронными
136
+
137
+ ```python
138
+ import asyncio
139
+
140
+ from zapostit import AsyncZapostit
141
+
142
+
143
+ async def main() -> None:
144
+ async with AsyncZapostit() as api:
145
+ async for post in api.iter_posts("slivach", max_posts=20):
146
+ print(post.url)
147
+
148
+
149
+ asyncio.run(main())
150
+ ```
151
+
152
+ ## основные методы
153
+
154
+ - `get_authors()`, `get_author()`, `get_post_count()`
155
+ - `get_posts_page()`, `iter_posts()`, `search_posts()`, `iter_all_posts()`
156
+ - `get_comments_page()`, `get_replies_page()`, `iter_comments()`
157
+ - `get_comment_threads()`, `get_poll_results()`
158
+ - `watch_posts()`
159
+ - `login()`, `get_session()`
160
+ - `create_comment()`, `reply_to_comment()`, `delete_comment()`
161
+ - `toggle_reaction()`
162
+
163
+ модели сделаны на pydantic и не изменяются после создания. для сериализации используются `model_dump()` и `model_dump_json()`. неизвестные поля ответа сохраняются в `raw`
164
+
165
+ ## ошибки
166
+
167
+ все ошибки библиотеки наследуются от `ZapostitError`
168
+
169
+ - `ApiError`
170
+ - `AuthenticationError`
171
+ - `RateLimitError`
172
+ - `DecodeError`
173
+ - `NotFoundError`
174
+
175
+ запросы на чтение ограниченно повторяются при сетевых ошибках, ответах `429` и `5xx`
176
+
177
+ ## разработка
178
+
179
+ ```bash
180
+ ruff format --check src
181
+ ruff check src
182
+ mypy --strict src/zapostit
183
+ ```
184
+
185
+ ## лицензия
186
+
187
+ [mit](LICENSE). разрешены коммерческое использование, изменение, распространение, сублицензирование и приватное использование
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zapostit-api"
7
+ version = "0.2.0"
8
+ description = "Typed Python client for zapostit.com"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "yaroslav" },
14
+ ]
15
+ dependencies = [
16
+ "httpx>=0.27,<1",
17
+ "pydantic>=2.7,<3",
18
+ ]
19
+
20
+ [project.urls]
21
+ Repository = "https://github.com/yar2slav/zapostit-api"
22
+ Documentation = "https://github.com/yar2slav/zapostit-api#readme"
23
+ Issues = "https://github.com/yar2slav/zapostit-api/issues"
24
+
25
+ [project.optional-dependencies]
26
+ dev = [
27
+ "mypy>=1.11",
28
+ "ruff>=0.6",
29
+ ]
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/zapostit"]
33
+
34
+ [tool.ruff]
35
+ target-version = "py311"
36
+ line-length = 100
37
+
38
+ [tool.ruff.lint]
39
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
40
+ allowed-confusables = ["с", "о"]
41
+
42
+ [tool.mypy]
43
+ python_version = "3.11"
44
+ strict = true
45
+ plugins = ["pydantic.mypy"]
46
+ packages = ["zapostit"]
47
+ mypy_path = "src"