notionary 0.2.27__py3-none-any.whl → 0.3.0__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 (395) hide show
  1. notionary/__init__.py +5 -20
  2. notionary/blocks/__init__.py +4 -4
  3. notionary/blocks/client.py +90 -216
  4. notionary/blocks/enums.py +167 -0
  5. notionary/blocks/rich_text/markdown_rich_text_converter.py +280 -0
  6. notionary/blocks/rich_text/models.py +178 -0
  7. notionary/blocks/rich_text/name_id_resolver/__init__.py +13 -0
  8. notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
  9. notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
  10. notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
  11. notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
  12. notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
  13. notionary/blocks/rich_text/rich_text_markdown_converter.py +144 -0
  14. notionary/blocks/rich_text/rich_text_patterns.py +42 -0
  15. notionary/blocks/schemas.py +778 -0
  16. notionary/comments/__init__.py +1 -22
  17. notionary/comments/client.py +52 -187
  18. notionary/comments/factory.py +38 -0
  19. notionary/comments/models.py +5 -127
  20. notionary/comments/schemas.py +240 -0
  21. notionary/comments/service.py +34 -0
  22. notionary/data_source/http/client.py +11 -0
  23. notionary/data_source/http/data_source_instance_client.py +104 -0
  24. notionary/data_source/properties/schemas.py +402 -0
  25. notionary/data_source/query/builder.py +448 -0
  26. notionary/data_source/query/resolver.py +114 -0
  27. notionary/data_source/query/schema.py +302 -0
  28. notionary/data_source/query/validator.py +73 -0
  29. notionary/data_source/schema/registry.py +104 -0
  30. notionary/data_source/schema/service.py +136 -0
  31. notionary/data_source/schemas.py +27 -0
  32. notionary/data_source/service.py +377 -0
  33. notionary/database/client.py +30 -135
  34. notionary/database/database_metadata_update_client.py +19 -0
  35. notionary/database/schemas.py +29 -0
  36. notionary/database/service.py +168 -0
  37. notionary/exceptions/__init__.py +33 -0
  38. notionary/exceptions/api.py +41 -0
  39. notionary/exceptions/base.py +2 -0
  40. notionary/exceptions/block_parsing.py +16 -0
  41. notionary/exceptions/data_source/__init__.py +6 -0
  42. notionary/exceptions/data_source/builder.py +182 -0
  43. notionary/exceptions/data_source/properties.py +34 -0
  44. notionary/exceptions/properties.py +58 -0
  45. notionary/exceptions/search.py +57 -0
  46. notionary/file_upload/client.py +18 -30
  47. notionary/file_upload/models.py +7 -8
  48. notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
  49. notionary/http/client.py +204 -0
  50. notionary/http/models.py +50 -0
  51. notionary/page/blocks/client.py +1 -0
  52. notionary/page/content/factory.py +73 -0
  53. notionary/page/content/markdown/__init__.py +5 -0
  54. notionary/page/content/markdown/builder.py +226 -0
  55. notionary/page/content/markdown/nodes/__init__.py +52 -0
  56. notionary/page/content/markdown/nodes/audio.py +23 -0
  57. notionary/page/content/markdown/nodes/base.py +12 -0
  58. notionary/page/content/markdown/nodes/bookmark.py +25 -0
  59. notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
  60. notionary/page/content/markdown/nodes/bulleted_list.py +41 -0
  61. notionary/page/content/markdown/nodes/callout.py +34 -0
  62. notionary/page/content/markdown/nodes/code.py +28 -0
  63. notionary/page/content/markdown/nodes/columns.py +69 -0
  64. notionary/page/content/markdown/nodes/container.py +64 -0
  65. notionary/page/content/markdown/nodes/divider.py +14 -0
  66. notionary/page/content/markdown/nodes/embed.py +23 -0
  67. notionary/page/content/markdown/nodes/equation.py +19 -0
  68. notionary/page/content/markdown/nodes/file.py +23 -0
  69. notionary/page/content/markdown/nodes/heading.py +36 -0
  70. notionary/page/content/markdown/nodes/image.py +23 -0
  71. notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
  72. notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
  73. notionary/page/content/markdown/nodes/numbered_list.py +38 -0
  74. notionary/page/content/markdown/nodes/paragraph.py +14 -0
  75. notionary/page/content/markdown/nodes/pdf.py +23 -0
  76. notionary/page/content/markdown/nodes/quote.py +27 -0
  77. notionary/page/content/markdown/nodes/space.py +14 -0
  78. notionary/page/content/markdown/nodes/table.py +45 -0
  79. notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
  80. notionary/page/content/markdown/nodes/todo.py +38 -0
  81. notionary/page/content/markdown/nodes/toggle.py +27 -0
  82. notionary/page/content/markdown/nodes/video.py +23 -0
  83. notionary/page/content/parser/context.py +126 -0
  84. notionary/page/content/parser/factory.py +210 -0
  85. notionary/page/content/parser/parsers/__init__.py +58 -0
  86. notionary/page/content/parser/parsers/audio.py +40 -0
  87. notionary/page/content/parser/parsers/base.py +30 -0
  88. notionary/page/content/parser/parsers/bookmark.py +33 -0
  89. notionary/page/content/parser/parsers/breadcrumb.py +33 -0
  90. notionary/page/content/parser/parsers/bulleted_list.py +85 -0
  91. notionary/page/content/parser/parsers/callout.py +100 -0
  92. notionary/page/content/parser/parsers/caption.py +55 -0
  93. notionary/page/content/parser/parsers/code.py +81 -0
  94. notionary/page/content/parser/parsers/column.py +76 -0
  95. notionary/page/content/parser/parsers/column_list.py +81 -0
  96. notionary/page/content/parser/parsers/divider.py +33 -0
  97. notionary/page/content/parser/parsers/embed.py +33 -0
  98. notionary/page/content/parser/parsers/equation.py +65 -0
  99. notionary/page/content/parser/parsers/file.py +42 -0
  100. notionary/page/content/parser/parsers/heading.py +115 -0
  101. notionary/page/content/parser/parsers/image.py +42 -0
  102. notionary/page/content/parser/parsers/numbered_list.py +89 -0
  103. notionary/page/content/parser/parsers/paragraph.py +37 -0
  104. notionary/page/content/parser/parsers/pdf.py +42 -0
  105. notionary/page/content/parser/parsers/quote.py +125 -0
  106. notionary/page/content/parser/parsers/space.py +41 -0
  107. notionary/page/content/parser/parsers/table.py +144 -0
  108. notionary/page/content/parser/parsers/table_of_contents.py +32 -0
  109. notionary/page/content/parser/parsers/todo.py +96 -0
  110. notionary/page/content/parser/parsers/toggle.py +70 -0
  111. notionary/page/content/parser/parsers/video.py +42 -0
  112. notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
  113. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +95 -0
  114. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +114 -0
  115. notionary/page/content/parser/post_processing/port.py +9 -0
  116. notionary/page/content/parser/post_processing/service.py +16 -0
  117. notionary/page/content/parser/pre_processsing/handlers/__init__.py +11 -0
  118. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +130 -0
  119. notionary/page/content/parser/pre_processsing/handlers/indentation.py +84 -0
  120. notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
  121. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +73 -0
  122. notionary/page/content/parser/pre_processsing/service.py +15 -0
  123. notionary/page/content/parser/service.py +78 -0
  124. notionary/page/content/renderer/context.py +51 -0
  125. notionary/page/content/renderer/factory.py +231 -0
  126. notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
  127. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
  128. notionary/page/content/renderer/post_processing/port.py +7 -0
  129. notionary/page/content/renderer/post_processing/service.py +15 -0
  130. notionary/page/content/renderer/renderers/__init__.py +55 -0
  131. notionary/page/content/renderer/renderers/audio.py +31 -0
  132. notionary/page/content/renderer/renderers/base.py +31 -0
  133. notionary/page/content/renderer/renderers/bookmark.py +25 -0
  134. notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
  135. notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
  136. notionary/page/content/renderer/renderers/callout.py +50 -0
  137. notionary/page/content/renderer/renderers/captioned_block.py +58 -0
  138. notionary/page/content/renderer/renderers/code.py +34 -0
  139. notionary/page/content/renderer/renderers/column.py +53 -0
  140. notionary/page/content/renderer/renderers/column_list.py +44 -0
  141. notionary/page/content/renderer/renderers/divider.py +22 -0
  142. notionary/page/content/renderer/renderers/embed.py +25 -0
  143. notionary/page/content/renderer/renderers/equation.py +37 -0
  144. notionary/page/content/renderer/renderers/fallback.py +24 -0
  145. notionary/page/content/renderer/renderers/file.py +40 -0
  146. notionary/page/content/renderer/renderers/heading.py +95 -0
  147. notionary/page/content/renderer/renderers/image.py +31 -0
  148. notionary/page/content/renderer/renderers/numbered_list.py +42 -0
  149. notionary/page/content/renderer/renderers/paragraph.py +40 -0
  150. notionary/page/content/renderer/renderers/pdf.py +31 -0
  151. notionary/page/content/renderer/renderers/quote.py +49 -0
  152. notionary/page/content/renderer/renderers/table.py +115 -0
  153. notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
  154. notionary/page/content/renderer/renderers/table_row.py +17 -0
  155. notionary/page/content/renderer/renderers/todo.py +56 -0
  156. notionary/page/content/renderer/renderers/toggle.py +52 -0
  157. notionary/page/content/renderer/renderers/video.py +31 -0
  158. notionary/page/content/renderer/service.py +50 -0
  159. notionary/page/content/service.py +68 -0
  160. notionary/page/content/syntax/__init__.py +4 -0
  161. notionary/page/content/syntax/grammar.py +10 -0
  162. notionary/page/content/syntax/models.py +66 -0
  163. notionary/page/content/syntax/registry.py +393 -0
  164. notionary/page/page_context.py +7 -16
  165. notionary/page/page_http_client.py +15 -0
  166. notionary/page/page_metadata_update_client.py +19 -0
  167. notionary/page/properties/client.py +144 -0
  168. notionary/page/properties/factory.py +26 -0
  169. notionary/page/properties/models.py +308 -0
  170. notionary/page/properties/service.py +261 -0
  171. notionary/page/schemas.py +13 -0
  172. notionary/page/service.py +225 -0
  173. notionary/shared/entity/client.py +29 -0
  174. notionary/shared/entity/dto_parsers.py +53 -0
  175. notionary/shared/entity/entity_metadata_update_client.py +41 -0
  176. notionary/shared/entity/schemas.py +45 -0
  177. notionary/shared/entity/service.py +171 -0
  178. notionary/shared/models/cover.py +20 -0
  179. notionary/shared/models/file.py +21 -0
  180. notionary/shared/models/icon.py +28 -0
  181. notionary/shared/models/parent.py +41 -0
  182. notionary/shared/properties/type.py +30 -0
  183. notionary/shared/typings.py +3 -0
  184. notionary/user/__init__.py +4 -8
  185. notionary/user/base.py +138 -0
  186. notionary/user/bot.py +70 -0
  187. notionary/user/client.py +22 -111
  188. notionary/user/person.py +41 -0
  189. notionary/user/schemas.py +67 -0
  190. notionary/user/service.py +65 -0
  191. notionary/utils/date.py +51 -0
  192. notionary/utils/decorators.py +122 -0
  193. notionary/utils/fuzzy.py +68 -0
  194. notionary/utils/mixins/logging.py +58 -0
  195. notionary/utils/pagination.py +100 -0
  196. notionary/utils/uuid_utils.py +20 -0
  197. notionary/workspace/__init__.py +4 -0
  198. notionary/workspace/client.py +62 -0
  199. notionary/workspace/query/__init__.py +3 -0
  200. notionary/workspace/query/builder.py +60 -0
  201. notionary/workspace/query/models.py +61 -0
  202. notionary/workspace/query/service.py +100 -0
  203. notionary/workspace/schemas.py +21 -0
  204. notionary/workspace/service.py +116 -0
  205. notionary-0.3.0.dist-info/METADATA +201 -0
  206. notionary-0.3.0.dist-info/RECORD +209 -0
  207. {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info}/WHEEL +1 -1
  208. {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info/licenses}/LICENSE +9 -9
  209. notionary/base_notion_client.py +0 -219
  210. notionary/blocks/_bootstrap.py +0 -271
  211. notionary/blocks/audio/__init__.py +0 -11
  212. notionary/blocks/audio/audio_element.py +0 -158
  213. notionary/blocks/audio/audio_markdown_node.py +0 -24
  214. notionary/blocks/audio/audio_models.py +0 -10
  215. notionary/blocks/base_block_element.py +0 -42
  216. notionary/blocks/bookmark/__init__.py +0 -12
  217. notionary/blocks/bookmark/bookmark_element.py +0 -83
  218. notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
  219. notionary/blocks/bookmark/bookmark_models.py +0 -15
  220. notionary/blocks/breadcrumbs/__init__.py +0 -15
  221. notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
  222. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
  223. notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
  224. notionary/blocks/bulleted_list/__init__.py +0 -15
  225. notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
  226. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
  227. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
  228. notionary/blocks/callout/__init__.py +0 -12
  229. notionary/blocks/callout/callout_element.py +0 -99
  230. notionary/blocks/callout/callout_markdown_node.py +0 -19
  231. notionary/blocks/callout/callout_models.py +0 -33
  232. notionary/blocks/child_database/__init__.py +0 -14
  233. notionary/blocks/child_database/child_database_element.py +0 -59
  234. notionary/blocks/child_database/child_database_models.py +0 -12
  235. notionary/blocks/child_page/__init__.py +0 -9
  236. notionary/blocks/child_page/child_page_element.py +0 -94
  237. notionary/blocks/child_page/child_page_models.py +0 -12
  238. notionary/blocks/code/__init__.py +0 -11
  239. notionary/blocks/code/code_element.py +0 -149
  240. notionary/blocks/code/code_markdown_node.py +0 -80
  241. notionary/blocks/code/code_models.py +0 -94
  242. notionary/blocks/column/__init__.py +0 -25
  243. notionary/blocks/column/column_element.py +0 -65
  244. notionary/blocks/column/column_list_element.py +0 -52
  245. notionary/blocks/column/column_list_markdown_node.py +0 -34
  246. notionary/blocks/column/column_markdown_node.py +0 -42
  247. notionary/blocks/column/column_models.py +0 -26
  248. notionary/blocks/divider/__init__.py +0 -12
  249. notionary/blocks/divider/divider_element.py +0 -41
  250. notionary/blocks/divider/divider_markdown_node.py +0 -11
  251. notionary/blocks/divider/divider_models.py +0 -12
  252. notionary/blocks/embed/__init__.py +0 -12
  253. notionary/blocks/embed/embed_element.py +0 -98
  254. notionary/blocks/embed/embed_markdown_node.py +0 -19
  255. notionary/blocks/embed/embed_models.py +0 -14
  256. notionary/blocks/equation/__init__.py +0 -13
  257. notionary/blocks/equation/equation_element.py +0 -133
  258. notionary/blocks/equation/equation_element_markdown_node.py +0 -23
  259. notionary/blocks/equation/equation_models.py +0 -11
  260. notionary/blocks/file/__init__.py +0 -23
  261. notionary/blocks/file/file_element.py +0 -133
  262. notionary/blocks/file/file_element_markdown_node.py +0 -24
  263. notionary/blocks/file/file_element_models.py +0 -39
  264. notionary/blocks/heading/__init__.py +0 -19
  265. notionary/blocks/heading/heading_element.py +0 -112
  266. notionary/blocks/heading/heading_markdown_node.py +0 -16
  267. notionary/blocks/heading/heading_models.py +0 -29
  268. notionary/blocks/image_block/__init__.py +0 -11
  269. notionary/blocks/image_block/image_element.py +0 -130
  270. notionary/blocks/image_block/image_markdown_node.py +0 -25
  271. notionary/blocks/image_block/image_models.py +0 -10
  272. notionary/blocks/markdown/markdown_builder.py +0 -525
  273. notionary/blocks/markdown/markdown_document_model.py +0 -0
  274. notionary/blocks/markdown/markdown_node.py +0 -25
  275. notionary/blocks/mixins/captions/__init__.py +0 -4
  276. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
  277. notionary/blocks/mixins/captions/caption_mixin.py +0 -92
  278. notionary/blocks/mixins/file_upload/__init__.py +0 -3
  279. notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
  280. notionary/blocks/models.py +0 -174
  281. notionary/blocks/numbered_list/__init__.py +0 -16
  282. notionary/blocks/numbered_list/numbered_list_element.py +0 -65
  283. notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
  284. notionary/blocks/numbered_list/numbered_list_models.py +0 -17
  285. notionary/blocks/paragraph/__init__.py +0 -15
  286. notionary/blocks/paragraph/paragraph_element.py +0 -58
  287. notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
  288. notionary/blocks/paragraph/paragraph_models.py +0 -16
  289. notionary/blocks/pdf/__init__.py +0 -11
  290. notionary/blocks/pdf/pdf_element.py +0 -146
  291. notionary/blocks/pdf/pdf_markdown_node.py +0 -24
  292. notionary/blocks/pdf/pdf_models.py +0 -11
  293. notionary/blocks/quote/__init__.py +0 -14
  294. notionary/blocks/quote/quote_element.py +0 -75
  295. notionary/blocks/quote/quote_markdown_node.py +0 -16
  296. notionary/blocks/quote/quote_models.py +0 -18
  297. notionary/blocks/registry/__init__.py +0 -3
  298. notionary/blocks/registry/block_registry.py +0 -150
  299. notionary/blocks/rich_text/__init__.py +0 -33
  300. notionary/blocks/rich_text/rich_text_models.py +0 -221
  301. notionary/blocks/rich_text/text_inline_formatter.py +0 -456
  302. notionary/blocks/syntax_prompt_builder.py +0 -137
  303. notionary/blocks/table/__init__.py +0 -19
  304. notionary/blocks/table/table_element.py +0 -225
  305. notionary/blocks/table/table_markdown_node.py +0 -42
  306. notionary/blocks/table/table_models.py +0 -28
  307. notionary/blocks/table_of_contents/__init__.py +0 -17
  308. notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
  309. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
  310. notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
  311. notionary/blocks/todo/__init__.py +0 -12
  312. notionary/blocks/todo/todo_element.py +0 -81
  313. notionary/blocks/todo/todo_markdown_node.py +0 -21
  314. notionary/blocks/todo/todo_models.py +0 -18
  315. notionary/blocks/toggle/__init__.py +0 -12
  316. notionary/blocks/toggle/toggle_element.py +0 -112
  317. notionary/blocks/toggle/toggle_markdown_node.py +0 -31
  318. notionary/blocks/toggle/toggle_models.py +0 -17
  319. notionary/blocks/toggleable_heading/__init__.py +0 -11
  320. notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
  321. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
  322. notionary/blocks/types.py +0 -130
  323. notionary/blocks/video/__init__.py +0 -11
  324. notionary/blocks/video/video_element.py +0 -187
  325. notionary/blocks/video/video_element_models.py +0 -10
  326. notionary/blocks/video/video_markdown_node.py +0 -26
  327. notionary/database/__init__.py +0 -4
  328. notionary/database/database.py +0 -480
  329. notionary/database/database_filter_builder.py +0 -173
  330. notionary/database/database_provider.py +0 -227
  331. notionary/database/exceptions.py +0 -13
  332. notionary/database/models.py +0 -337
  333. notionary/database/notion_database.py +0 -487
  334. notionary/file_upload/__init__.py +0 -7
  335. notionary/page/client.py +0 -124
  336. notionary/page/markdown_whitespace_processor.py +0 -129
  337. notionary/page/models.py +0 -322
  338. notionary/page/notion_page.py +0 -712
  339. notionary/page/page_content_deleting_service.py +0 -117
  340. notionary/page/page_content_writer.py +0 -80
  341. notionary/page/property_formatter.py +0 -99
  342. notionary/page/reader/handler/__init__.py +0 -19
  343. notionary/page/reader/handler/base_block_renderer.py +0 -44
  344. notionary/page/reader/handler/block_processing_context.py +0 -35
  345. notionary/page/reader/handler/block_rendering_context.py +0 -48
  346. notionary/page/reader/handler/column_list_renderer.py +0 -51
  347. notionary/page/reader/handler/column_renderer.py +0 -60
  348. notionary/page/reader/handler/equation_renderer.py +0 -0
  349. notionary/page/reader/handler/line_renderer.py +0 -73
  350. notionary/page/reader/handler/numbered_list_renderer.py +0 -85
  351. notionary/page/reader/handler/toggle_renderer.py +0 -69
  352. notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
  353. notionary/page/reader/page_content_retriever.py +0 -81
  354. notionary/page/search_filter_builder.py +0 -132
  355. notionary/page/utils.py +0 -60
  356. notionary/page/writer/handler/__init__.py +0 -24
  357. notionary/page/writer/handler/code_handler.py +0 -72
  358. notionary/page/writer/handler/column_handler.py +0 -141
  359. notionary/page/writer/handler/column_list_handler.py +0 -139
  360. notionary/page/writer/handler/equation_handler.py +0 -74
  361. notionary/page/writer/handler/line_handler.py +0 -35
  362. notionary/page/writer/handler/line_processing_context.py +0 -54
  363. notionary/page/writer/handler/regular_line_handler.py +0 -86
  364. notionary/page/writer/handler/table_handler.py +0 -66
  365. notionary/page/writer/handler/toggle_handler.py +0 -159
  366. notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
  367. notionary/page/writer/markdown_to_notion_converter.py +0 -139
  368. notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
  369. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  370. notionary/page/writer/notion_text_length_processor.py +0 -150
  371. notionary/schemas/__init__.py +0 -3
  372. notionary/schemas/base.py +0 -73
  373. notionary/shared/__init__.py +0 -3
  374. notionary/shared/name_to_id_resolver.py +0 -203
  375. notionary/telemetry/__init__.py +0 -19
  376. notionary/telemetry/service.py +0 -136
  377. notionary/telemetry/views.py +0 -73
  378. notionary/user/base_notion_user.py +0 -53
  379. notionary/user/models.py +0 -84
  380. notionary/user/notion_bot_user.py +0 -226
  381. notionary/user/notion_user.py +0 -255
  382. notionary/user/notion_user_manager.py +0 -101
  383. notionary/util/__init__.py +0 -15
  384. notionary/util/concurrency_limiter.py +0 -0
  385. notionary/util/factory_decorator.py +0 -0
  386. notionary/util/factory_only.py +0 -37
  387. notionary/util/fuzzy.py +0 -75
  388. notionary/util/logging_mixin.py +0 -59
  389. notionary/util/page_id_utils.py +0 -27
  390. notionary/util/singleton.py +0 -18
  391. notionary/util/singleton_metaclass.py +0 -22
  392. notionary/workspace.py +0 -105
  393. notionary-0.2.27.dist-info/METADATA +0 -270
  394. notionary-0.2.27.dist-info/RECORD +0 -202
  395. /notionary/{database → user}/factory.py +0 -0
