notionary 0.2.26__py3-none-any.whl → 0.2.28__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.
Files changed (387) hide show
  1. notionary/__init__.py +5 -20
  2. notionary/blocks/client.py +87 -215
  3. notionary/blocks/enums.py +167 -0
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
  5. notionary/blocks/rich_text/models.py +164 -0
  6. notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
  7. notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
  8. notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
  9. notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
  10. notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
  11. notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
  12. notionary/blocks/rich_text/rich_text_patterns.py +39 -0
  13. notionary/blocks/schemas.py +746 -0
  14. notionary/comments/client.py +52 -187
  15. notionary/comments/factory.py +40 -0
  16. notionary/comments/models.py +5 -127
  17. notionary/comments/schemas.py +240 -0
  18. notionary/comments/service.py +34 -0
  19. notionary/data_source/http/client.py +11 -0
  20. notionary/data_source/http/data_source_instance_client.py +94 -0
  21. notionary/data_source/properties/models.py +406 -0
  22. notionary/data_source/query/builder.py +429 -0
  23. notionary/data_source/query/resolver.py +114 -0
  24. notionary/data_source/query/schema.py +304 -0
  25. notionary/data_source/query/validator.py +73 -0
  26. notionary/data_source/schemas.py +27 -0
  27. notionary/data_source/service.py +353 -0
  28. notionary/database/client.py +30 -135
  29. notionary/database/database_metadata_update_client.py +19 -0
  30. notionary/database/schemas.py +29 -0
  31. notionary/database/service.py +169 -0
  32. notionary/exceptions/__init__.py +33 -0
  33. notionary/exceptions/api.py +41 -0
  34. notionary/exceptions/base.py +2 -0
  35. notionary/exceptions/block_parsing.py +16 -0
  36. notionary/exceptions/data_source/__init__.py +6 -0
  37. notionary/exceptions/data_source/builder.py +182 -0
  38. notionary/exceptions/data_source/properties.py +34 -0
  39. notionary/exceptions/properties.py +58 -0
  40. notionary/exceptions/search.py +33 -0
  41. notionary/file_upload/client.py +18 -30
  42. notionary/file_upload/models.py +7 -8
  43. notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
  44. notionary/http/client.py +205 -0
  45. notionary/http/models.py +49 -0
  46. notionary/page/blocks/client.py +1 -0
  47. notionary/page/content/factory.py +68 -0
  48. notionary/page/content/markdown/__init__.py +5 -0
  49. notionary/page/content/markdown/builder.py +304 -0
  50. notionary/page/content/markdown/nodes/__init__.py +54 -0
  51. notionary/page/content/markdown/nodes/audio.py +23 -0
  52. notionary/page/content/markdown/nodes/base.py +12 -0
  53. notionary/page/content/markdown/nodes/bookmark.py +25 -0
  54. notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
  55. notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
  56. notionary/page/content/markdown/nodes/callout.py +32 -0
  57. notionary/page/content/markdown/nodes/code.py +30 -0
  58. notionary/page/content/markdown/nodes/columns.py +51 -0
  59. notionary/page/content/markdown/nodes/divider.py +14 -0
  60. notionary/page/content/markdown/nodes/embed.py +23 -0
  61. notionary/page/content/markdown/nodes/equation.py +19 -0
  62. notionary/page/content/markdown/nodes/file.py +23 -0
  63. notionary/page/content/markdown/nodes/heading.py +16 -0
  64. notionary/page/content/markdown/nodes/image.py +23 -0
  65. notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
  66. notionary/page/content/markdown/nodes/numbered_list.py +15 -0
  67. notionary/page/content/markdown/nodes/paragraph.py +14 -0
  68. notionary/page/content/markdown/nodes/pdf.py +23 -0
  69. notionary/page/content/markdown/nodes/quote.py +15 -0
  70. notionary/page/content/markdown/nodes/space.py +14 -0
  71. notionary/page/content/markdown/nodes/table.py +45 -0
  72. notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
  73. notionary/page/content/markdown/nodes/todo.py +22 -0
  74. notionary/page/content/markdown/nodes/toggle.py +28 -0
  75. notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
  76. notionary/page/content/markdown/nodes/video.py +23 -0
  77. notionary/page/content/parser/context.py +49 -0
  78. notionary/page/content/parser/factory.py +219 -0
  79. notionary/page/content/parser/parsers/__init__.py +60 -0
  80. notionary/page/content/parser/parsers/audio.py +40 -0
  81. notionary/page/content/parser/parsers/base.py +30 -0
  82. notionary/page/content/parser/parsers/bookmark.py +33 -0
  83. notionary/page/content/parser/parsers/breadcrumb.py +33 -0
  84. notionary/page/content/parser/parsers/bulleted_list.py +41 -0
  85. notionary/page/content/parser/parsers/callout.py +129 -0
  86. notionary/page/content/parser/parsers/caption.py +55 -0
  87. notionary/page/content/parser/parsers/code.py +81 -0
  88. notionary/page/content/parser/parsers/column.py +117 -0
  89. notionary/page/content/parser/parsers/column_list.py +81 -0
  90. notionary/page/content/parser/parsers/divider.py +33 -0
  91. notionary/page/content/parser/parsers/embed.py +33 -0
  92. notionary/page/content/parser/parsers/equation.py +65 -0
  93. notionary/page/content/parser/parsers/file.py +42 -0
  94. notionary/page/content/parser/parsers/heading.py +58 -0
  95. notionary/page/content/parser/parsers/image.py +42 -0
  96. notionary/page/content/parser/parsers/numbered_list.py +45 -0
  97. notionary/page/content/parser/parsers/paragraph.py +36 -0
  98. notionary/page/content/parser/parsers/pdf.py +42 -0
  99. notionary/page/content/parser/parsers/quote.py +65 -0
  100. notionary/page/content/parser/parsers/space.py +35 -0
  101. notionary/page/content/parser/parsers/table.py +144 -0
  102. notionary/page/content/parser/parsers/table_of_contents.py +32 -0
  103. notionary/page/content/parser/parsers/todo.py +58 -0
  104. notionary/page/content/parser/parsers/toggle.py +127 -0
  105. notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
  106. notionary/page/content/parser/parsers/video.py +42 -0
  107. notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
  108. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
  109. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
  110. notionary/page/content/parser/post_processing/port.py +9 -0
  111. notionary/page/content/parser/post_processing/service.py +16 -0
  112. notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
  113. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
  114. notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
  115. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
  116. notionary/page/content/parser/pre_processsing/service.py +15 -0
  117. notionary/page/content/parser/service.py +69 -0
  118. notionary/page/content/renderer/context.py +48 -0
  119. notionary/page/content/renderer/factory.py +240 -0
  120. notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
  121. notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
  122. notionary/page/content/renderer/post_processing/port.py +7 -0
  123. notionary/page/content/renderer/post_processing/service.py +15 -0
  124. notionary/page/content/renderer/renderers/__init__.py +57 -0
  125. notionary/page/content/renderer/renderers/audio.py +31 -0
  126. notionary/page/content/renderer/renderers/base.py +31 -0
  127. notionary/page/content/renderer/renderers/bookmark.py +25 -0
  128. notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
  129. notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
  130. notionary/page/content/renderer/renderers/callout.py +65 -0
  131. notionary/page/content/renderer/renderers/captioned_block.py +58 -0
  132. notionary/page/content/renderer/renderers/code.py +34 -0
  133. notionary/page/content/renderer/renderers/column.py +44 -0
  134. notionary/page/content/renderer/renderers/column_list.py +31 -0
  135. notionary/page/content/renderer/renderers/divider.py +22 -0
  136. notionary/page/content/renderer/renderers/embed.py +25 -0
  137. notionary/page/content/renderer/renderers/equation.py +37 -0
  138. notionary/page/content/renderer/renderers/fallback.py +24 -0
  139. notionary/page/content/renderer/renderers/file.py +40 -0
  140. notionary/page/content/renderer/renderers/heading.py +69 -0
  141. notionary/page/content/renderer/renderers/image.py +31 -0
  142. notionary/page/content/renderer/renderers/numbered_list.py +41 -0
  143. notionary/page/content/renderer/renderers/paragraph.py +40 -0
  144. notionary/page/content/renderer/renderers/pdf.py +31 -0
  145. notionary/page/content/renderer/renderers/quote.py +49 -0
  146. notionary/page/content/renderer/renderers/table.py +115 -0
  147. notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
  148. notionary/page/content/renderer/renderers/table_row.py +17 -0
  149. notionary/page/content/renderer/renderers/todo.py +56 -0
  150. notionary/page/content/renderer/renderers/toggle.py +53 -0
  151. notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
  152. notionary/page/content/renderer/renderers/video.py +31 -0
  153. notionary/page/content/renderer/service.py +50 -0
  154. notionary/page/content/service.py +65 -0
  155. notionary/page/content/syntax/models.py +68 -0
  156. notionary/page/content/syntax/service.py +453 -0
  157. notionary/page/page_context.py +7 -16
  158. notionary/page/page_http_client.py +15 -0
  159. notionary/page/page_metadata_update_client.py +19 -0
  160. notionary/page/properties/client.py +144 -0
  161. notionary/page/properties/factory.py +26 -0
  162. notionary/page/properties/models.py +307 -0
  163. notionary/page/properties/service.py +257 -0
  164. notionary/page/schemas.py +13 -0
  165. notionary/page/service.py +222 -0
  166. notionary/shared/entity/client.py +29 -0
  167. notionary/shared/entity/dto_parsers.py +53 -0
  168. notionary/shared/entity/entity_metadata_update_client.py +41 -0
  169. notionary/shared/entity/schemas.py +45 -0
  170. notionary/shared/entity/service.py +171 -0
  171. notionary/shared/models/cover.py +20 -0
  172. notionary/shared/models/file.py +21 -0
  173. notionary/shared/models/icon.py +28 -0
  174. notionary/shared/models/parent.py +41 -0
  175. notionary/shared/properties/type.py +30 -0
  176. notionary/user/__init__.py +4 -8
  177. notionary/user/base.py +89 -0
  178. notionary/user/bot.py +70 -0
  179. notionary/user/client.py +22 -111
  180. notionary/user/person.py +41 -0
  181. notionary/user/schemas.py +67 -0
  182. notionary/user/service.py +65 -0
  183. notionary/utils/async_retry.py +39 -0
  184. notionary/utils/date.py +51 -0
  185. notionary/utils/fuzzy.py +56 -0
  186. notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
  187. notionary/utils/pagination.py +50 -0
  188. notionary/utils/singleton.py +13 -0
  189. notionary/utils/uuid_utils.py +20 -0
  190. notionary/workspace/__init__.py +3 -0
  191. notionary/workspace/client.py +62 -0
  192. notionary/workspace/query/builder.py +60 -0
  193. notionary/workspace/query/models.py +60 -0
  194. notionary/workspace/query/service.py +93 -0
  195. notionary/workspace/schemas.py +21 -0
  196. notionary/workspace/service.py +116 -0
  197. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
  198. notionary-0.2.28.dist-info/RECORD +200 -0
  199. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
  200. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
  201. notionary/base_notion_client.py +0 -219
  202. notionary/blocks/__init__.py +0 -5
  203. notionary/blocks/_bootstrap.py +0 -271
  204. notionary/blocks/audio/__init__.py +0 -11
  205. notionary/blocks/audio/audio_element.py +0 -158
  206. notionary/blocks/audio/audio_markdown_node.py +0 -24
  207. notionary/blocks/audio/audio_models.py +0 -10
  208. notionary/blocks/base_block_element.py +0 -42
  209. notionary/blocks/bookmark/__init__.py +0 -12
  210. notionary/blocks/bookmark/bookmark_element.py +0 -83
  211. notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
  212. notionary/blocks/bookmark/bookmark_models.py +0 -15
  213. notionary/blocks/breadcrumbs/__init__.py +0 -15
  214. notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
  215. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
  216. notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
  217. notionary/blocks/bulleted_list/__init__.py +0 -15
  218. notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
  219. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
  220. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
  221. notionary/blocks/callout/__init__.py +0 -12
  222. notionary/blocks/callout/callout_element.py +0 -99
  223. notionary/blocks/callout/callout_markdown_node.py +0 -19
  224. notionary/blocks/callout/callout_models.py +0 -33
  225. notionary/blocks/child_database/__init__.py +0 -14
  226. notionary/blocks/child_database/child_database_element.py +0 -59
  227. notionary/blocks/child_database/child_database_models.py +0 -12
  228. notionary/blocks/child_page/__init__.py +0 -9
  229. notionary/blocks/child_page/child_page_element.py +0 -94
  230. notionary/blocks/child_page/child_page_models.py +0 -12
  231. notionary/blocks/code/__init__.py +0 -11
  232. notionary/blocks/code/code_element.py +0 -149
  233. notionary/blocks/code/code_markdown_node.py +0 -80
  234. notionary/blocks/code/code_models.py +0 -94
  235. notionary/blocks/column/__init__.py +0 -25
  236. notionary/blocks/column/column_element.py +0 -65
  237. notionary/blocks/column/column_list_element.py +0 -52
  238. notionary/blocks/column/column_list_markdown_node.py +0 -34
  239. notionary/blocks/column/column_markdown_node.py +0 -42
  240. notionary/blocks/column/column_models.py +0 -26
  241. notionary/blocks/divider/__init__.py +0 -12
  242. notionary/blocks/divider/divider_element.py +0 -41
  243. notionary/blocks/divider/divider_markdown_node.py +0 -11
  244. notionary/blocks/divider/divider_models.py +0 -12
  245. notionary/blocks/embed/__init__.py +0 -12
  246. notionary/blocks/embed/embed_element.py +0 -98
  247. notionary/blocks/embed/embed_markdown_node.py +0 -19
  248. notionary/blocks/embed/embed_models.py +0 -14
  249. notionary/blocks/equation/__init__.py +0 -13
  250. notionary/blocks/equation/equation_element.py +0 -133
  251. notionary/blocks/equation/equation_element_markdown_node.py +0 -23
  252. notionary/blocks/equation/equation_models.py +0 -11
  253. notionary/blocks/file/__init__.py +0 -23
  254. notionary/blocks/file/file_element.py +0 -133
  255. notionary/blocks/file/file_element_markdown_node.py +0 -24
  256. notionary/blocks/file/file_element_models.py +0 -39
  257. notionary/blocks/heading/__init__.py +0 -19
  258. notionary/blocks/heading/heading_element.py +0 -112
  259. notionary/blocks/heading/heading_markdown_node.py +0 -16
  260. notionary/blocks/heading/heading_models.py +0 -29
  261. notionary/blocks/image_block/__init__.py +0 -11
  262. notionary/blocks/image_block/image_element.py +0 -130
  263. notionary/blocks/image_block/image_markdown_node.py +0 -25
  264. notionary/blocks/image_block/image_models.py +0 -10
  265. notionary/blocks/markdown/markdown_builder.py +0 -525
  266. notionary/blocks/markdown/markdown_document_model.py +0 -0
  267. notionary/blocks/markdown/markdown_node.py +0 -25
  268. notionary/blocks/mixins/captions/__init__.py +0 -4
  269. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
  270. notionary/blocks/mixins/captions/caption_mixin.py +0 -92
  271. notionary/blocks/mixins/file_upload/__init__.py +0 -3
  272. notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
  273. notionary/blocks/models.py +0 -174
  274. notionary/blocks/numbered_list/__init__.py +0 -16
  275. notionary/blocks/numbered_list/numbered_list_element.py +0 -65
  276. notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
  277. notionary/blocks/numbered_list/numbered_list_models.py +0 -17
  278. notionary/blocks/paragraph/__init__.py +0 -15
  279. notionary/blocks/paragraph/paragraph_element.py +0 -58
  280. notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
  281. notionary/blocks/paragraph/paragraph_models.py +0 -16
  282. notionary/blocks/pdf/__init__.py +0 -11
  283. notionary/blocks/pdf/pdf_element.py +0 -146
  284. notionary/blocks/pdf/pdf_markdown_node.py +0 -24
  285. notionary/blocks/pdf/pdf_models.py +0 -11
  286. notionary/blocks/quote/__init__.py +0 -14
  287. notionary/blocks/quote/quote_element.py +0 -75
  288. notionary/blocks/quote/quote_markdown_node.py +0 -16
  289. notionary/blocks/quote/quote_models.py +0 -18
  290. notionary/blocks/registry/__init__.py +0 -3
  291. notionary/blocks/registry/block_registry.py +0 -150
  292. notionary/blocks/rich_text/__init__.py +0 -33
  293. notionary/blocks/rich_text/rich_text_models.py +0 -221
  294. notionary/blocks/rich_text/text_inline_formatter.py +0 -456
  295. notionary/blocks/syntax_prompt_builder.py +0 -137
  296. notionary/blocks/table/__init__.py +0 -19
  297. notionary/blocks/table/table_element.py +0 -225
  298. notionary/blocks/table/table_markdown_node.py +0 -42
  299. notionary/blocks/table/table_models.py +0 -28
  300. notionary/blocks/table_of_contents/__init__.py +0 -17
  301. notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
  302. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
  303. notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
  304. notionary/blocks/todo/__init__.py +0 -12
  305. notionary/blocks/todo/todo_element.py +0 -81
  306. notionary/blocks/todo/todo_markdown_node.py +0 -21
  307. notionary/blocks/todo/todo_models.py +0 -18
  308. notionary/blocks/toggle/__init__.py +0 -12
  309. notionary/blocks/toggle/toggle_element.py +0 -112
  310. notionary/blocks/toggle/toggle_markdown_node.py +0 -31
  311. notionary/blocks/toggle/toggle_models.py +0 -17
  312. notionary/blocks/toggleable_heading/__init__.py +0 -11
  313. notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
  314. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
  315. notionary/blocks/types.py +0 -130
  316. notionary/blocks/video/__init__.py +0 -11
  317. notionary/blocks/video/video_element.py +0 -187
  318. notionary/blocks/video/video_element_models.py +0 -10
  319. notionary/blocks/video/video_markdown_node.py +0 -26
  320. notionary/comments/__init__.py +0 -26
  321. notionary/database/__init__.py +0 -4
  322. notionary/database/database.py +0 -480
  323. notionary/database/database_filter_builder.py +0 -173
  324. notionary/database/database_provider.py +0 -227
  325. notionary/database/exceptions.py +0 -13
  326. notionary/database/factory.py +0 -0
  327. notionary/database/models.py +0 -337
  328. notionary/database/notion_database.py +0 -487
  329. notionary/file_upload/__init__.py +0 -7
  330. notionary/page/client.py +0 -124
  331. notionary/page/markdown_whitespace_processor.py +0 -129
  332. notionary/page/models.py +0 -322
  333. notionary/page/notion_page.py +0 -674
  334. notionary/page/page_content_deleting_service.py +0 -117
  335. notionary/page/page_content_writer.py +0 -80
  336. notionary/page/property_formatter.py +0 -99
  337. notionary/page/reader/handler/__init__.py +0 -19
  338. notionary/page/reader/handler/base_block_renderer.py +0 -44
  339. notionary/page/reader/handler/block_processing_context.py +0 -35
  340. notionary/page/reader/handler/block_rendering_context.py +0 -48
  341. notionary/page/reader/handler/column_list_renderer.py +0 -51
  342. notionary/page/reader/handler/column_renderer.py +0 -60
  343. notionary/page/reader/handler/equation_renderer.py +0 -0
  344. notionary/page/reader/handler/line_renderer.py +0 -73
  345. notionary/page/reader/handler/numbered_list_renderer.py +0 -85
  346. notionary/page/reader/handler/toggle_renderer.py +0 -69
  347. notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
  348. notionary/page/reader/page_content_retriever.py +0 -81
  349. notionary/page/search_filter_builder.py +0 -132
  350. notionary/page/utils.py +0 -60
  351. notionary/page/writer/handler/__init__.py +0 -24
  352. notionary/page/writer/handler/code_handler.py +0 -72
  353. notionary/page/writer/handler/column_handler.py +0 -141
  354. notionary/page/writer/handler/column_list_handler.py +0 -139
  355. notionary/page/writer/handler/equation_handler.py +0 -74
  356. notionary/page/writer/handler/line_handler.py +0 -35
  357. notionary/page/writer/handler/line_processing_context.py +0 -54
  358. notionary/page/writer/handler/regular_line_handler.py +0 -86
  359. notionary/page/writer/handler/table_handler.py +0 -66
  360. notionary/page/writer/handler/toggle_handler.py +0 -159
  361. notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
  362. notionary/page/writer/markdown_to_notion_converter.py +0 -139
  363. notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
  364. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  365. notionary/page/writer/notion_text_length_processor.py +0 -150
  366. notionary/schemas/__init__.py +0 -3
  367. notionary/schemas/base.py +0 -73
  368. notionary/shared/__init__.py +0 -3
  369. notionary/shared/name_to_id_resolver.py +0 -203
  370. notionary/telemetry/__init__.py +0 -19
  371. notionary/telemetry/service.py +0 -136
  372. notionary/telemetry/views.py +0 -73
  373. notionary/user/base_notion_user.py +0 -53
  374. notionary/user/models.py +0 -84
  375. notionary/user/notion_bot_user.py +0 -226
  376. notionary/user/notion_user.py +0 -255
  377. notionary/user/notion_user_manager.py +0 -101
  378. notionary/util/__init__.py +0 -15
  379. notionary/util/concurrency_limiter.py +0 -0
  380. notionary/util/factory_decorator.py +0 -0
  381. notionary/util/factory_only.py +0 -37
  382. notionary/util/fuzzy.py +0 -75
  383. notionary/util/page_id_utils.py +0 -27
  384. notionary/util/singleton.py +0 -18
  385. notionary/util/singleton_metaclass.py +0 -22
  386. notionary/workspace.py +0 -105
  387. notionary-0.2.26.dist-info/RECORD +0 -202
