notionary 0.2.22__tar.gz → 0.2.23__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.
Files changed (204) hide show
  1. {notionary-0.2.22 → notionary-0.2.23}/PKG-INFO +1 -3
  2. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/child_database/child_database_element.py +2 -4
  3. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/rich_text/text_inline_formatter.py +1 -1
  4. notionary-0.2.23/notionary/comments/__init__.py +26 -0
  5. notionary-0.2.23/notionary/comments/client.py +211 -0
  6. notionary-0.2.23/notionary/comments/models.py +129 -0
  7. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/client.py +1 -6
  8. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/notion_page.py +55 -11
  9. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/page_context.py +0 -1
  10. notionary-0.2.23/notionary/shared/__init__.py +5 -0
  11. {notionary-0.2.22/notionary/blocks/rich_text → notionary-0.2.23/notionary/shared}/name_to_id_resolver.py +0 -2
  12. {notionary-0.2.22 → notionary-0.2.23}/pyproject.toml +2 -3
  13. {notionary-0.2.22 → notionary-0.2.23}/LICENSE +0 -0
  14. {notionary-0.2.22 → notionary-0.2.23}/README.md +0 -0
  15. {notionary-0.2.22 → notionary-0.2.23}/notionary/__init__.py +0 -0
  16. {notionary-0.2.22 → notionary-0.2.23}/notionary/base_notion_client.py +0 -0
  17. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/__init__.py +0 -0
  18. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/_bootstrap.py +0 -0
  19. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/audio/__init__.py +0 -0
  20. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/audio/audio_element.py +0 -0
  21. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/audio/audio_markdown_node.py +0 -0
  22. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/audio/audio_models.py +0 -0
  23. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/base_block_element.py +0 -0
  24. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bookmark/__init__.py +0 -0
  25. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bookmark/bookmark_element.py +0 -0
  26. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bookmark/bookmark_markdown_node.py +0 -0
  27. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bookmark/bookmark_models.py +0 -0
  28. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/breadcrumbs/__init__.py +0 -0
  29. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -0
  30. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -0
  31. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -0
  32. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bulleted_list/__init__.py +0 -0
  33. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bulleted_list/bulleted_list_element.py +0 -0
  34. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -0
  35. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/bulleted_list/bulleted_list_models.py +0 -0
  36. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/callout/__init__.py +0 -0
  37. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/callout/callout_element.py +0 -0
  38. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/callout/callout_markdown_node.py +0 -0
  39. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/callout/callout_models.py +0 -0
  40. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/child_database/__init__.py +0 -0
  41. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/child_database/child_database_models.py +0 -0
  42. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/child_page/__init__.py +0 -0
  43. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/child_page/child_page_element.py +0 -0
  44. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/child_page/child_page_models.py +0 -0
  45. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/client.py +0 -0
  46. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/code/__init__.py +0 -0
  47. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/code/code_element.py +0 -0
  48. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/code/code_markdown_node.py +0 -0
  49. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/code/code_models.py +0 -0
  50. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/column/__init__.py +0 -0
  51. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/column/column_element.py +0 -0
  52. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/column/column_list_element.py +0 -0
  53. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/column/column_list_markdown_node.py +0 -0
  54. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/column/column_markdown_node.py +0 -0
  55. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/column/column_models.py +0 -0
  56. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/divider/__init__.py +0 -0
  57. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/divider/divider_element.py +0 -0
  58. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/divider/divider_markdown_node.py +0 -0
  59. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/divider/divider_models.py +0 -0
  60. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/embed/__init__.py +0 -0
  61. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/embed/embed_element.py +0 -0
  62. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/embed/embed_markdown_node.py +0 -0
  63. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/embed/embed_models.py +0 -0
  64. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/equation/__init__.py +0 -0
  65. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/equation/equation_element.py +0 -0
  66. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/equation/equation_element_markdown_node.py +0 -0
  67. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/equation/equation_models.py +0 -0
  68. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/file/__init__.py +0 -0
  69. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/file/file_element.py +0 -0
  70. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/file/file_element_markdown_node.py +0 -0
  71. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/file/file_element_models.py +0 -0
  72. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/guards.py +0 -0
  73. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/heading/__init__.py +0 -0
  74. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/heading/heading_element.py +0 -0
  75. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/heading/heading_markdown_node.py +0 -0
  76. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/heading/heading_models.py +0 -0
  77. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/image_block/__init__.py +0 -0
  78. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/image_block/image_element.py +0 -0
  79. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/image_block/image_markdown_node.py +0 -0
  80. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/image_block/image_models.py +0 -0
  81. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/mixins/captions/__init__.py +0 -0
  82. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -0
  83. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/mixins/captions/caption_mixin.py +0 -0
  84. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/models.py +0 -0
  85. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/numbered_list/__init__.py +0 -0
  86. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/numbered_list/numbered_list_element.py +0 -0
  87. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -0
  88. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/numbered_list/numbered_list_models.py +0 -0
  89. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/paragraph/__init__.py +0 -0
  90. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/paragraph/paragraph_element.py +0 -0
  91. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/paragraph/paragraph_markdown_node.py +0 -0
  92. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/paragraph/paragraph_models.py +0 -0
  93. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/pdf/__init__.py +0 -0
  94. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/pdf/pdf_element.py +0 -0
  95. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/pdf/pdf_markdown_node.py +0 -0
  96. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/pdf/pdf_models.py +0 -0
  97. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/quote/__init__.py +0 -0
  98. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/quote/quote_element.py +0 -0
  99. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/quote/quote_markdown_node.py +0 -0
  100. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/quote/quote_models.py +0 -0
  101. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/registry/__init__.py +0 -0
  102. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/registry/block_registry.py +0 -0
  103. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/registry/block_registry_builder.py +0 -0
  104. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/rich_text/__init__.py +0 -0
  105. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/rich_text/rich_text_models.py +0 -0
  106. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/syntax_prompt_builder.py +0 -0
  107. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table/__init__.py +0 -0
  108. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table/table_element.py +0 -0
  109. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table/table_markdown_node.py +0 -0
  110. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table/table_models.py +0 -0
  111. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table_of_contents/__init__.py +0 -0
  112. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table_of_contents/table_of_contents_element.py +0 -0
  113. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -0
  114. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/table_of_contents/table_of_contents_models.py +0 -0
  115. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/todo/__init__.py +0 -0
  116. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/todo/todo_element.py +0 -0
  117. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/todo/todo_markdown_node.py +0 -0
  118. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/todo/todo_models.py +0 -0
  119. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggle/__init__.py +0 -0
  120. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggle/toggle_element.py +0 -0
  121. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggle/toggle_markdown_node.py +0 -0
  122. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggle/toggle_models.py +0 -0
  123. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggleable_heading/__init__.py +0 -0
  124. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -0
  125. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -0
  126. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/types.py +0 -0
  127. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/video/__init__.py +0 -0
  128. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/video/video_element.py +0 -0
  129. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/video/video_element_models.py +0 -0
  130. {notionary-0.2.22 → notionary-0.2.23}/notionary/blocks/video/video_markdown_node.py +0 -0
  131. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/__init__.py +0 -0
  132. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/client.py +0 -0
  133. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/database.py +0 -0
  134. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/database_filter_builder.py +0 -0
  135. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/database_provider.py +0 -0
  136. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/exceptions.py +0 -0
  137. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/factory.py +0 -0
  138. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/models.py +0 -0
  139. {notionary-0.2.22 → notionary-0.2.23}/notionary/database/notion_database.py +0 -0
  140. {notionary-0.2.22 → notionary-0.2.23}/notionary/file_upload/__init__.py +0 -0
  141. {notionary-0.2.22 → notionary-0.2.23}/notionary/file_upload/client.py +0 -0
  142. {notionary-0.2.22 → notionary-0.2.23}/notionary/file_upload/models.py +0 -0
  143. {notionary-0.2.22 → notionary-0.2.23}/notionary/file_upload/notion_file_upload.py +0 -0
  144. {notionary-0.2.22 → notionary-0.2.23}/notionary/markdown/___init__.py +0 -0
  145. {notionary-0.2.22 → notionary-0.2.23}/notionary/markdown/makdown_document_model.py +0 -0
  146. {notionary-0.2.22 → notionary-0.2.23}/notionary/markdown/markdown_builder.py +0 -0
  147. {notionary-0.2.22 → notionary-0.2.23}/notionary/markdown/markdown_document_model.py +0 -0
  148. {notionary-0.2.22 → notionary-0.2.23}/notionary/markdown/markdown_node.py +0 -0
  149. {notionary-0.2.22 → notionary-0.2.23}/notionary/models/notion_database_response.py +0 -0
  150. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/models.py +0 -0
  151. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/page_content_deleting_service.py +0 -0
  152. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/page_content_writer.py +0 -0
  153. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/property_formatter.py +0 -0
  154. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/__init__.py +0 -0
  155. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/base_block_renderer.py +0 -0
  156. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/block_processing_context.py +0 -0
  157. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/block_rendering_context.py +0 -0
  158. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/column_list_renderer.py +0 -0
  159. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/column_renderer.py +0 -0
  160. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/equation_renderer.py +0 -0
  161. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/line_renderer.py +0 -0
  162. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/numbered_list_renderer.py +0 -0
  163. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/toggle_renderer.py +0 -0
  164. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/handler/toggleable_heading_renderer.py +0 -0
  165. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/reader/page_content_retriever.py +0 -0
  166. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/search_filter_builder.py +0 -0
  167. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/utils.py +0 -0
  168. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/__init__.py +0 -0
  169. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/code_handler.py +0 -0
  170. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/column_handler.py +0 -0
  171. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/column_list_handler.py +0 -0
  172. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/equation_handler.py +0 -0
  173. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/line_handler.py +0 -0
  174. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/line_processing_context.py +0 -0
  175. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/regular_line_handler.py +0 -0
  176. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/table_handler.py +0 -0
  177. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/toggle_handler.py +0 -0
  178. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/handler/toggleable_heading_handler.py +0 -0
  179. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/markdown_to_notion_converter.py +0 -0
  180. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/markdown_to_notion_converter_context.py +0 -0
  181. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/markdown_to_notion_formatting_post_processor.py +0 -0
  182. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
  183. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  184. {notionary-0.2.22 → notionary-0.2.23}/notionary/page/writer/notion_text_length_processor.py +0 -0
  185. {notionary-0.2.22 → notionary-0.2.23}/notionary/telemetry/__init__.py +0 -0
  186. {notionary-0.2.22 → notionary-0.2.23}/notionary/telemetry/service.py +0 -0
  187. {notionary-0.2.22 → notionary-0.2.23}/notionary/telemetry/views.py +0 -0
  188. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/__init__.py +0 -0
  189. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/base_notion_user.py +0 -0
  190. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/client.py +0 -0
  191. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/models.py +0 -0
  192. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/notion_bot_user.py +0 -0
  193. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/notion_user.py +0 -0
  194. {notionary-0.2.22 → notionary-0.2.23}/notionary/user/notion_user_manager.py +0 -0
  195. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/__init__.py +0 -0
  196. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/concurrency_limiter.py +0 -0
  197. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/factory_decorator.py +0 -0
  198. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/factory_only.py +0 -0
  199. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/fuzzy.py +0 -0
  200. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/logging_mixin.py +0 -0
  201. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/page_id_utils.py +0 -0
  202. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/singleton.py +0 -0
  203. {notionary-0.2.22 → notionary-0.2.23}/notionary/util/singleton_metaclass.py +0 -0
  204. {notionary-0.2.22 → notionary-0.2.23}/notionary/workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: notionary
