notionary 0.2.27__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.27.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.27.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
  200. {notionary-0.2.27.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 -712
  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.27.dist-info/RECORD +0 -202
@@ -0,0 +1,40 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import Block, BlockType
4
+ from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
5
+
6
+
7
+ class FileRenderer(CaptionedBlockRenderer):
8
+ @override
9
+ def _can_handle(self, block: Block) -> bool:
10
+ return block.type == BlockType.FILE
11
+
12
+ @override
13
+ async def _render_main_content(self, block: Block) -> str:
14
+ url = self._extract_file_url(block)
15
+
16
+ if not url:
17
+ return ""
18
+
19
+ syntax = self._syntax_registry.get_file_syntax()
20
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
+
22
+ def _extract_file_url(self, block: Block) -> str:
23
+ if not block.file:
24
+ return ""
25
+
26
+ if block.file.external:
27
+ return block.file.external.url or ""
28
+ elif block.file.file:
29
+ return block.file.file.url or ""
30
+
31
+ return ""
32
+
33
+ def _extract_file_name(self, block: Block) -> str:
34
+ if not block.file:
35
+ return ""
36
+
37
+ if block.file.name:
38
+ return block.file.name or ""
39
+
40
+ return ""
@@ -0,0 +1,69 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.schemas import Block, BlockType
5
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
6
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
7
+ from notionary.page.content.syntax.service import SyntaxRegistry
8
+
9
+
10
+ class HeadingRenderer(BlockRenderer):
11
+ def __init__(
12
+ self,
13
+ syntax_registry: SyntaxRegistry | None = None,
14
+ rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
15
+ ) -> None:
16
+ super().__init__(syntax_registry=syntax_registry)
17
+ self._syntax = self._syntax_registry.get_heading_syntax()
18
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
19
+
20
+ @override
21
+ def _can_handle(self, block: Block) -> bool:
22
+ if block.type == BlockType.HEADING_1:
23
+ return not block.heading_1.is_toggleable
24
+ if block.type == BlockType.HEADING_2:
25
+ return not block.heading_2.is_toggleable
26
+ if block.type == BlockType.HEADING_3:
27
+ return not block.heading_3.is_toggleable
28
+
29
+ return False
30
+
31
+ @override
32
+ async def _process(self, context: MarkdownRenderingContext) -> None:
33
+ level = self._get_heading_level(context.block)
34
+ title = await self._get_heading_title(context.block)
35
+
36
+ if not title or level == 0:
37
+ return
38
+
39
+ heading_markdown = f"{self._syntax.start_delimiter * level} {title}"
40
+
41
+ if context.indent_level > 0:
42
+ heading_markdown = context.indent_text(heading_markdown)
43
+
44
+ context.markdown_result = heading_markdown
45
+
46
+ def _get_heading_level(self, block: Block) -> int:
47
+ if block.type == BlockType.HEADING_1:
48
+ return 1
49
+ elif block.type == BlockType.HEADING_2:
50
+ return 2
51
+ elif block.type == BlockType.HEADING_3:
52
+ return 3
53
+ else:
54
+ return 0
55
+
56
+ async def _get_heading_title(self, block: Block) -> str:
57
+ if block.type == BlockType.HEADING_1:
58
+ heading_content = block.heading_1
59
+ elif block.type == BlockType.HEADING_2:
60
+ heading_content = block.heading_2
61
+ elif block.type == BlockType.HEADING_3:
62
+ heading_content = block.heading_3
63
+ else:
64
+ return ""
65
+
66
+ if not heading_content or not heading_content.rich_text:
67
+ return ""
68
+
69
+ return await self._rich_text_markdown_converter.to_markdown(heading_content.rich_text)
@@ -0,0 +1,31 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import Block, BlockType
4
+ from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
5
+
6
+
7
+ class ImageRenderer(CaptionedBlockRenderer):
8
+ @override
9
+ def _can_handle(self, block: Block) -> bool:
10
+ return block.type == BlockType.IMAGE
11
+
12
+ @override
13
+ async def _render_main_content(self, block: Block) -> str:
14
+ url = self._extract_image_url(block)
15
+
16
+ if not url:
17
+ return ""
18
+
19
+ syntax = self._syntax_registry.get_image_syntax()
20
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
+
22
+ def _extract_image_url(self, block: Block) -> str:
23
+ if not block.image:
24
+ return ""
25
+
26
+ if block.image.external:
27
+ return block.image.external.url or ""
28
+ elif block.image.file:
29
+ return block.image.file.url or ""
30
+
31
+ return ""
@@ -0,0 +1,41 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import (
4
+ RichTextToMarkdownConverter,
5
+ )
6
+ from notionary.blocks.schemas import Block, BlockType
7
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
8
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
9
+ from notionary.page.content.syntax.service import SyntaxRegistry
10
+
11
+
12
+ class NumberedListRenderer(BlockRenderer):
13
+ # Placeholder for numbered list fixer (post processing)
14
+ NUMBERED_LIST_PLACEHOLDER = "__NUM__"
15
+
16
+ def __init__(
17
+ self,
18
+ syntax_registry: SyntaxRegistry | None = None,
19
+ rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
20
+ ) -> None:
21
+ super().__init__(syntax_registry=syntax_registry)
22
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
23
+
24
+ @override
25
+ def _can_handle(self, block: Block) -> bool:
26
+ return block.type == BlockType.NUMBERED_LIST_ITEM
27
+
28
+ @override
29
+ async def _process(self, context: MarkdownRenderingContext) -> None:
30
+ list_item_data = context.block.numbered_list_item
31
+ rich_text = list_item_data.rich_text if list_item_data else []
32
+ content = await self._rich_text_markdown_converter.to_markdown(rich_text)
33
+
34
+ item_line = context.indent_text(f"{self.NUMBERED_LIST_PLACEHOLDER}. {content}")
35
+
36
+ children_markdown = await context.render_children_with_additional_indent(1)
37
+
38
+ if children_markdown:
39
+ context.markdown_result = f"{item_line}\n{children_markdown}"
40
+ else:
41
+ context.markdown_result = item_line
@@ -0,0 +1,40 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.schemas import Block, BlockType
5
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
6
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
7
+
8
+
9
+ class ParagraphRenderer(BlockRenderer):
10
+ def __init__(self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None) -> None:
11
+ super().__init__()
12
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
13
+
14
+ @override
15
+ def _can_handle(self, block: Block) -> bool:
16
+ return block.type == BlockType.PARAGRAPH
17
+
18
+ @override
19
+ async def _process(self, context: MarkdownRenderingContext) -> None:
20
+ markdown = await self._convert_paragraph_to_markdown(context.block)
21
+
22
+ if not markdown:
23
+ context.markdown_result = ""
24
+ return
25
+
26
+ if context.indent_level > 0:
27
+ markdown = context.indent_text(markdown)
28
+
29
+ children_markdown = await context.render_children_with_additional_indent(1)
30
+
31
+ if children_markdown:
32
+ context.markdown_result = f"{markdown}\n{children_markdown}"
33
+ else:
34
+ context.markdown_result = markdown
35
+
36
+ async def _convert_paragraph_to_markdown(self, block: Block) -> str | None:
37
+ if not block.paragraph or not block.paragraph.rich_text:
38
+ return None
39
+
40
+ return await self._rich_text_markdown_converter.to_markdown(block.paragraph.rich_text)
@@ -0,0 +1,31 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import Block, BlockType
4
+ from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
5
+
6
+
7
+ class PdfRenderer(CaptionedBlockRenderer):
8
+ @override
9
+ def _can_handle(self, block: Block) -> bool:
10
+ return block.type == BlockType.PDF
11
+
12
+ @override
13
+ async def _render_main_content(self, block: Block) -> str:
14
+ url = self._extract_pdf_url(block)
15
+
16
+ if not url:
17
+ return ""
18
+
19
+ syntax = self._syntax_registry.get_pdf_syntax()
20
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
+
22
+ def _extract_pdf_url(self, block: Block) -> str:
23
+ if not block.pdf:
24
+ return ""
25
+
26
+ if block.pdf.external:
27
+ return block.pdf.external.url or ""
28
+ elif block.pdf.file:
29
+ return block.pdf.file.url or ""
30
+
31
+ return ""
@@ -0,0 +1,49 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.schemas import Block, BlockType
5
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
6
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
7
+ from notionary.page.content.syntax.service import SyntaxRegistry
8
+
9
+
10
+ class QuoteRenderer(BlockRenderer):
11
+ def __init__(
12
+ self,
13
+ syntax_registry: SyntaxRegistry | None = None,
14
+ rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
15
+ ) -> None:
16
+ super().__init__(syntax_registry=syntax_registry)
17
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
18
+
19
+ @override
20
+ def _can_handle(self, block: Block) -> bool:
21
+ return block.type == BlockType.QUOTE
22
+
23
+ @override
24
+ async def _process(self, context: MarkdownRenderingContext) -> None:
25
+ markdown = await self._convert_quote_to_markdown(context.block)
26
+
27
+ if not markdown:
28
+ context.markdown_result = ""
29
+ return
30
+
31
+ syntax = self._syntax_registry.get_quote_syntax()
32
+ quote_lines = markdown.split("\n")
33
+ quote_markdown = "\n".join(f"{syntax.start_delimiter}{line}" for line in quote_lines)
34
+
35
+ if context.indent_level > 0:
36
+ quote_markdown = context.indent_text(quote_markdown)
37
+
38
+ children_markdown = await context.render_children_with_additional_indent(1)
39
+
40
+ if children_markdown:
41
+ context.markdown_result = f"{quote_markdown}\n{children_markdown}"
42
+ else:
43
+ context.markdown_result = quote_markdown
44
+
45
+ async def _convert_quote_to_markdown(self, block: Block) -> str | None:
46
+ if not block.quote or not block.quote.rich_text:
47
+ return None
48
+
49
+ return await self._rich_text_markdown_converter.to_markdown(block.quote.rich_text)
@@ -0,0 +1,115 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.schemas import Block, BlockType
5
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
6
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
7
+
8
+
9
+ class TableRenderer(BlockRenderer):
10
+ MINIMUM_COLUMN_WIDTH = 3
11
+
12
+ def __init__(self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None) -> None:
13
+ super().__init__()
14
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
15
+ self._table_syntax = self._syntax_registry.get_table_syntax()
16
+
17
+ @override
18
+ def _can_handle(self, block: Block) -> bool:
19
+ return block.type == BlockType.TABLE
20
+
21
+ @override
22
+ async def _process(self, context: MarkdownRenderingContext) -> None:
23
+ table_markdown = await self._build_table_markdown(context.block)
24
+
25
+ if not table_markdown:
26
+ context.markdown_result = ""
27
+ return
28
+
29
+ if context.indent_level > 0:
30
+ table_markdown = context.indent_text(table_markdown)
31
+
32
+ children_markdown = await context.render_children_with_additional_indent(1)
33
+
34
+ if children_markdown:
35
+ context.markdown_result = f"{table_markdown}\n{children_markdown}"
36
+ else:
37
+ context.markdown_result = table_markdown
38
+
39
+ async def _build_table_markdown(self, block: Block) -> str:
40
+ if not block.table or not block.has_children or not block.children:
41
+ return ""
42
+
43
+ rows = []
44
+ for row_block in block.children:
45
+ if row_block.type != BlockType.TABLE_ROW or not row_block.table_row:
46
+ continue
47
+
48
+ row_cells = await self._extract_row_cells(row_block)
49
+ rows.append(row_cells)
50
+
51
+ if not rows:
52
+ return ""
53
+
54
+ max_columns = max(len(row) for row in rows)
55
+ normalized_rows = self._normalize_row_lengths(rows, max_columns)
56
+ column_widths = self._calculate_column_widths(normalized_rows, max_columns)
57
+
58
+ markdown_lines = []
59
+
60
+ first_row = normalized_rows[0]
61
+ formatted_first_row = self._format_row(first_row, column_widths)
62
+ markdown_lines.append(formatted_first_row)
63
+
64
+ separator_line = self._create_separator_line(column_widths)
65
+ markdown_lines.append(separator_line)
66
+
67
+ remaining_rows = normalized_rows[1:]
68
+ for row in remaining_rows:
69
+ formatted_row = self._format_row(row, column_widths)
70
+ markdown_lines.append(formatted_row)
71
+
72
+ return "\n".join(markdown_lines)
73
+
74
+ def _normalize_row_lengths(self, rows: list[list[str]], target_length: int) -> list[list[str]]:
75
+ return [row + [""] * (target_length - len(row)) for row in rows]
76
+
77
+ def _calculate_column_widths(self, rows: list[list[str]], num_columns: int) -> list[int]:
78
+ widths = [max(len(row[i]) for row in rows) for i in range(num_columns)]
79
+ return [max(width, self.MINIMUM_COLUMN_WIDTH) for width in widths]
80
+
81
+ def _format_row(self, cells: list[str], column_widths: list[int]) -> str:
82
+ centered_cells = [cell.center(column_widths[i]) for i, cell in enumerate(cells)]
83
+ delimiter = self._table_syntax.start_delimiter
84
+ return f"{delimiter} {f' {delimiter} '.join(centered_cells)} {delimiter}"
85
+
86
+ def _create_separator_line(self, column_widths: list[int]) -> str:
87
+ separators = ["-" * width for width in column_widths]
88
+ delimiter = self._table_syntax.start_delimiter
89
+ return f"{delimiter} {f' {delimiter} '.join(separators)} {delimiter}"
90
+
91
+ def _has_column_header(self, block: Block) -> bool:
92
+ if not block.table:
93
+ return False
94
+ return block.table.has_column_header or False
95
+
96
+ def _has_row_header(self, block: Block) -> bool:
97
+ if not block.table:
98
+ return False
99
+ return block.table.has_row_header or False
100
+
101
+ def _get_table_width(self, block: Block) -> int:
102
+ if not block.table:
103
+ return 0
104
+ return block.table.table_width or 0
105
+
106
+ async def _extract_row_cells(self, row_block: Block) -> list[str]:
107
+ if not row_block.table_row or not row_block.table_row.cells:
108
+ return []
109
+
110
+ cells = []
111
+ for cell in row_block.table_row.cells:
112
+ cell_text = await self._rich_text_markdown_converter.to_markdown(cell)
113
+ cells.append(cell_text or "")
114
+
115
+ return cells
@@ -0,0 +1,26 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import Block, BlockType
4
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
5
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
6
+
7
+
8
+ class TableOfContentsRenderer(BlockRenderer):
9
+ @override
10
+ def _can_handle(self, block: Block) -> bool:
11
+ return block.type == BlockType.TABLE_OF_CONTENTS
12
+
13
+ @override
14
+ async def _process(self, context: MarkdownRenderingContext) -> None:
15
+ syntax = self._syntax_registry.get_table_of_contents_syntax()
16
+ toc_markdown = syntax.start_delimiter
17
+
18
+ if context.indent_level > 0:
19
+ toc_markdown = context.indent_text(toc_markdown)
20
+
21
+ children_markdown = await context.render_children_with_additional_indent(1)
22
+
23
+ if children_markdown:
24
+ context.markdown_result = f"{toc_markdown}\n{children_markdown}"
25
+ else:
26
+ context.markdown_result = toc_markdown
@@ -0,0 +1,17 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.enums import BlockType
4
+ from notionary.blocks.schemas import Block
5
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
6
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
7
+
8
+
9
+ class TableRowHandler(BlockRenderer):
10
+ @override
11
+ def _can_handle(self, block: Block) -> bool:
12
+ return block.type == BlockType.TABLE_ROW
13
+
14
+ @override
15
+ async def _process(self, context: MarkdownRenderingContext) -> None:
16
+ """Table rows are internally handled by table as the structure supports it"""
17
+ pass
@@ -0,0 +1,56 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.enums import BlockType
4
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
5
+ from notionary.blocks.schemas import Block
6
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
7
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
8
+ from notionary.page.content.syntax.service import SyntaxRegistry
9
+
10
+
11
+ class TodoRenderer(BlockRenderer):
12
+ def __init__(
13
+ self,
14
+ syntax_registry: SyntaxRegistry | None = None,
15
+ rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
16
+ ) -> None:
17
+ super().__init__(syntax_registry=syntax_registry)
18
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
19
+
20
+ @override
21
+ def _can_handle(self, block: Block) -> bool:
22
+ return block.type == BlockType.TO_DO
23
+
24
+ @override
25
+ async def _process(self, context: MarkdownRenderingContext) -> None:
26
+ is_checked, content = await self._extract_todo_info(context.block)
27
+
28
+ if not content:
29
+ context.markdown_result = ""
30
+ return
31
+
32
+ syntax = self._syntax_registry.get_todo_done_syntax() if is_checked else self._syntax_registry.get_todo_syntax()
33
+
34
+ todo_markdown = f"{syntax.start_delimiter} {content}"
35
+
36
+ if context.indent_level > 0:
37
+ todo_markdown = context.indent_text(todo_markdown)
38
+
39
+ children_markdown = await context.render_children_with_additional_indent(1)
40
+
41
+ if children_markdown:
42
+ context.markdown_result = f"{todo_markdown}\n{children_markdown}"
43
+ else:
44
+ context.markdown_result = todo_markdown
45
+
46
+ async def _extract_todo_info(self, block: Block) -> tuple[bool, str]:
47
+ if not block.to_do:
48
+ return False, ""
49
+
50
+ is_checked = block.to_do.checked or False
51
+
52
+ content = ""
53
+ if block.to_do.rich_text:
54
+ content = await self._rich_text_markdown_converter.to_markdown(block.to_do.rich_text)
55
+
56
+ return is_checked, content
@@ -0,0 +1,53 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.enums import BlockType
4
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
5
+ from notionary.blocks.schemas import Block
6
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
7
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
8
+ from notionary.page.content.syntax.service import SyntaxRegistry
9
+
10
+
11
+ class ToggleRenderer(BlockRenderer):
12
+ def __init__(
13
+ self,
14
+ syntax_registry: SyntaxRegistry | None = None,
15
+ rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
16
+ ) -> None:
17
+ super().__init__(syntax_registry=syntax_registry)
18
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
19
+
20
+ @override
21
+ def _can_handle(self, block: Block) -> bool:
22
+ return block.type == BlockType.TOGGLE
23
+
24
+ @override
25
+ async def _process(self, context: MarkdownRenderingContext) -> None:
26
+ toggle_title = await self._extract_toggle_title(context.block)
27
+
28
+ if not toggle_title:
29
+ return
30
+
31
+ syntax = self._syntax_registry.get_toggle_syntax()
32
+ toggle_start = f"{syntax.start_delimiter} {toggle_title}"
33
+
34
+ if context.indent_level > 0:
35
+ toggle_start = context.indent_text(toggle_start)
36
+
37
+ children_markdown = await context.render_children()
38
+
39
+ toggle_end = syntax.end_delimiter
40
+ if context.indent_level > 0:
41
+ toggle_end = context.indent_text(toggle_end)
42
+
43
+ if children_markdown:
44
+ context.markdown_result = f"{toggle_start}\n{children_markdown}\n{toggle_end}"
45
+ else:
46
+ context.markdown_result = f"{toggle_start}\n{toggle_end}"
47
+
48
+ async def _extract_toggle_title(self, block: Block) -> str:
49
+ if not block.toggle or not block.toggle.rich_text:
50
+ return ""
51
+
52
+ rich_text_title = block.toggle.rich_text
53
+ return await self._rich_text_markdown_converter.to_markdown(rich_text_title)
@@ -0,0 +1,78 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
4
+ from notionary.blocks.schemas import Block, BlockType
5
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
6
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
7
+ from notionary.page.content.syntax.service import SyntaxRegistry
8
+
9
+
10
+ class ToggleableHeadingRenderer(BlockRenderer):
11
+ def __init__(
12
+ self,
13
+ syntax_registry: SyntaxRegistry | None = None,
14
+ rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
15
+ ) -> None:
16
+ super().__init__(syntax_registry=syntax_registry)
17
+ self._heading_syntax = self._syntax_registry.get_heading_syntax()
18
+ self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
19
+
20
+ @override
21
+ def _can_handle(self, block: Block) -> bool:
22
+ if block.type == BlockType.HEADING_1:
23
+ return block.heading_1.is_toggleable
24
+ if block.type == BlockType.HEADING_2:
25
+ return block.heading_2.is_toggleable
26
+ if block.type == BlockType.HEADING_3:
27
+ return block.heading_3.is_toggleable
28
+
29
+ @override
30
+ async def _process(self, context: MarkdownRenderingContext) -> None:
31
+ level = self._get_heading_level(context.block)
32
+ title = await self._get_heading_title(context.block)
33
+
34
+ if not title or level == 0:
35
+ return
36
+
37
+ syntax = self._syntax_registry.get_toggleable_heading_syntax()
38
+ prefix = self._syntax_registry.TOGGLE_DELIMITER + " " + (self._heading_syntax.start_delimiter * level)
39
+ heading_start = f"{prefix} {title}"
40
+
41
+ if context.indent_level > 0:
42
+ heading_start = context.indent_text(heading_start)
43
+
44
+ children_markdown = await context.render_children()
45
+
46
+ heading_end = syntax.end_delimiter
47
+ if context.indent_level > 0:
48
+ heading_end = context.indent_text(heading_end)
49
+
50
+ if children_markdown:
51
+ context.markdown_result = f"{heading_start}\n{children_markdown}\n{heading_end}"
52
+ else:
53
+ context.markdown_result = f"{heading_start}\n{heading_end}"
54
+
55
+ def _get_heading_level(self, block: Block) -> int:
56
+ if block.type == BlockType.HEADING_1:
57
+ return 1
58
+ elif block.type == BlockType.HEADING_2:
59
+ return 2
60
+ elif block.type == BlockType.HEADING_3:
61
+ return 3
62
+ else:
63
+ return 0
64
+
65
+ async def _get_heading_title(self, block: Block) -> str:
66
+ if block.type == BlockType.HEADING_1:
67
+ heading_content = block.heading_1
68
+ elif block.type == BlockType.HEADING_2:
69
+ heading_content = block.heading_2
70
+ elif block.type == BlockType.HEADING_3:
71
+ heading_content = block.heading_3
72
+ else:
73
+ return ""
74
+
75
+ if not heading_content or not heading_content.rich_text:
76
+ return ""
77
+
78
+ return await self._rich_text_markdown_converter.to_markdown(heading_content.rich_text)