@@ -1,211 +1,76 @@
1
- from __future__ import annotations
1
+ from collections.abc import AsyncGenerator
2
2
 
3
- from typing import Any, AsyncGenerator, Optional
3
+ from notionary.blocks.rich_text.models import RichText
4
+ from notionary.comments.schemas import (
5
+ CommentCreateRequest,
6
+ CommentDto,
7
+ CommentListRequest,
8
+ CommentListResponse,
9
+ )
10
+ from notionary.http.client import NotionHttpClient
11
+ from notionary.utils.pagination import paginate_notion_api, paginate_notion_api_generator
4
12
 
5
- from notionary.base_notion_client import BaseNotionClient
6
- from notionary.blocks.rich_text.rich_text_models import RichTextObject
7
- from notionary.comments.models import Comment, CommentListResponse
8
13
 
14
+ class CommentClient(NotionHttpClient):
15
+ def __init__(self) -> None:
16
+ super().__init__()
9
17
 
10
- class CommentClient(BaseNotionClient):
11
- """
12
- Client for Notion comment operations.
13
- Uses Pydantic models for typed responses.
14
-
15
- Notes / API constraints:
16
- - Listing returns only *unresolved* comments. Resolved comments are not returned.
17
- - You can create:
18
- 1) a top-level comment on a page
19
- 2) a reply in an existing discussion (requires discussion_id)
20
- You cannot start a brand-new inline thread via API.
21
- - Read/Insert comment capabilities must be enabled for the integration.
22
- """
23
-
24
- async def retrieve_comment(self, comment_id: str) -> Comment:
25
- """
26
- Retrieve a single Comment object by its ID.
27
-
28
- Requires the integration to have "Read comment" capability enabled.
29
- Raises 403 (restricted_resource) without it.
30
- """
31
- resp = await self.get(f"comments/{comment_id}")
32
- if resp is None:
33
- raise RuntimeError("Failed to retrieve comment.")
34
- return Comment.model_validate(resp)
35
-
36
- async def list_all_comments_for_page(
37
- self, *, page_id: str, page_size: int = 100
38
- ) -> list[Comment]:
39
- """Returns all unresolved comments for a page (handles pagination)."""
40
- results: list[Comment] = []
41
- cursor: str | None = None
42
- while True:
43
- page = await self.list_comments(
44
- block_id=page_id, start_cursor=cursor, page_size=page_size
45
- )
46
- results.extend(page.results)
47
- if not page.has_more:
48
- break
49
- cursor = page.next_cursor
50
- return results
51
-
52
- async def list_comments(
18
+ async def iter_comments(
53
19
  self,
54
- *,
55
20
  block_id: str,
56
- start_cursor: Optional[str] = None,
57
- page_size: Optional[int] = None,
58
- ) -> CommentListResponse:
21
+ *,
22
+ page_size: int = 100,
23
+ ) -> AsyncGenerator[CommentDto]:
59
24
  """
60
- List unresolved comments for a page or block.
61
-
62
- Args:
63
- block_id: Page ID or block ID to list comments for.
64
- start_cursor: Pagination cursor.
65
- page_size: Max items per page (<= 100).
66
-
67
- Returns:
68
- CommentListResponse with results, next_cursor, has_more, etc.
25
+ Iterates through all comments for a block, yielding each comment individually.
26
+ Uses pagination to handle large result sets efficiently without loading everything into memory.
69
27
  """