3
- Version: 0.2.22
3
+ Version: 0.2.23
4
4
  Summary: Python library for programmatic Notion workspace management - databases, pages, and content with advanced Markdown support
5
5
  License: MIT
6
6
  Author: Mathis Arends
@@ -13,9 +13,7 @@ Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
- Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
17
16
  Requires-Dist: httpx (>=0.28.0)
18
- Requires-Dist: isort (>=6.0.1,<7.0.0)
19
17
  Requires-Dist: posthog (>=6.3.1,<7.0.0)
20
18
  Requires-Dist: pydantic (>=2.11.4)
21
19
  Requires-Dist: python-dotenv (>=1.1.0)
@@ -8,6 +8,7 @@ from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformati
8
8
  from notionary.blocks.models import Block, BlockType
9
9
  from notionary.util import LoggingMixin
10
10
 
11
+
11
12
  class ChildDatabaseElement(BaseBlockElement, LoggingMixin):
12
13
  """
13
14
  Handles conversion between Markdown database references and Notion child database blocks.
@@ -23,14 +24,11 @@ class ChildDatabaseElement(BaseBlockElement, LoggingMixin):
23
24
  return block.type == BlockType.CHILD_DATABASE and block.child_database
24
25
 
25
26
  @classmethod
26
- async def markdown_to_notion(
27
- cls, text: str
28
- ) -> Optional[str]:
27
+ async def markdown_to_notion(cls, text: str) -> Optional[str]:
29
28
  """
