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,62 @@
1
+ import re
2
+ from typing import override
3
+
4
+ from notionary.page.content.renderer.post_processing.port import PostProcessor
5
+
6
+
7
+ class NumberedListPlaceholderReplaceerPostProcessor(PostProcessor):
8
+ """
9
+ Handles post processing of numbered lists in markdown text.
10
+ Would otherwise require complex state management during initial rendering.
11
+ """
12
+
13
+ NUMBERED_LIST_PLACEHOLDER = "__NUM__"
14
+ LIST_ITEM_PATTERN = rf"^\s*{re.escape(NUMBERED_LIST_PLACEHOLDER)}\.\s*(.*)"
15
+ NUMBERED_ITEM_PATTERN = r"^\d+\.\s+"
16
+
17
+ @override
18
+ def process(self, markdown_text: str) -> str:
19
+ lines = markdown_text.splitlines()
20
+ processed_lines = []
21
+ list_counter = 1
22
+
23
+ for line_index, line in enumerate(lines):
24
+ if self._is_custom_list_item(line):
25
+ content = self._extract_list_content(line)
26
+ processed_lines.append(f"{list_counter}. {content}")
27
+ list_counter += 1
28
+ elif self._should_skip_blank_line(lines, line_index, processed_lines):
29
+ continue
30
+ else:
31
+ list_counter = 1
32
+ processed_lines.append(line)
33
+
34
+ return "\n".join(processed_lines)
35
+
36
+ def _is_custom_list_item(self, line: str) -> bool:
37
+ return bool(re.match(self.LIST_ITEM_PATTERN, line.strip()))
38
+
39
+ def _extract_list_content(self, line: str) -> str:
40
+ match = re.match(self.LIST_ITEM_PATTERN, line.strip())
41
+ return match.group(1) if match else ""
42
+
43
+ def _is_numbered_list_item(self, line: str) -> bool:
44
+ return bool(re.match(self.NUMBERED_ITEM_PATTERN, line))
45
+
46
+ def _is_blank_line(self, line: str) -> bool:
47
+ return not line.strip()
48
+
49
+ def _should_skip_blank_line(self, lines: list[str], current_index: int, processed_lines: list[str]) -> bool:
50
+ if not self._is_blank_line(lines[current_index]):
51
+ return False
52
+
53
+ previous_is_list_item = processed_lines and self._is_numbered_list_item(processed_lines[-1])
54
+ if not previous_is_list_item:
55
+ return False
56
+
57
+ next_index = current_index + 1
58
+ if next_index >= len(lines):
59
+ return False
60
+
61
+ next_is_list_item = self._is_custom_list_item(lines[next_index])
62
+ return next_is_list_item
@@ -0,0 +1,7 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class PostProcessor(ABC):
5
+ @abstractmethod
6
+ def process(self, markdown_text: str) -> str:
7
+ pass
@@ -0,0 +1,15 @@
1
+ from notionary.page.content.parser.post_processing.port import PostProcessor
2
+
3
+
4
+ class MarkdownRenderingPostProcessor:
5
+ def __init__(self) -> None:
6
+ self._processors: list[PostProcessor] = []
7
+
8
+ def register(self, processor: PostProcessor) -> None:
9
+ self._processors.append(processor)
10
+
11
+ def process(self, markdown_text: str) -> str:
12
+ result = markdown_text
13
+ for processor in self._processors:
14
+ result = processor.process(result)
15
+ return result
@@ -0,0 +1,57 @@
1
+ from .audio import AudioRenderer
2
+ from .base import BlockRenderer
3
+ from .bookmark import BookmarkRenderer
4
+ from .breadcrumb import BreadcrumbRenderer
5
+ from .bulleted_list import BulletedListRenderer
6
+ from .callout import CalloutRenderer
7
+ from .code import CodeRenderer
8
+ from .column import ColumnRenderer
9
+ from .column_list import ColumnListRenderer
10
+ from .divider import DividerRenderer
11
+ from .embed import EmbedRenderer
12
+ from .equation import EquationRenderer
13
+ from .fallback import FallbackRenderer
14
+ from .file import FileRenderer
15
+ from .heading import HeadingRenderer
16
+ from .image import ImageRenderer
17
+ from .numbered_list import NumberedListRenderer
18
+ from .paragraph import ParagraphRenderer
19
+ from .pdf import PdfRenderer
20
+ from .quote import QuoteRenderer
21
+ from .table import TableRenderer
22
+ from .table_of_contents import TableOfContentsRenderer
23
+ from .table_row import TableRowHandler
24
+ from .todo import TodoRenderer
25
+ from .toggle import ToggleRenderer
26
+ from .toggleable_heading import ToggleableHeadingRenderer
27
+ from .video import VideoRenderer
28
+
29
+ __all__ = [
30
+ "AudioRenderer",
31
+ "BlockRenderer",
32
+ "BookmarkRenderer",
33
+ "BreadcrumbRenderer",
34
+ "BulletedListRenderer",
35
+ "CalloutRenderer",
36
+ "CodeRenderer",
37
+ "ColumnListRenderer",
38
+ "ColumnRenderer",
39
+ "DividerRenderer",
40
+ "EmbedRenderer",
41
+ "EquationRenderer",
42
+ "FallbackRenderer",
43
+ "FileRenderer",
44
+ "HeadingRenderer",
45
+ "ImageRenderer",
46
+ "NumberedListRenderer",
47
+ "ParagraphRenderer",
48
+ "PdfRenderer",
49
+ "QuoteRenderer",
50
+ "TableOfContentsRenderer",
51
+ "TableRenderer",
52
+ "TableRowHandler",
53
+ "TodoRenderer",
54
+ "ToggleRenderer",
55
+ "ToggleableHeadingRenderer",
56
+ "VideoRenderer",
57
+ ]
@@ -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 AudioRenderer(CaptionedBlockRenderer):
8
+ @override
9
+ def _can_handle(self, block: Block) -> bool:
10
+ return block.type == BlockType.AUDIO
11
+
12
+ @override
13
+ async def _render_main_content(self, block: Block) -> str:
14
+ url = self._extract_audio_url(block)
15
+
16
+ if not url:
17
+ return ""
18
+
19
+ syntax = self._syntax_registry.get_audio_syntax()
20
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
+
22
+ def _extract_audio_url(self, block: Block) -> str:
23
+ if not block.audio:
24
+ return ""
25
+
26
+ if block.audio.external:
27
+ return block.audio.external.url or ""
28
+ elif block.audio.file:
29
+ return block.audio.file.url or ""
30
+
31
+ return ""
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from notionary.blocks.schemas import Block
6
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
7
+ from notionary.page.content.syntax.service import SyntaxRegistry
8
+
9
+
10
+ class BlockRenderer(ABC):
11
+ def __init__(self, syntax_registry: SyntaxRegistry | None = None) -> None:
12
+ self._syntax_registry = syntax_registry or SyntaxRegistry()
13
+ self._next_handler: BlockRenderer | None = None
14
+
15
+ def set_next(self, handler: BlockRenderer) -> BlockRenderer:
16
+ self._next_handler = handler
17
+ return handler
18
+
19
+ async def handle(self, context: MarkdownRenderingContext) -> None:
20
+ if self._can_handle(context.block):
21
+ await self._process(context)
22
+ elif self._next_handler:
23
+ await self._next_handler.handle(context)
24
+
25
+ @abstractmethod
26
+ def _can_handle(self, block: Block) -> bool:
27
+ pass
28
+
29
+ @abstractmethod
30
+ async def _process(self, context: MarkdownRenderingContext) -> None:
31
+ pass
@@ -0,0 +1,25 @@
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 BookmarkRenderer(CaptionedBlockRenderer):
8
+ @override
9
+ def _can_handle(self, block: Block) -> bool:
10
+ return block.type == BlockType.BOOKMARK
11
+
12
+ @override
13
+ async def _render_main_content(self, block: Block) -> str:
14
+ url = self._extract_bookmark_url(block)
15
+
16
+ if not url:
17
+ return ""
18
+
19
+ syntax = self._syntax_registry.get_bookmark_syntax()
20
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
+
22
+ def _extract_bookmark_url(self, block: Block) -> str:
23
+ if not block.bookmark:
24
+ return ""
25
+ return block.bookmark.url or ""
@@ -0,0 +1,21 @@
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 BreadcrumbRenderer(BlockRenderer):
9
+ @override
10
+ def _can_handle(self, block: Block) -> bool:
11
+ return block.type == BlockType.BREADCRUMB
12
+
13
+ @override
14
+ async def _process(self, context: MarkdownRenderingContext) -> None:
15
+ syntax = self._syntax_registry.get_breadcrumb_syntax()
16
+ breadcrumb_markdown = syntax.start_delimiter
17
+
18
+ if context.indent_level > 0:
19
+ breadcrumb_markdown = context.indent_text(breadcrumb_markdown)
20
+
21
+ context.markdown_result = breadcrumb_markdown
@@ -0,0 +1,48 @@
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 BulletedListRenderer(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.BULLETED_LIST_ITEM
22
+
23
+ @override
24
+ async def _process(self, context: MarkdownRenderingContext) -> None:
25
+ markdown = await self._convert_bulleted_list_to_markdown(context.block)
26
+
27
+ if not markdown:
28
+ context.markdown_result = ""
29
+ return
30
+
31
+ syntax = self._syntax_registry.get_bulleted_list_syntax()
32
+ list_item_markdown = f"{syntax.start_delimiter}{markdown}"
33
+
34
+ if context.indent_level > 0:
35
+ list_item_markdown = context.indent_text(list_item_markdown)
36
+
37
+ children_markdown = await context.render_children_with_additional_indent(1)
38
+
39
+ if children_markdown:
40
+ context.markdown_result = f"{list_item_markdown}\n{children_markdown}"
41
+ else:
42
+ context.markdown_result = list_item_markdown
43
+
44
+ async def _convert_bulleted_list_to_markdown(self, block: Block) -> str | None:
45
+ if not block.bulleted_list_item or not block.bulleted_list_item.rich_text:
46
+ return None
47
+
48
+ return await self._rich_text_markdown_converter.to_markdown(block.bulleted_list_item.rich_text)
@@ -0,0 +1,65 @@
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 CalloutRenderer(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.CALLOUT
22
+
23
+ @override
24
+ async def _process(self, context: MarkdownRenderingContext) -> None:
25
+ icon = await self._extract_callout_icon(context.block)
26
+ content = await self._extract_callout_content(context.block)
27
+
28
+ if not content:
29
+ context.markdown_result = ""
30
+ return
31
+
32
+ syntax = self._syntax_registry.get_callout_syntax()
33
+
34
+ # Build callout structure
35
+ # Extract just the base part before the regex pattern
36
+ callout_type = syntax.start_delimiter.split()[1] # Gets "callout" from "::: callout"
37
+ callout_header = f"{self._syntax_registry.MULTI_LINE_BLOCK_DELIMITER} {callout_type}"
38
+ if icon:
39
+ callout_header = f"{self._syntax_registry.MULTI_LINE_BLOCK_DELIMITER} {callout_type} {icon}"
40
+
41
+ if context.indent_level > 0:
42
+ callout_header = context.indent_text(callout_header)
43
+
44
+ # Process children if they exist
45
+ children_markdown = await context.render_children()
46
+
47
+ callout_end = syntax.end_delimiter
48
+ if context.indent_level > 0:
49
+ callout_end = context.indent_text(callout_end)
50
+
51
+ # Combine content
52
+ if children_markdown:
53
+ context.markdown_result = f"{callout_header}\n{content}\n{children_markdown}\n{callout_end}"
54
+ else:
55
+ context.markdown_result = f"{callout_header}\n{content}\n{callout_end}"
56
+
57
+ async def _extract_callout_icon(self, block: Block) -> str:
58
+ if not block.callout or not block.callout.icon:
59
+ return ""
60
+ return block.callout.icon.emoji or ""
61
+
62
+ async def _extract_callout_content(self, block: Block) -> str:
63
+ if not block.callout or not block.callout.rich_text:
64
+ return ""
65
+ return await self._rich_text_markdown_converter.to_markdown(block.callout.rich_text)
@@ -0,0 +1,58 @@
1
+ from abc import abstractmethod
2
+ from typing import override
3
+
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 CaptionedBlockRenderer(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
+ @abstractmethod
21
+ async def _render_main_content(self, block: Block) -> str:
22
+ raise NotImplementedError
23
+
24
+ @override
25
+ async def _process(self, context: MarkdownRenderingContext) -> None:
26
+ main_content = await self._render_main_content(context.block)
27
+
28
+ if not main_content:
29
+ context.markdown_result = ""
30
+ return
31
+
32
+ caption_markdown = await self._render_caption(context.block)
33
+
34
+ final_markdown = f"{main_content}{caption_markdown}"
35
+
36
+ if context.indent_level > 0:
37
+ final_markdown = context.indent_text(final_markdown)
38
+
39
+ children_markdown = await context.render_children_with_additional_indent(1)
40
+
41
+ if children_markdown:
42
+ context.markdown_result = f"{final_markdown}\n{children_markdown}"
43
+ else:
44
+ context.markdown_result = final_markdown
45
+
46
+ async def _render_caption(self, block: Block) -> str:
47
+ block_data_object = getattr(block, block.type.value, None)
48
+
49
+ if not block_data_object or not hasattr(block_data_object, "caption"):
50
+ return ""
51
+
52
+ caption_rich_text = getattr(block_data_object, "caption", [])
53
+ if not caption_rich_text:
54
+ return ""
55
+
56
+ caption_markdown = await self._rich_text_markdown_converter.to_markdown(caption_rich_text)
57
+
58
+ return f"\n[caption] {caption_markdown}"
@@ -0,0 +1,34 @@
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.renderers.captioned_block import CaptionedBlockRenderer
6
+
7
+
8
+ class CodeRenderer(CaptionedBlockRenderer):
9
+ @override
10
+ def _can_handle(self, block: Block) -> bool:
11
+ return block.type == BlockType.CODE
12
+
13
+ @override
14
+ async def _render_main_content(self, block: Block) -> str:
15
+ language = self._extract_code_language(block)
16
+ code_content = await self._extract_code_content(block)
17
+
18
+ if not code_content:
19
+ return ""
20
+
21
+ syntax = self._syntax_registry.get_code_syntax()
22
+ code_start = f"{syntax.start_delimiter}{language}"
23
+ code_end = syntax.end_delimiter
24
+ return f"{code_start}\n{code_content}\n{code_end}"
25
+
26
+ def _extract_code_language(self, block: Block) -> str:
27
+ if not block.code or not block.code.language:
28
+ return ""
29
+ return block.code.language.value
30
+
31
+ async def _extract_code_content(self, block: Block) -> str:
32
+ if not block.code or not block.code.rich_text:
33
+ return ""
34
+ return await self._rich_text_markdown_converter.to_markdown(block.code.rich_text)
@@ -0,0 +1,44 @@
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 ColumnRenderer(BlockRenderer):
10
+ @override
11
+ def _can_handle(self, block: Block) -> bool:
12
+ return block.type == BlockType.COLUMN
13
+
14
+ @override
15
+ async def _process(self, context: MarkdownRenderingContext) -> None:
16
+ column_start = self._extract_column_start(context.block)
17
+
18
+ if context.indent_level > 0:
19
+ column_start = context.indent_text(column_start)
20
+
21
+ children_markdown = await context.render_children()
22
+
23
+ syntax = self._syntax_registry.get_column_syntax()
24
+ column_end = syntax.end_delimiter
25
+ if context.indent_level > 0:
26
+ column_end = context.indent_text(column_end, spaces=context.indent_level * 4)
27
+
28
+ if children_markdown:
29
+ context.markdown_result = f"{column_start}\n{children_markdown}\n{column_end}"
30
+ else:
31
+ context.markdown_result = f"{column_start}\n{column_end}"
32
+
33
+ def _extract_column_start(self, block: Block) -> str:
34
+ syntax = self._syntax_registry.get_column_syntax()
35
+ base_start = syntax.start_delimiter
36
+
37
+ if not block.column:
38
+ return base_start
39
+
40
+ width_ratio = block.column.width_ratio
41
+ if width_ratio:
42
+ return f"{base_start} {width_ratio}"
43
+ else:
44
+ return base_start
@@ -0,0 +1,31 @@
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 ColumnListRenderer(BlockRenderer):
10
+ @override
11
+ def _can_handle(self, block: Block) -> bool:
12
+ return block.type == BlockType.COLUMN_LIST
13
+
14
+ @override
15
+ async def _process(self, context: MarkdownRenderingContext) -> None:
16
+ syntax = self._syntax_registry.get_column_list_syntax()
17
+ column_list_start = syntax.start_delimiter
18
+
19
+ if context.indent_level > 0:
20
+ column_list_start = context.indent_text(column_list_start)
21
+
22
+ children_markdown = await context.render_children()
23
+
24
+ column_list_end = syntax.end_delimiter
25
+ if context.indent_level > 0:
26
+ column_list_end = context.indent_text(column_list_end)
27
+
28
+ if children_markdown:
29
+ context.markdown_result = f"{column_list_start}\n{children_markdown}\n{column_list_end}"
30
+ else:
31
+ context.markdown_result = f"{column_list_start}\n{column_list_end}"
@@ -0,0 +1,22 @@
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 DividerRenderer(BlockRenderer):
10
+ @override
11
+ def _can_handle(self, block: Block) -> bool:
12
+ return block.type == BlockType.DIVIDER
13
+
14
+ @override
15
+ async def _process(self, context: MarkdownRenderingContext) -> None:
16
+ syntax = self._syntax_registry.get_divider_syntax()
17
+ divider_markdown = syntax.start_delimiter
18
+
19
+ if context.indent_level > 0:
20
+ divider_markdown = context.indent_text(divider_markdown)
21
+
22
+ context.markdown_result = divider_markdown
@@ -0,0 +1,25 @@
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 EmbedRenderer(CaptionedBlockRenderer):
8
+ @override
9
+ def _can_handle(self, block: Block) -> bool:
10
+ return block.type == BlockType.EMBED
11
+
12
+ @override
13
+ async def _render_main_content(self, block: Block) -> str:
14
+ url = self._extract_embed_url(block)
15
+
16
+ if not url:
17
+ return ""
18
+
19
+ syntax = self._syntax_registry.get_embed_syntax()
20
+ return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
21
+
22
+ def _extract_embed_url(self, block: Block) -> str:
23
+ if not block.embed:
24
+ return ""
25
+ return block.embed.url or ""
@@ -0,0 +1,37 @@
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 EquationRenderer(BlockRenderer):
9
+ @override
10
+ def _can_handle(self, block: Block) -> bool:
11
+ return block.type == BlockType.EQUATION
12
+
13
+ @override
14
+ async def _process(self, context: MarkdownRenderingContext) -> None:
15
+ expression = self._extract_equation_expression(context.block)
16
+
17
+ if not expression:
18
+ context.markdown_result = ""
19
+ return
20
+
21
+ syntax = self._syntax_registry.get_equation_syntax()
22
+ equation_markdown = f"{syntax.start_delimiter}{expression}{syntax.end_delimiter}"
23
+
24
+ if context.indent_level > 0:
25
+ equation_markdown = context.indent_text(equation_markdown)
26
+
27
+ children_markdown = await context.render_children_with_additional_indent(1)
28
+
29
+ if children_markdown:
30
+ context.markdown_result = f"{equation_markdown}\n{children_markdown}"
31
+ else:
32
+ context.markdown_result = equation_markdown
33
+
34
+ def _extract_equation_expression(self, block: Block) -> str:
35
+ if not block.equation:
36
+ return ""
37
+ return block.equation.expression or ""
@@ -0,0 +1,24 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import Block
4
+ from notionary.page.content.renderer.context import MarkdownRenderingContext
5
+ from notionary.page.content.renderer.renderers.base import BlockRenderer
6
+ from notionary.utils.mixins.logging import LoggingMixin
7
+
8
+
9
+ class FallbackRenderer(BlockRenderer, LoggingMixin):
10
+ @override
11
+ def _can_handle(self, block: Block) -> bool:
12
+ return True
13
+
14
+ @override
15
+ async def _process(self, context: MarkdownRenderingContext) -> None:
16
+ block_type = context.block.type.value if context.block.type else "unknown"
17
+ self.logger.warning(f"No handler found for block type: {block_type}")
18
+
19
+ fallback_message = f"[Unsupported block type: {block_type}]"
20
+
21
+ if context.indent_level > 0:
22
+ fallback_message = context.indent_text(fallback_message)
23
+
24
+ context.markdown_result = fallback_message