70
- params: dict[str, str | int] = {"block_id": block_id}
71
- if start_cursor:
72
- params["start_cursor"] = start_cursor
73
- if page_size:
74
- params["page_size"] = page_size
28
+ async for comment in paginate_notion_api_generator(
29
+ self._list_comments_page, block_id=block_id, page_size=page_size
30
+ ):
31
+ yield comment
75
32
 
76
- resp = await self.get("comments", params=params)
77
- if resp is None:
78
- raise RuntimeError("Failed to list comments.")
79
- return CommentListResponse.model_validate(resp)
33
+ async def get_all_comments(self, block_id: str, *, page_size: int = 100) -> list[CommentDto]:
34
+ all_comments = await paginate_notion_api(self._list_comments_page, block_id=block_id, page_size=page_size)
80
35
 
81
- async def iter_comments(
36
+ self.logger.debug("Retrieved %d total comments for block %s", len(all_comments), block_id)
37
+ return all_comments
38
+
39
+ async def _list_comments_page(
82
40
  self,
83
- *,
84
41
  block_id: str,
42
+ *,
43
+ start_cursor: str | None = None,
85
44
  page_size: int = 100,
86
- ) -> AsyncGenerator[Comment, None]:
87
- """
88
- Async generator over all unresolved comments for a given page/block.
89
- Handles pagination under the hood.
90
- """
91
- cursor: Optional[str] = None
92
- while True:
93
- page = await self.list_comments(
94
- block_id=block_id, start_cursor=cursor, page_size=page_size
95
- )
96
- for item in page.results:
97
- yield item
98
- if not page.has_more:
99
- break
100
- cursor = page.next_cursor
45
+ ) -> CommentListResponse:
46
+ request = CommentListRequest(
47
+ block_id=block_id,
48
+ start_cursor=start_cursor,
49
+ page_size=page_size,
50
+ )
51
+ resp = await self.get("comments", params=request.model_dump())
52
+ return CommentListResponse.model_validate(resp)
101
53
 