@@ -0,0 +1,81 @@
1
+ import re
2
+ from typing import override
3
+
4
+ from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
5
+ from notionary.blocks.rich_text.models import RichText
6
+ from notionary.blocks.schemas import CodeData, CodingLanguage, CreateCodeBlock
7
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
8
+ from notionary.page.content.syntax import SyntaxRegistry
9
+
10
+
11
+ class CodeParser(LineParser):
12
+ DEFAULT_LANGUAGE = CodingLanguage.PLAIN_TEXT
13
+
14
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
15
+ super().__init__(syntax_registry)
16
+ self._syntax = syntax_registry.get_code_syntax()
17
+ self._rich_text_converter = rich_text_converter
18
+ self._code_start_pattern = self._syntax.regex_pattern
19
+ self._code_end_pattern = self._syntax.end_regex_pattern or re.compile(r"^```\s*$")
20
+
21
+ @override
22
+ def _can_handle(self, context: BlockParsingContext) -> bool:
23
+ if context.is_inside_parent_context():
24
+ return False
25
+ return self._is_code_fence_start(context.line)
26
+
27
+ @override
28
+ async def _process(self, context: BlockParsingContext) -> None:
29
+ code_lines = self._collect_code_lines(context)
30
+ lines_consumed = self._count_lines_consumed(context)
31
+
32
+ block = await self._create_code_block(opening_line=context.line, code_lines=code_lines)
33
+ if not block:
34
+ return
35
+
36
+ context.lines_consumed = lines_consumed
37
+ context.result_blocks.append(block)
38
+
39
+ def _is_code_fence_start(self, line: str) -> bool:
40
+ return self._code_start_pattern.match(line) is not None
41
+
42
+ def _is_code_fence_end(self, line: str) -> bool:
43
+ return self._code_end_pattern.match(line) is not None
44
+
45
+ def _collect_code_lines(self, context: BlockParsingContext) -> list[str]:
46
+ code_lines = []
47
+ for line in context.get_remaining_lines():
48
+ if self._is_code_fence_end(line):
49
+ break
50
+ code_lines.append(line)
51
+ return code_lines
52
+
53
+ def _count_lines_consumed(self, context: BlockParsingContext) -> int:
54
+ for line_index, line in enumerate(context.get_remaining_lines()):
55
+ if self._is_code_fence_end(line):
56
+ return line_index + 1
57
+ return len(context.get_remaining_lines())
58
+
59
+ async def _create_code_block(self, opening_line: str, code_lines: list[str]) -> CreateCodeBlock | None:
60
+ match = self._code_start_pattern.match(opening_line)
61
+ if not match:
62
+ return None
63
+
64
+ language = self._parse_language(match.group(1))
65
+ rich_text = await self._create_rich_text_from_code(code_lines)
66
+
67
+ code_data = CodeData(rich_text=rich_text, language=language, caption=[])
68
+ return CreateCodeBlock(code=code_data)
69
+
70
+ def _parse_language(self, language_str: str | None) -> CodingLanguage:
71
+ return CodingLanguage.from_string(language_str, default=self.DEFAULT_LANGUAGE)
72
+
73
+ async def _create_rich_text_from_code(self, code_lines: list[str]) -> list[RichText]:
74
+ content = "\n".join(code_lines) if code_lines else ""
75
+ return await self._rich_text_converter.to_rich_text(content)
76
+
77
+ def _is_code_fence_start(self, line: str) -> bool:
78
+ return self._code_start_pattern.match(line) is not None
79
+
80
+ def _is_code_fence_end(self, line: str) -> bool:
81
+ return self._code_end_pattern.match(line) is not None
@@ -0,0 +1,76 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import CreateColumnBlock, CreateColumnData
4
+ from notionary.page.content.parser.parsers.base import (
5
+ BlockParsingContext,
6
+ LineParser,
7
+ )
8
+ from notionary.page.content.syntax import SyntaxRegistry
9
+
10
+
11
+ class ColumnParser(LineParser):
12
+ MIN_WIDTH_RATIO = 0
13
+ MAX_WIDTH_RATIO = 1.0
14
+
15
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
16
+ super().__init__(syntax_registry)
17
+ self._syntax = syntax_registry.get_column_syntax()
18
+
19
+ @override
20
+ def _can_handle(self, context: BlockParsingContext) -> bool:
21
+ return self._is_column_start(context)
22
+
23
+ @override
24
+ async def _process(self, context: BlockParsingContext) -> None:
25
+ if self._is_column_start(context):
26
+ await self._process_column(context)
27
+
28
+ def _is_column_start(self, context: BlockParsingContext) -> bool:
29
+ return self._syntax.regex_pattern.match(context.line) is not None
30
+
31
+ async def _process_column(self, context: BlockParsingContext) -> None:
32
+ block = self._create_column_block(context.line)
33
+ if not block:
34
+ return
35
+
36
+ await self._populate_children(block, context)
37
+ context.result_blocks.append(block)
38
+
39
+ def _create_column_block(self, line: str) -> CreateColumnBlock | None:
40
+ match = self._syntax.regex_pattern.match(line)
41
+ if not match:
42
+ return None
43
+
44
+ width_ratio = self._parse_width_ratio(match.group(1))
45
+ column_data = CreateColumnData(width_ratio=width_ratio, children=[])
46
+
47
+ return CreateColumnBlock(column=column_data)
48
+
49
+ def _parse_width_ratio(self, ratio_str: str | None) -> float | None:
50
+ if not ratio_str:
51
+ return None
52
+
53
+ try:
54
+ width_ratio = float(ratio_str)
55
+ return width_ratio if self._is_valid_width_ratio(width_ratio) else None
56
+ except ValueError:
57
+ return None
58
+
59
+ def _is_valid_width_ratio(self, width_ratio: float) -> bool:
60
+ return self.MIN_WIDTH_RATIO < width_ratio <= self.MAX_WIDTH_RATIO
61
+
62
+ async def _populate_children(self, block: CreateColumnBlock, context: BlockParsingContext) -> None:
63
+ parent_indent_level = context.get_line_indentation_level()
64
+ child_lines = context.collect_indented_child_lines(parent_indent_level)
65
+
66
+ if not child_lines:
67
+ return
68
+
69
+ child_blocks = await self._parse_indented_children(child_lines, context)
70
+ block.column.children = child_blocks
71
+ context.lines_consumed = len(child_lines)
72
+
73
+ async def _parse_indented_children(self, child_lines: list[str], context: BlockParsingContext) -> list:
74
+ stripped_lines = context.strip_indentation_level(child_lines, levels=1)
75
+ child_markdown = "\n".join(stripped_lines)
76
+ return await context.parse_nested_markdown(child_markdown)
@@ -0,0 +1,81 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.enums import BlockType
4
+ from notionary.blocks.schemas import BlockCreatePayload, CreateColumnListBlock, CreateColumnListData
5
+ from notionary.page.content.parser.parsers.base import (
6
+ BlockParsingContext,
7
+ LineParser,
8
+ )
9
+ from notionary.page.content.syntax import SyntaxRegistry
10
+
11
+
12
+ class ColumnListParser(LineParser):
13
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
14
+ super().__init__(syntax_registry)
15
+ self._syntax = syntax_registry.get_column_list_syntax()
16
+
17
+ @override
18
+ def _can_handle(self, context: BlockParsingContext) -> bool:
19
+ return self._is_column_list_start(context)
20
+
21
+ @override
22
+ async def _process(self, context: BlockParsingContext) -> None:
23
+ if self._is_column_list_start(context):
24
+ await self._process_column_list(context)
25
+
26
+ def _is_column_list_start(self, context: BlockParsingContext) -> bool:
27
+ return self._syntax.regex_pattern.match(context.line) is not None
28
+
29
+ async def _process_column_list(self, context: BlockParsingContext) -> None:
30
+ block = self._create_column_list_block()
31
+ await self._populate_columns(block, context)
32
+ context.result_blocks.append(block)
33
+
34
+ def _create_column_list_block(self) -> CreateColumnListBlock:
35
+ column_list_data = CreateColumnListData(children=[])
36
+ return CreateColumnListBlock(column_list=column_list_data)
37
+
38
+ async def _populate_columns(self, block: CreateColumnListBlock, context: BlockParsingContext) -> None:
39
+ parent_indent_level = context.get_line_indentation_level()
40
+ child_lines = self._collect_children_allowing_empty_lines(context, parent_indent_level)
41
+
42
+ if not child_lines:
43
+ return
44
+
45
+ column_blocks = await self._parse_column_children(child_lines, context)
46
+ block.column_list.children = column_blocks
47
+ context.lines_consumed = len(child_lines)
48
+
49
+ async def _parse_column_children(self, child_lines: list[str], context: BlockParsingContext) -> list:
50
+ stripped_lines = context.strip_indentation_level(child_lines, levels=1)
51
+ child_markdown = "\n".join(stripped_lines)
52
+ parsed_blocks = await context.parse_nested_markdown(child_markdown)
53
+ return self._extract_column_blocks(parsed_blocks)
54
+
55
+ def _collect_children_allowing_empty_lines(
56
+ self, context: BlockParsingContext, parent_indent_level: int
57
+ ) -> list[str]:
58
+ child_lines = []
59
+ expected_child_indent = parent_indent_level + 1
60
+ remaining_lines = context.get_remaining_lines()
61
+
62
+ for line in remaining_lines:
63
+ if self._should_include_as_child(line, expected_child_indent, context):
64
+ child_lines.append(line)
65
+ else:
66
+ break
67
+
68
+ return child_lines
69
+
70
+ def _should_include_as_child(self, line: str, expected_indent: int, context: BlockParsingContext) -> bool:
71
+ if not line.strip():
72
+ return True
73
+
74
+ line_indent = context.get_line_indentation_level(line)
75
+ return line_indent >= expected_indent
76
+
77
+ def _extract_column_blocks(self, blocks: list[BlockCreatePayload]) -> list:
78
+ return [block for block in blocks if self._is_valid_column_block(block)]
79
+
80
+ def _is_valid_column_block(self, block: BlockCreatePayload) -> bool:
81
+ return block.type == BlockType.COLUMN and block.column is not None
@@ -0,0 +1,33 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import CreateDividerBlock, DividerData
4
+ from notionary.page.content.parser.parsers.base import (
5
+ BlockParsingContext,
6
+ LineParser,
7
+ )
8
+ from notionary.page.content.syntax import SyntaxRegistry
9
+
10
+
11
+ class DividerParser(LineParser):
12
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
13
+ super().__init__(syntax_registry)
14
+ self._syntax = syntax_registry.get_divider_syntax()
15
+
16
+ @override
17
+ def _can_handle(self, context: BlockParsingContext) -> bool:
18
+ if context.is_inside_parent_context():
19
+ return False
20
+ return self._is_divider(context.line)
21
+
22
+ @override
23
+ async def _process(self, context: BlockParsingContext) -> None:
24
+ block = self._create_divider_block()
25
+ if block:
26
+ context.result_blocks.append(block)
27
+
28
+ def _is_divider(self, line: str) -> bool:
29
+ return self._syntax.regex_pattern.match(line) is not None
30
+
31
+ def _create_divider_block(self) -> CreateDividerBlock:
32
+ divider_data = DividerData()
33
+ return CreateDividerBlock(divider=divider_data)
@@ -0,0 +1,33 @@
1
+ """Parser for embed blocks."""
2
+
3
+ from typing import override
4
+
5
+ from notionary.blocks.schemas import CreateEmbedBlock, EmbedData
6
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
7
+ from notionary.page.content.syntax import SyntaxRegistry
8
+
9
+
10
+ class EmbedParser(LineParser):
11
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
12
+ super().__init__(syntax_registry)
13
+ self._syntax = syntax_registry.get_embed_syntax()
14
+
15
+ @override
16
+ def _can_handle(self, context: BlockParsingContext) -> bool:
17
+ if context.is_inside_parent_context():
18
+ return False
19
+ return self._syntax.regex_pattern.search(context.line) is not None
20
+
21
+ @override
22
+ async def _process(self, context: BlockParsingContext) -> None:
23
+ url = self._extract_url(context.line)
24
+ if not url:
25
+ return
26
+
27
+ embed_data = EmbedData(url=url, caption=[])
28
+ block = CreateEmbedBlock(embed=embed_data)
29
+ context.result_blocks.append(block)
30
+
31
+ def _extract_url(self, line: str) -> str | None:
32
+ match = self._syntax.regex_pattern.search(line)
33
+ return match.group(1) if match else None
@@ -0,0 +1,65 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import CreateEquationBlock, EquationData
4
+ from notionary.page.content.parser.parsers.base import (
5
+ BlockParsingContext,
6
+ LineParser,
7
+ )
8
+ from notionary.page.content.syntax import SyntaxRegistry
9
+
10
+
11
+ class EquationParser(LineParser):
12
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
13
+ super().__init__(syntax_registry)
14
+ self._syntax = syntax_registry.get_equation_syntax()
15
+
16
+ @override
17
+ def _can_handle(self, context: BlockParsingContext) -> bool:
18
+ if context.is_inside_parent_context():
19
+ return False
20
+ return self._is_equation_delimiter(context.line)
21
+
22
+ @override
23
+ async def _process(self, context: BlockParsingContext) -> None:
24
+ equation_content = self._collect_equation_content(context)
25
+ lines_consumed = self._count_lines_consumed(context)
26
+
27
+ block = self._create_equation_block(opening_line=context.line, equation_lines=equation_content)
28
+
29
+ if block:
30
+ context.lines_consumed = lines_consumed
31
+ context.result_blocks.append(block)
32
+
33
+ def _is_equation_delimiter(self, line: str) -> bool:
34
+ return self._syntax.regex_pattern.match(line) is not None
35
+
36
+ def _collect_equation_content(self, context: BlockParsingContext) -> list[str]:
37
+ content_lines = []
38
+
39
+ for line in context.get_remaining_lines():
40
+ if self._is_equation_delimiter(line):
41
+ break
42
+ content_lines.append(line)
43
+
44
+ return content_lines
45
+
46
+ def _count_lines_consumed(self, context: BlockParsingContext) -> int:
47
+ for line_index, line in enumerate(context.get_remaining_lines()):
48
+ if self._is_equation_delimiter(line):
49
+ return line_index + 1
50
+
51
+ return len(context.get_remaining_lines())
52
+
53
+ def _create_equation_block(self, opening_line: str, equation_lines: list[str]) -> CreateEquationBlock | None:
54
+ if opening_line.strip() != self._syntax.start_delimiter:
55
+ return None
56
+
57
+ if not equation_lines:
58
+ return None
59
+
60
+ expression = "\n".join(equation_lines).strip()
61
+
62
+ if expression:
63
+ return CreateEquationBlock(equation=EquationData(expression=expression))
64
+
65
+ return None
@@ -0,0 +1,42 @@
1
+ """Parser for file blocks."""
2
+
3
+ from typing import override
4
+
5
+ from notionary.blocks.schemas import (
6
+ CreateFileBlock,
7
+ ExternalFile,
8
+ FileData,
9
+ FileType,
10
+ )
11
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
12
+ from notionary.page.content.syntax import SyntaxRegistry
13
+
14
+
15
+ class FileParser(LineParser):
16
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
+ super().__init__(syntax_registry)
18
+ self._syntax = syntax_registry.get_file_syntax()
19
+
20
+ @override
21
+ def _can_handle(self, context: BlockParsingContext) -> bool:
22
+ if context.is_inside_parent_context():
23
+ return False
24
+ return self._syntax.regex_pattern.search(context.line) is not None
25
+
26
+ @override
27
+ async def _process(self, context: BlockParsingContext) -> None:
28
+ url = self._extract_url(context.line)
29
+ if not url:
30
+ return
31
+
32
+ file_data = FileData(
33
+ type=FileType.EXTERNAL,
34
+ external=ExternalFile(url=url),
35
+ caption=[],
36
+ )
37
+ block = CreateFileBlock(file=file_data)
38
+ context.result_blocks.append(block)
39
+
40
+ def _extract_url(self, line: str) -> str | None:
41
+ match = self._syntax.regex_pattern.search(line)
42
+ return match.group(1).strip() if match else None
@@ -0,0 +1,115 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
4
+ from notionary.blocks.schemas import (
5
+ BlockColor,
6
+ BlockCreatePayload,
7
+ BlockType,
8
+ CreateHeading1Block,
9
+ CreateHeading2Block,
10
+ CreateHeading3Block,
11
+ CreateHeadingBlock,
12
+ CreateHeadingData,
13
+ )
14
+ from notionary.page.content.parser.parsers.base import (
15
+ BlockParsingContext,
16
+ LineParser,
17
+ )
18
+ from notionary.page.content.syntax import SyntaxRegistry
19
+
20
+
21
+ class HeadingParser(LineParser):
22
+ MIN_HEADING_LEVEL = 1
23
+ MAX_HEADING_LEVEL = 3
24
+
25
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
26
+ super().__init__(syntax_registry)
27
+ self._syntax = syntax_registry.get_heading_syntax()
28
+ self._rich_text_converter = rich_text_converter
29
+
30
+ @override
31
+ def _can_handle(self, context: BlockParsingContext) -> bool:
32
+ if context.is_inside_parent_context():
33
+ return False
34
+ return self._syntax.regex_pattern.match(context.line) is not None
35
+
36
+ @override
37
+ async def _process(self, context: BlockParsingContext) -> None:
38
+ block = await self._create_heading_block(context.line)
39
+ if not block:
40
+ return
41
+
42
+ await self._process_nested_children(block, context)
43
+ context.result_blocks.append(block)
44
+
45
+ async def _process_nested_children(self, block: CreateHeadingBlock, context: BlockParsingContext) -> None:
46
+ parent_indent_level = context.get_line_indentation_level()
47
+ child_lines = context.collect_indented_child_lines(parent_indent_level)
48
+
49
+ if not child_lines:
50
+ return
51
+
52
+ child_lines = self._remove_trailing_empty_lines(child_lines)
53
+
54
+ if not child_lines:
55
+ return
56
+
57
+ self._set_heading_toggleable(block, True)
58
+
59
+ stripped_lines = context.strip_indentation_level(child_lines, levels=1)
60
+ child_markdown = "\n".join(stripped_lines)
61
+
62
+ child_blocks = await context.parse_nested_markdown(child_markdown)
63
+ self._set_heading_children(block, child_blocks)
64
+
65
+ context.lines_consumed = len(child_lines)
66
+
67
+ def _set_heading_toggleable(self, block: CreateHeadingBlock, is_toggleable: bool) -> None:
68
+ if block.type == BlockType.HEADING_1:
69
+ block.heading_1.is_toggleable = is_toggleable
70
+ elif block.type == BlockType.HEADING_2:
71
+ block.heading_2.is_toggleable = is_toggleable
72
+ elif block.type == BlockType.HEADING_3:
73
+ block.heading_3.is_toggleable = is_toggleable
74
+
75
+ def _set_heading_children(self, block: CreateHeadingBlock, children: list[BlockCreatePayload]) -> None:
76
+ if block.type == BlockType.HEADING_1:
77
+ block.heading_1.children = children
78
+ elif block.type == BlockType.HEADING_2:
79
+ block.heading_2.children = children
80
+ elif block.type == BlockType.HEADING_3:
81
+ block.heading_3.children = children
82
+
83
+ def _remove_trailing_empty_lines(self, lines: list[str]) -> list[str]:
84
+ while lines and not lines[-1].strip():
85
+ lines.pop()
86
+ return lines
87
+
88
+ async def _create_heading_block(self, line: str) -> CreateHeadingBlock | None:
89
+ match = self._syntax.regex_pattern.match(line)
90
+ if not match:
91
+ return None
92
+
93
+ level = len(match.group(1))
94
+ content = match.group(2).strip()
95
+
96
+ if not self._is_valid_heading(level, content):
97
+ return None
98
+
99
+ heading_data = await self._build_heading_data(content)
100
+ return self._create_heading_block_by_level(level, heading_data)
101
+
102
+ def _is_valid_heading(self, level: int, content: str) -> bool:
103
+ return self.MIN_HEADING_LEVEL <= level <= self.MAX_HEADING_LEVEL and bool(content)
104
+
105
+ async def _build_heading_data(self, content: str) -> CreateHeadingData:
106
+ rich_text = await self._rich_text_converter.to_rich_text(content)
107
+ return CreateHeadingData(rich_text=rich_text, color=BlockColor.DEFAULT, is_toggleable=False, children=[])
108
+
109
+ def _create_heading_block_by_level(self, level: int, heading_data: CreateHeadingData) -> CreateHeadingBlock:
110
+ if level == 1:
111
+ return CreateHeading1Block(heading_1=heading_data)
112
+ elif level == 2:
113
+ return CreateHeading2Block(heading_2=heading_data)
114
+ else:
115
+ return CreateHeading3Block(heading_3=heading_data)
@@ -0,0 +1,42 @@
1
+ """Parser for image blocks."""
2
+
3
+ from typing import override
4
+
5
+ from notionary.blocks.schemas import (
6
+ CreateImageBlock,
7
+ ExternalFile,
8
+ FileData,
9
+ FileType,
10
+ )
11
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
12
+ from notionary.page.content.syntax import SyntaxRegistry
13
+
14
+
15
+ class ImageParser(LineParser):
16
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
+ super().__init__(syntax_registry)
18
+ self._syntax = syntax_registry.get_image_syntax()
19
+
20
+ @override
21
+ def _can_handle(self, context: BlockParsingContext) -> bool:
22
+ if context.is_inside_parent_context():
23
+ return False
24
+ return self._syntax.regex_pattern.search(context.line) is not None
25
+
26
+ @override
27
+ async def _process(self, context: BlockParsingContext) -> None:
28
+ url = self._extract_url(context.line)
29
+ if not url:
30
+ return
31
+
32
+ image_data = FileData(
33
+ type=FileType.EXTERNAL,
34
+ external=ExternalFile(url=url),
35
+ caption=[],
36
+ )
37
+ block = CreateImageBlock(image=image_data)
38
+ context.result_blocks.append(block)
39
+
40
+ def _extract_url(self, line: str) -> str | None:
41
+ match = self._syntax.regex_pattern.search(line)
42
+ return match.group(1).strip() if match else None
@@ -0,0 +1,89 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.markdown_rich_text_converter import (
4
+ MarkdownRichTextConverter,
5
+ )
6
+ from notionary.blocks.schemas import (
7
+ BlockColor,
8
+ CreateNumberedListItemBlock,
9
+ CreateNumberedListItemData,
10
+ )
11
+ from notionary.page.content.parser.parsers.base import (
12
+ BlockParsingContext,
13
+ LineParser,
14
+ )
15
+ from notionary.page.content.syntax import SyntaxRegistry
16
+
17
+
18
+ class NumberedListParser(LineParser):
19
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
20
+ super().__init__(syntax_registry)
21
+ self._syntax = syntax_registry.get_numbered_list_syntax()
22
+ self._rich_text_converter = rich_text_converter
23
+
24
+ @override
25
+ def _can_handle(self, context: BlockParsingContext) -> bool:
26
+ if context.is_inside_parent_context():
27
+ return False
28
+ return self._is_numbered_list_line(context.line)
29
+
30
+ def _is_numbered_list_line(self, line: str) -> bool:
31
+ return self._syntax.regex_pattern.match(line) is not None
32
+
33
+ @override
34
+ async def _process(self, context: BlockParsingContext) -> None:
35
+ block = await self._create_numbered_list_block(context.line)
36
+ if not block:
37
+ return
38
+
39
+ await self._process_nested_children(block, context)
40
+ context.result_blocks.append(block)
41
+
42
+ async def _process_nested_children(self, block: CreateNumberedListItemBlock, context: BlockParsingContext) -> None:
43
+ child_lines = self._collect_child_lines(context)
44
+ if not child_lines:
45
+ return
46
+
47
+ child_blocks = await self._parse_child_blocks(child_lines, context)
48
+ if child_blocks:
49
+ block.numbered_list_item.children = child_blocks
50
+
51
+ context.lines_consumed = len(child_lines)
52
+
53
+ def _collect_child_lines(self, context: BlockParsingContext) -> list[str]:
54
+ parent_indent_level = context.get_line_indentation_level()
55
+ return context.collect_indented_child_lines(parent_indent_level)
56
+
57
+ async def _parse_child_blocks(
58
+ self, child_lines: list[str], context: BlockParsingContext
59
+ ) -> list[CreateNumberedListItemBlock]:
60
+ stripped_lines = self._remove_parent_indentation(child_lines, context)
61
+ children_text = self._convert_lines_to_text(stripped_lines)
62
+ return await context.parse_nested_markdown(children_text)
63
+
64
+ def _remove_parent_indentation(self, lines: list[str], context: BlockParsingContext) -> list[str]:
65
+ return context.strip_indentation_level(lines, levels=1)
66
+
67
+ def _convert_lines_to_text(self, lines: list[str]) -> str:
68
+ return "\n".join(lines)
69
+
70
+ async def _create_numbered_list_block(self, text: str) -> CreateNumberedListItemBlock | None:
71
+ content = self._extract_list_content(text)
72
+ if content is None:
73
+ return None
74
+
75
+ rich_text = await self._convert_to_rich_text(content)
76
+ return self._build_block(rich_text)
77
+
78
+ def _extract_list_content(self, text: str) -> str | None:
79
+ match = self._syntax.regex_pattern.match(text)
80
+ if not match:
81
+ return None
82
+ return match.group(3)
83
+
84
+ async def _convert_to_rich_text(self, content: str):
85
+ return await self._rich_text_converter.to_rich_text(content)
86
+
87
+ def _build_block(self, rich_text) -> CreateNumberedListItemBlock:
88
+ numbered_list_content = CreateNumberedListItemData(rich_text=rich_text, color=BlockColor.DEFAULT)
89
+ return CreateNumberedListItemBlock(numbered_list_item=numbered_list_content)