30
29
  Convert markdown database syntax to actual Notion database.
31
30
  Returns the database_id if successful, None otherwise.
32
31
  """
33
- cls.logger.warning("Creating database from markdown is not supported via the block api. Call the create_child_page method in NotionPage instead.s")
34
32
  return None
35
33
 
36
34
  @classmethod
@@ -10,7 +10,7 @@ from notionary.blocks.rich_text.rich_text_models import (
10
10
  MentionTemplateMention,
11
11
  )
12
12
  from notionary.blocks.types import BlockColor
13
- from notionary.blocks.rich_text.name_to_id_resolver import NameIdResolver
13
+ from notionary.shared import NameIdResolver
14
14
 
15
15
 
16
16
  class TextInlineFormatter:
@@ -0,0 +1,26 @@
1
+ from .client import CommentClient
2
+ from .models import (
3
+ Comment,
4
+ CommentAttachment,
5
+ CommentAttachmentExternal,
6
+ CommentAttachmentFile,
7
+ CommentDisplayName,
8
+ CommentListResponse,
9
+ CommentParent,
10
+ FileWithExpiry,
11
+ UserRef,
12
+ )
13
+
14
+
15
+ __all__ = [
16
+ "CommentClient",
17
+ "Comment",
18
+ "CommentAttachment",
19
+ "CommentAttachmentExternal",
20
+ "CommentAttachmentFile",
21
+ "CommentDisplayName",
22
+ "CommentListResponse",
23
+ "CommentParent",
24
+ "FileWithExpiry",
25
+ "UserRef",
26
+ ]
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, AsyncGenerator, Optional
4
+
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
+
9
+
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(
53
+ self,
54
+ *,
55
+ block_id: str,
56
+ start_cursor: Optional[str] = None,
57
+ page_size: Optional[int] = None,
58
+ ) -> CommentListResponse:
59
+ """
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.
69
+ """
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
75
+
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)
80
+
81
+ async def iter_comments(
82
+ self,
83
+ *,
84
+ block_id: str,
85
+ 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
101
+
102
+ async def create_comment_on_page(
103
+ self,
104
+ *,
105
+ 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.
112
+
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
130
+
131
+ 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)
135
+
136
+ async def create_comment(
137
+ 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
170
+
171
+ if display_name:
172
+ body["display_name"] = display_name
173
+ if attachments:
174
+ body["attachments"] = attachments
175
+
176
+ 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
+ )
@@ -0,0 +1,129 @@
1
+ # notionary/comments/models.py
2
+ from __future__ import annotations
3
+
4
+ from datetime import datetime
5
+ from typing import Literal, Optional, Union
6
+
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
@@ -42,18 +42,13 @@ class NotionPageClient(BaseNotionClient):
42
42
  )
43
43
 
44
44
  properties: dict[str, Any] = {
45
- "title": {
46
- "title": [
47
- {"type": "text", "text": {"content": title}}
48
- ]
49
- }
45
+ "title": {"title": [{"type": "text", "text": {"content": title}}]}
50
46
  }
51
47
 
52
48
  payload = {"parent": parent, "properties": properties}
53
49
  response = await self.post("pages", payload)
54
50
  return NotionPageResponse.model_validate(response)
55
51
 
56
-
57
52
  async def patch_page(
58
53
  self, page_id: str, data: Optional[dict[str, Any]] = None
59
54
  ) -> NotionPageResponse:
@@ -5,6 +5,7 @@ import random
5
5
  from typing import TYPE_CHECKING, Any, Callable, Optional, Union
6
6
 
7
7
  from notionary.blocks.client import NotionBlockClient
8
+ from notionary.comments import CommentClient, Comment
8
9
  from notionary.blocks.syntax_prompt_builder import SyntaxPromptBuilder
9
10
  from notionary.blocks.models import DatabaseParent
10
11
  from notionary.blocks.registry.block_registry import BlockRegistry
@@ -24,6 +25,7 @@ from notionary.util.fuzzy import find_best_match
24
25
  if TYPE_CHECKING:
25
26
  from notionary import NotionDatabase
26
27
 
28
+
27
29
  class NotionPage(LoggingMixin):
28
30
  """