102
- async def create_comment_on_page(
54
+ async def create_comment_for_page(
103
55
  self,
104
- *,
56
+ rich_text: list[RichText],
105
57
  page_id: str,
106
- text: str,
107
- display_name: Optional[dict] = None,
108
- attachments: Optional[list[dict]] = None,
109
- ) -> Comment:
110
- """
111
- Create a top-level comment on a page.
58
+ ) -> CommentDto:
59
+ request = CommentCreateRequest.for_page(page_id=page_id, rich_text=rich_text)
112
60
 
113
- Args:
114
- page_id: Target page ID.
115
- text: Plain text content for the comment (rich_text will be constructed).
116
- display_name: Optional "Comment Display Name" object to override author label.
117
- attachments: Optional list of "Comment Attachment" objects (max 3).
118
-
119
- Returns:
120
- The created Comment object.
121
- """
122
- body: dict = {
123
- "parent": {"page_id": page_id},
124
- "rich_text": [{"type": "text", "text": {"content": text}}],
125
- }
126
- if display_name:
127
- body["display_name"] = display_name
128
- if attachments:
129
- body["attachments"] = attachments
61
+ body = request.model_dump(exclude_unset=True, exclude_none=True)
130
62
 
131
63
  resp = await self.post("comments", data=body)
132
- if resp is None:
133
- raise RuntimeError("Failed to create page comment.")
134
- return Comment.model_validate(resp)
64
+ return CommentDto.model_validate(resp)
135
65
 
136
- async def create_comment(
66
+ async def create_comment_for_discussion(
137
67
  self,
138
- *,
139
- page_id: Optional[str] = None,
140
- discussion_id: Optional[str] = None,
141
- content: Optional[str] = None,
142
- rich_text: Optional[list[RichTextObject]] = None,
143
- display_name: Optional[dict[str, Any]] = None,
144
- attachments: Optional[list[dict[str, Any]]] = None,
145
- ) -> Comment:
146
- """
147
- Create a comment on a page OR reply to an existing discussion.
148
-
149
- Rules:
150
- - Exactly one of page_id or discussion_id must be provided.
151
- - Provide either rich_text OR content (plain text). If both given, rich_text wins.
152
- - Up to 3 attachments allowed by Notion.
153
- """
154
- # validate parent
155
- if (page_id is None) == (discussion_id is None):
156
- raise ValueError("Specify exactly one parent: page_id OR discussion_id")
157
-
158
- # build rich_text if only content is provided
159
- rt = rich_text if rich_text else None
160
- if rt is None:
161
- if not content:
162
- raise ValueError("Provide either 'rich_text' or 'content'.")
163
- rt = [{"type": "text", "text": {"content": content}}]
164
-
165
- body: dict[str, Any] = {"rich_text": rt}
166
- if page_id:
167
- body["parent"] = {"page_id": page_id}
168
- else:
169
- body["discussion_id"] = discussion_id
68
+ rich_text: list[RichText],
69
+ discussion_id: str,
70
+ ) -> CommentDto:
71
+ request = CommentCreateRequest.for_discussion(discussion_id=discussion_id, rich_text=rich_text)
170
72
 
171
- if display_name:
172
- body["display_name"] = display_name
173
- if attachments:
174
- body["attachments"] = attachments
73
+ body = request.model_dump(exclude_unset=True, exclude_none=True)
175
74
 
176
75
  resp = await self.post("comments", data=body)
177
- if resp is None:
178
- raise RuntimeError("Failed to create comment.")
179
- return Comment.model_validate(resp)
180
-
181
- # ---------- Convenience wrappers ----------
182
-
183
- async def create_comment_on_page(
184
- self,
185
- *,
186
- page_id: str,
187
- text: str,
188
- display_name: Optional[dict] = None,
189
- attachments: Optional[list[dict]] = None,
190
- ) -> Comment:
191
- return await self.create_comment(
192
- page_id=page_id,
193
- content=text,
194
- display_name=display_name,
195
- attachments=attachments,
196
- )
197
-
198
- async def reply_to_discussion(
199
- self,
200
- *,
201
- discussion_id: str,
202
- text: str,
203
- display_name: Optional[dict] = None,
204
- attachments: Optional[list[dict]] = None,
205
- ) -> Comment:
206
- return await self.create_comment(
207
- discussion_id=discussion_id,
208
- content=text,
209
- display_name=display_name,
210
- attachments=attachments,
211
- )
76
+ return CommentDto.model_validate(resp)
@@ -0,0 +1,40 @@
1
+ import asyncio
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.comments.models import Comment
5
+ from notionary.comments.schemas import CommentDto
6
+ from notionary.user.client import UserHttpClient
7
+ from notionary.user.person import PersonUser
8
+ from notionary.utils.mixins.logging import LoggingMixin
9
+
10
+
11
+ class CommentFactory(LoggingMixin):
12
+ UNKNOWN_AUTHOR = "Unknown Author"
13
+
14
+ def __init__(
15
+ self,
16
+ http_client: UserHttpClient | None = None,
17
+ markdown_converter: RichTextToMarkdownConverter | None = None,
18
+ ) -> None:
19
+ self.http_client = http_client
20
+ self.markdown_converter = markdown_converter or RichTextToMarkdownConverter()
21
+
22
+ async def create_from_dto(self, dto: CommentDto) -> Comment:
23
+ author_name, content = await asyncio.gather(self._resolve_user_name(dto), self._resolve_content(dto))
24
+
25
+ return Comment(author_name=author_name, content=content)
26
+
27
+ async def _resolve_user_name(self, dto: CommentDto) -> str:
28
+ user_id = dto.created_by.id
29
+
30
+ try:
31
+ person = await PersonUser.from_id(user_id, self.http_client)
32
+ if person and person.name:
33
+ return person.name
34
+ except Exception:
35
+ self.logger.warning(f"Failed to resolve user name for user_id: {user_id}", exc_info=True)
36
+
37
+ return self.UNKNOWN_AUTHOR
38
+
39
+ async def _resolve_content(self, dto: CommentDto) -> str:
40
+ return await self.markdown_converter.to_markdown(dto.rich_text)
@@ -1,129 +1,7 @@
1
- # notionary/comments/models.py
2
- from __future__ import annotations
1
+ from dataclasses import dataclass
3
2
 
4
- from datetime import datetime
5
- from typing import Literal, Optional, Union
6
3
 
7
- from pydantic import BaseModel, Field, ConfigDict
8
-
9
- from notionary.blocks.rich_text import RichTextObject
10
-
11
-
12
- class UserRef(BaseModel):
13
- """Minimal Notion user reference."""
14
-
15
- model_config = ConfigDict(extra="ignore")
16
- object: Literal["user"] = "user"
17
- id: str
18
-
19
-
20
- class CommentParent(BaseModel):
21
- """
22
- Parent of a comment. Can be page_id or block_id.
23
- Notion responds with the active one; the other remains None.
24
- """
25
-
26
- model_config = ConfigDict(extra="ignore")
27
- type: Literal["page_id", "block_id"]
28
- page_id: Optional[str] = None
29
- block_id: Optional[str] = None
30
-
31
-
32
- class FileWithExpiry(BaseModel):
33
- """File object with temporary URL (common Notion pattern)."""
34
-
35
- model_config = ConfigDict(extra="ignore")
36
- url: str
37
- expiry_time: Optional[datetime] = None
38
-
39
-
40
- class CommentAttachmentFile(BaseModel):
41
- """Attachment stored by Notion with expiring download URL."""
42
-
43
- model_config = ConfigDict(extra="ignore")
44
- type: Literal["file"] = "file"
45
- name: Optional[str] = None
46
- file: FileWithExpiry
47
-
48
-
49
- class CommentAttachmentExternal(BaseModel):
50
- """External attachment referenced by URL."""
51
-
52
- model_config = ConfigDict(extra="ignore")
53
- type: Literal["external"] = "external"
54
- name: Optional[str] = None
55
- external: dict # {"url": "..."} – kept generic
56
-
57
-
58
- CommentAttachment = Union[CommentAttachmentFile, CommentAttachmentExternal]
59
-
60
-
61
- # ---------------------------
62
- # Display name override (optional)
63
- # ---------------------------
64
-
65
-
66
- class CommentDisplayName(BaseModel):
67
- """
68
- Optional display name override for comments created by an integration.
69
- Example: {"type": "integration", "resolved_name": "int"}.
70
- """
71
-
72
- model_config = ConfigDict(extra="ignore")
73
- type: str
74
- resolved_name: Optional[str] = None
75
-
76
-
77
- # ---------------------------
78
- # Core Comment object
79
- # ---------------------------
80
-
81
-
82
- class Comment(BaseModel):
83
- """
84
- Notion Comment object as returned by:
85
- - GET /v1/comments/{comment_id} (retrieve)
86
- - GET /v1/comments?block_id=... (list -> in results[])
87
- - POST /v1/comments (create)
88
- """
89
-
90
- model_config = ConfigDict(extra="ignore")
91
-
92
- object: Literal["comment"] = "comment"
93
- id: str
94
-
95
- parent: CommentParent
96
- discussion_id: str
97
-
98
- created_time: datetime
99
- last_edited_time: datetime
100
-
101
- created_by: UserRef
102
-
103
- rich_text: list[RichTextObject] = Field(default_factory=list)
104
-
105
- # Optional fields that may appear depending on capabilities/payload
106
- display_name: Optional[CommentDisplayName] = None
107
- attachments: Optional[list[CommentAttachment]] = None
108
-
109
-
110
- # ---------------------------
111
- # List envelope (for list-comments)
112
- # ---------------------------
113
-
114
-
115
- class CommentListResponse(BaseModel):
116
- """
117
- Envelope for GET /v1/comments?block_id=...
118
- """
119
-
120
- model_config = ConfigDict(extra="ignore")
121
-
122
- object: Literal["list"] = "list"
123
- results: list[Comment] = Field(default_factory=list)
124
- next_cursor: Optional[str] = None
125
- has_more: bool = False
126
-
127
- # Notion includes these two fields on the list envelope.
128
- type: Optional[Literal["comment"]] = None
129
- comment: Optional[dict] = None
4
+ @dataclass
5
+ class Comment:
6
+ author_name: str
7
+ content: str
@@ -0,0 +1,240 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from notionary.blocks.rich_text.models import RichText
10
+
11
+ # ---------------------------
12
+ # Comment Parent
13
+ # ---------------------------
14
+
15
+
16
+ class CommentParentType(StrEnum):
17
+ PAGE_ID = "page_id"
18
+ BLOCK_ID = "block_id"
19
+
20
+
21
+ class PageCommentParent(BaseModel):
22
+ type: Literal[CommentParentType.PAGE_ID] = CommentParentType.PAGE_ID
23
+ page_id: str
24
+
25
+
26
+ class BlockCommentParent(BaseModel):
27
+ type: Literal[CommentParentType.BLOCK_ID] = CommentParentType.BLOCK_ID
28
+ block_id: str
29
+
30
+
31
+ CommentParent = PageCommentParent | BlockCommentParent
32
+
33
+
34
+ # ---------------------------
35
+ # Comment Attachment (Response/DTO)
36
+ # ---------------------------
37
+
38
+
39
+ class CommentAttachmentCategory(StrEnum):
40
+ AUDIO = "audio"
41
+ IMAGE = "image"
42
+ PDF = "pdf"
43
+ PRODUCTIVITY = "productivity"
44
+ VIDEO = "video"
45
+
46
+
47
+ class FileWithExpiry(BaseModel):
48
+ url: str
49
+ expiry_time: datetime
50
+
51
+
52
+ class CommentAttachmentDto(BaseModel):
53
+ category: CommentAttachmentCategory
54
+ file: FileWithExpiry
55
+
56
+
57
+ # ---------------------------
58
+ # Comment Attachment (Request/Input)
59
+ # ---------------------------
60
+
61
+
62
+ class CommentAttachmentFileUploadType(StrEnum):
63
+ FILE_UPLOAD = "file_upload"
64
+
65
+
66
+ class CommentAttachmentInput(BaseModel):
67
+ file_upload_id: str
68
+ type: Literal[CommentAttachmentFileUploadType.FILE_UPLOAD] = CommentAttachmentFileUploadType.FILE_UPLOAD
69
+
70
+
71
+ # ---------------------------
72
+ # Comment Display Name
73
+ # ---------------------------
74
+
75
+
76
+ class CommentDisplayNameType(StrEnum):
77
+ INTEGRATION = "integration"
78
+ USER = "user"
79
+ CUSTOM = "custom"
80
+
81
+
82
+ class CustomDisplayName(BaseModel):
83
+ name: str
84
+
85
+
86
+ class IntegrationDisplayName(BaseModel):
87
+ type: Literal[CommentDisplayNameType.INTEGRATION] = CommentDisplayNameType.INTEGRATION
88
+
89
+
90
+ class UserDisplayName(BaseModel):
91
+ type: Literal[CommentDisplayNameType.USER] = CommentDisplayNameType.USER
92
+
93
+
94
+ class CustomCommentDisplayName(BaseModel):
95
+ type: Literal[CommentDisplayNameType.CUSTOM] = CommentDisplayNameType.CUSTOM
96
+ custom: CustomDisplayName
97
+
98
+
99
+ CommentDisplayNameInput = IntegrationDisplayName | UserDisplayName | CustomCommentDisplayName
100
+
101
+
102
+ class CommentDisplayNameDto(BaseModel):
103
+ type: CommentDisplayNameType
104
+ resolved_name: str
105
+
106
+
107
+ # ---------------------------
108
+ # Comment Create Request
109
+ # ---------------------------
110
+
111
+
112
+ class CommentCreateRequest(BaseModel):
113
+ rich_text: list[RichText]
114
+ parent: CommentParent | None = None
115
+ discussion_id: str | None = None
116
+ display_name: CommentDisplayNameInput | None = None
117
+ attachments: list[CommentAttachmentInput] | None = None
118
+
119
+ @classmethod
120
+ def for_page(
121
+ cls,
122
+ page_id: str,
123
+ rich_text: list[RichText],
124
+ display_name: CommentDisplayNameInput | None = None,
125
+ attachments: list[CommentAttachmentInput] | None = None,
126
+ ) -> CommentCreateRequest:
127
+ return cls(
128
+ rich_text=rich_text,
129
+ parent=PageCommentParent(page_id=page_id),
130
+ display_name=display_name,
131
+ attachments=attachments,
132
+ )
133
+
134
+ @classmethod
135
+ def for_block(
136
+ cls,
137
+ block_id: str,
138
+ rich_text: list[RichText],
139
+ display_name: CommentDisplayNameInput | None = None,
140
+ attachments: list[CommentAttachmentInput] | None = None,
141
+ ) -> CommentCreateRequest:
142
+ return cls(
143
+ rich_text=rich_text,
144
+ parent=BlockCommentParent(block_id=block_id),
145
+ display_name=display_name,
146
+ attachments=attachments,
147
+ )
148
+
149
+ @classmethod
150
+ def for_discussion(
151
+ cls,
152
+ discussion_id: str,
153
+ rich_text: list[RichText],
154
+ display_name: CommentDisplayNameInput | None = None,
155
+ attachments: list[CommentAttachmentInput] | None = None,
156
+ ) -> CommentCreateRequest:
157
+ return cls(
158
+ rich_text=rich_text,
159
+ discussion_id=discussion_id,
160
+ display_name=display_name,
161
+ attachments=attachments,
162
+ )
163
+
164
+
165
+ # ---------------------------
166
+ # Comment List Request
167
+ # ---------------------------
168
+
169
+
170
+ class CommentListRequest(BaseModel):
171
+ block_id: str
172
+ start_cursor: str | None = None
173
+ page_size: int | None = None
174
+
175
+
176
+ # ---------------------------
177
+ # User Reference
178
+ # ---------------------------
179
+
180
+
181
+ class UserRef(BaseModel):
182
+ object: Literal["user"] = "user"
183
+ id: str
184
+
185
+
186
+ # ---------------------------
187
+ # Comment DTO (Response)
188
+ # ---------------------------
189
+
190
+
191
+ class CommentDto(BaseModel):
192
+ """Comment object as returned by the API"""
193
+
194
+ object: Literal["comment"] = "comment"
195
+ id: str
196
+
197
+ parent: CommentParent
198
+ discussion_id: str
199
+
200
+ created_time: datetime
201
+ last_edited_time: datetime
202
+
203
+ created_by: UserRef
204
+
205
+ rich_text: list[RichText] = Field(default_factory=list)
206
+ attachments: list[CommentAttachmentDto] = Field(default_factory=list)
207
+ display_name: CommentDisplayNameDto | None = None
208
+
209
+
210
+ # ---------------------------
211
+ # List Response
212
+ # ---------------------------
213
+
214
+
215
+ class CommentListResponse(BaseModel):
216
+ object: Literal["list"] = "list"
217
+ results: list[CommentDto] = Field(default_factory=list)
218
+ next_cursor: str | None = None
219
+ has_more: bool = False
220
+
221
+
222
+ # ---------------------------
223
+ # Convenience Builders
224
+ # ---------------------------
225
+
226
+
227
+ class CommentDisplayNameBuilder:
228
+ """Helper class to build display names easily"""
229
+
230
+ @staticmethod
231
+ def integration() -> IntegrationDisplayName:
232
+ return IntegrationDisplayName()
233
+
234
+ @staticmethod
235
+ def user() -> UserDisplayName:
236
+ return UserDisplayName()
237
+
238
+ @staticmethod
239
+ def custom(name: str) -> CustomCommentDisplayName:
240
+ return CustomCommentDisplayName(custom=CustomDisplayName(name=name))