29
31
  Managing content and metadata of a Notion page.
@@ -56,8 +58,9 @@ class NotionPage(LoggingMixin):
56
58
 
57
59
  self._client = NotionPageClient(token=token)
58
60
  self._block_client = NotionBlockClient(token=token)
61
+ self._comment_client = CommentClient(token=token)
59
62
  self._page_data = None
60
-
63
+
61
64
  self.block_element_registry = BlockRegistry.create_registry()
62
65
 
63
66
  self._page_content_writer = PageContentWriter(
@@ -213,6 +216,41 @@ class NotionPage(LoggingMixin):
213
216
  markdown_syntax_builder = SyntaxPromptBuilder()
214
217
  return markdown_syntax_builder.build_concise_reference()
215
218
 
219
+ async def get_comments(self) -> list[Comment]:
220
+ return await self._comment_client.list_all_comments_for_page(page_id=self._page_id)
221
+
222
+ async def post_comment(
223
+ self,
224
+ content: str,
225
+ *,
226
+ discussion_id: Optional[str] = None,
227
+ rich_text: Optional[list[dict[str, Any]]] = None,
228
+ ) -> Optional[Comment]:
229
+ """
230
+ Post a comment on this page.
231
+
232
+ Args:
233
+ content: The plain text content of the comment
234
+ discussion_id: Optional discussion ID to reply to an existing discussion
235
+ rich_text: Optional rich text formatting for the comment content
236
+
237
+ Returns:
238
+ Comment: The created comment object, or None if creation failed
239
+ """
240
+ try:
241
+ # Use the comment client to create the comment
242
+ comment = await self._comment_client.create_comment(
243
+ page_id=self._page_id,
244
+ content=content,
245
+ discussion_id=discussion_id,
246
+ rich_text=rich_text,
247
+ )
248
+ self.logger.info(f"Successfully posted comment on page '{self._title}'")
249
+ return comment
250
+ except Exception as e:
251
+ self.logger.error(f"Failed to post comment on page '{self._title}': {str(e)}")
252
+ return None
253
+
216
254
  async def set_title(self, title: str) -> str:
217
255
  """
218
256
  Set the title of the page.
@@ -320,27 +358,33 @@ class NotionPage(LoggingMixin):
320
358
 
321
359
  self.logger.error(f"Error updating page emoji: {str(e)}")
322
360
  return None
323
-
361
+
324
362
  async def create_child_database(self, title: str) -> NotionDatabase:
325
363
  from notionary import NotionDatabase
364
+
326
365
  database_client = NotionDatabaseClient(token=self._client.token)
327
-
328
- create_database_response = await database_client.create_database(
366
+
367
+ create_database_response = await database_client.create_database(
329
368
  title=title,
330
369
  parent_page_id=self._page_id,
331
370
  )
332
-
333
- return await NotionDatabase.from_database_id(id=create_database_response.id, token=self._client.token)
334
-
371
+
372
+ return await NotionDatabase.from_database_id(
373
+ id=create_database_response.id, token=self._client.token
374
+ )
375
+
335
376
  async def create_child_page(self, title: str) -> NotionPage:
336
377
  from notionary import NotionPage
378
+
337
379
  child_page_response = await self._client.create_page(
338
380
  parent_page_id=self._page_id,
339
381
  title=title,
340
382
  )
341
-
342
- return await NotionPage.from_page_id(page_id=child_page_response.id, token=self._client.token)
343
-
383
+
384
+ return await NotionPage.from_page_id(
385
+ page_id=child_page_response.id, token=self._client.token
386
+ )
387
+
344
388
  async def set_external_icon(self, url: str) -> Optional[str]:
345
389
  """
346
390
  Sets the page icon to an external image.
@@ -636,4 +680,4 @@ class NotionPage(LoggingMixin):
636
680
  """Extract parent database ID from page response."""
637
681
  parent = page_response.parent
638
682
  if isinstance(parent, DatabaseParent):
639
- return parent.database_id
683
+ return parent.database_id
@@ -1,4 +1,3 @@
1
- # notionary/blocks/context/page_context.py
2
1
  from __future__ import annotations
3
2
 
4
3
  from typing import TYPE_CHECKING, Optional
@@ -0,0 +1,5 @@
1
+ from .name_to_id_resolver import NameIdResolver
2
+
3
+ __all__ = [
4
+ "NameIdResolver"
5
+ ]
@@ -1,7 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
4
- from contextlib import contextmanager
5
3
  from typing import Optional
6
4
 
7
5
  from notionary.user.notion_user_manager import NotionUserManager
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "notionary"
3
- version = "0.2.22"
3
+ version = "0.2.23"
4
4
  description = "Python library for programmatic Notion workspace management - databases, pages, and content with advanced Markdown support"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -13,8 +13,6 @@ dependencies = [
13
13
  "python-dotenv>=1.1.0",
14
14
  "pydantic>=2.11.4",
15
15
  "posthog (>=6.3.1,<7.0.0)",
16
- "aiofiles (>=24.1.0,<25.0.0)",
17
- "isort (>=6.0.1,<7.0.0)",
18
16
  ]
19
17
 
20
18
  [project.urls]
@@ -32,3 +30,4 @@ pytest = "^8.4.1"
32
30
  pytest-asyncio = "^1.1.0"
33
31
  pytest-mock = "^3.14.1"
34
32
  pytest-cov = "^6.2.1"
33
+ isort = "^6.0.1"
File without changes
File without changes