notionary 0.2.26__py3-none-any.whl → 0.2.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. notionary/__init__.py +5 -20
  2. notionary/blocks/client.py +87 -215
  3. notionary/blocks/enums.py +167 -0
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
  5. notionary/blocks/rich_text/models.py +164 -0
  6. notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
  7. notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
  8. notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
  9. notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
  10. notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
  11. notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
  12. notionary/blocks/rich_text/rich_text_patterns.py +39 -0
  13. notionary/blocks/schemas.py +746 -0
  14. notionary/comments/client.py +52 -187
  15. notionary/comments/factory.py +40 -0
  16. notionary/comments/models.py +5 -127
  17. notionary/comments/schemas.py +240 -0
  18. notionary/comments/service.py +34 -0
  19. notionary/data_source/http/client.py +11 -0
  20. notionary/data_source/http/data_source_instance_client.py +94 -0
  21. notionary/data_source/properties/models.py +406 -0
  22. notionary/data_source/query/builder.py +429 -0
  23. notionary/data_source/query/resolver.py +114 -0
  24. notionary/data_source/query/schema.py +304 -0
  25. notionary/data_source/query/validator.py +73 -0
  26. notionary/data_source/schemas.py +27 -0
  27. notionary/data_source/service.py +353 -0
  28. notionary/database/client.py +30 -135
  29. notionary/database/database_metadata_update_client.py +19 -0
  30. notionary/database/schemas.py +29 -0
  31. notionary/database/service.py +169 -0
  32. notionary/exceptions/__init__.py +33 -0
  33. notionary/exceptions/api.py +41 -0
  34. notionary/exceptions/base.py +2 -0
  35. notionary/exceptions/block_parsing.py +16 -0
  36. notionary/exceptions/data_source/__init__.py +6 -0
  37. notionary/exceptions/data_source/builder.py +182 -0
  38. notionary/exceptions/data_source/properties.py +34 -0
  39. notionary/exceptions/properties.py +58 -0
  40. notionary/exceptions/search.py +33 -0
  41. notionary/file_upload/client.py +18 -30
  42. notionary/file_upload/models.py +7 -8
  43. notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
  44. notionary/http/client.py +205 -0
  45. notionary/http/models.py +49 -0
  46. notionary/page/blocks/client.py +1 -0
  47. notionary/page/content/factory.py +68 -0
  48. notionary/page/content/markdown/__init__.py +5 -0
  49. notionary/page/content/markdown/builder.py +304 -0
  50. notionary/page/content/markdown/nodes/__init__.py +54 -0
  51. notionary/page/content/markdown/nodes/audio.py +23 -0
  52. notionary/page/content/markdown/nodes/base.py +12 -0
  53. notionary/page/content/markdown/nodes/bookmark.py +25 -0
  54. notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
  55. notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
  56. notionary/page/content/markdown/nodes/callout.py +32 -0
  57. notionary/page/content/markdown/nodes/code.py +30 -0
  58. notionary/page/content/markdown/nodes/columns.py +51 -0
  59. notionary/page/content/markdown/nodes/divider.py +14 -0
  60. notionary/page/content/markdown/nodes/embed.py +23 -0
  61. notionary/page/content/markdown/nodes/equation.py +19 -0
  62. notionary/page/content/markdown/nodes/file.py +23 -0
  63. notionary/page/content/markdown/nodes/heading.py +16 -0
  64. notionary/page/content/markdown/nodes/image.py +23 -0
  65. notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
  66. notionary/page/content/markdown/nodes/numbered_list.py +15 -0
  67. notionary/page/content/markdown/nodes/paragraph.py +14 -0
  68. notionary/page/content/markdown/nodes/pdf.py +23 -0
  69. notionary/page/content/markdown/nodes/quote.py +15 -0
  70. notionary/page/content/markdown/nodes/space.py +14 -0
  71. notionary/page/content/markdown/nodes/table.py +45 -0
  72. notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
  73. notionary/page/content/markdown/nodes/todo.py +22 -0
  74. notionary/page/content/markdown/nodes/toggle.py +28 -0
  75. notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
  76. notionary/page/content/markdown/nodes/video.py +23 -0
  77. notionary/page/content/parser/context.py +49 -0
  78. notionary/page/content/parser/factory.py +219 -0
  79. notionary/page/content/parser/parsers/__init__.py +60 -0
  80. notionary/page/content/parser/parsers/audio.py +40 -0
  81. notionary/page/content/parser/parsers/base.py +30 -0
  82. notionary/page/content/parser/parsers/bookmark.py +33 -0
  83. notionary/page/content/parser/parsers/breadcrumb.py +33 -0
  84. notionary/page/content/parser/parsers/bulleted_list.py +41 -0
  85. notionary/page/content/parser/parsers/callout.py +129 -0
  86. notionary/page/content/parser/parsers/caption.py +55 -0
  87. notionary/page/content/parser/parsers/code.py +81 -0
  88. notionary/page/content/parser/parsers/column.py +117 -0
  89. notionary/page/content/parser/parsers/column_list.py +81 -0
  90. notionary/page/content/parser/parsers/divider.py +33 -0
  91. notionary/page/content/parser/parsers/embed.py +33 -0
  92. notionary/page/content/parser/parsers/equation.py +65 -0
  93. notionary/page/content/parser/parsers/file.py +42 -0
  94. notionary/page/content/parser/parsers/heading.py +58 -0
  95. notionary/page/content/parser/parsers/image.py +42 -0
  96. notionary/page/content/parser/parsers/numbered_list.py +45 -0
  97. notionary/page/content/parser/parsers/paragraph.py +36 -0
  98. notionary/page/content/parser/parsers/pdf.py +42 -0
  99. notionary/page/content/parser/parsers/quote.py +65 -0
  100. notionary/page/content/parser/parsers/space.py +35 -0
  101. notionary/page/content/parser/parsers/table.py +144 -0
  102. notionary/page/content/parser/parsers/table_of_contents.py +32 -0
  103. notionary/page/content/parser/parsers/todo.py +58 -0
  104. notionary/page/content/parser/parsers/toggle.py +127 -0
  105. notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
  106. notionary/page/content/parser/parsers/video.py +42 -0
  107. notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
  108. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
  109. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
  110. notionary/page/content/parser/post_processing/port.py +9 -0
  111. notionary/page/content/parser/post_processing/service.py +16 -0
  112. notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
  113. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
  114. notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
  115. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
  116. notionary/page/content/parser/pre_processsing/service.py +15 -0
  117. notionary/page/content/parser/service.py +69 -0
  118. notionary/page/content/renderer/context.py +48 -0
  119. notionary/page/content/renderer/factory.py +240 -0
  120. notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
  121. notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
  122. notionary/page/content/renderer/post_processing/port.py +7 -0
  123. notionary/page/content/renderer/post_processing/service.py +15 -0
  124. notionary/page/content/renderer/renderers/__init__.py +57 -0
  125. notionary/page/content/renderer/renderers/audio.py +31 -0
  126. notionary/page/content/renderer/renderers/base.py +31 -0
  127. notionary/page/content/renderer/renderers/bookmark.py +25 -0
  128. notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
  129. notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
  130. notionary/page/content/renderer/renderers/callout.py +65 -0
  131. notionary/page/content/renderer/renderers/captioned_block.py +58 -0
  132. notionary/page/content/renderer/renderers/code.py +34 -0
  133. notionary/page/content/renderer/renderers/column.py +44 -0
  134. notionary/page/content/renderer/renderers/column_list.py +31 -0
  135. notionary/page/content/renderer/renderers/divider.py +22 -0
  136. notionary/page/content/renderer/renderers/embed.py +25 -0
  137. notionary/page/content/renderer/renderers/equation.py +37 -0
  138. notionary/page/content/renderer/renderers/fallback.py +24 -0
  139. notionary/page/content/renderer/renderers/file.py +40 -0
  140. notionary/page/content/renderer/renderers/heading.py +69 -0
  141. notionary/page/content/renderer/renderers/image.py +31 -0
  142. notionary/page/content/renderer/renderers/numbered_list.py +41 -0
  143. notionary/page/content/renderer/renderers/paragraph.py +40 -0
  144. notionary/page/content/renderer/renderers/pdf.py +31 -0
  145. notionary/page/content/renderer/renderers/quote.py +49 -0
  146. notionary/page/content/renderer/renderers/table.py +115 -0
  147. notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
  148. notionary/page/content/renderer/renderers/table_row.py +17 -0
  149. notionary/page/content/renderer/renderers/todo.py +56 -0
  150. notionary/page/content/renderer/renderers/toggle.py +53 -0
  151. notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
  152. notionary/page/content/renderer/renderers/video.py +31 -0
  153. notionary/page/content/renderer/service.py +50 -0
  154. notionary/page/content/service.py +65 -0
  155. notionary/page/content/syntax/models.py +68 -0
  156. notionary/page/content/syntax/service.py +453 -0
  157. notionary/page/page_context.py +7 -16
  158. notionary/page/page_http_client.py +15 -0
  159. notionary/page/page_metadata_update_client.py +19 -0
  160. notionary/page/properties/client.py +144 -0
  161. notionary/page/properties/factory.py +26 -0
  162. notionary/page/properties/models.py +307 -0
  163. notionary/page/properties/service.py +257 -0
  164. notionary/page/schemas.py +13 -0
  165. notionary/page/service.py +222 -0
  166. notionary/shared/entity/client.py +29 -0
  167. notionary/shared/entity/dto_parsers.py +53 -0
  168. notionary/shared/entity/entity_metadata_update_client.py +41 -0
  169. notionary/shared/entity/schemas.py +45 -0
  170. notionary/shared/entity/service.py +171 -0
  171. notionary/shared/models/cover.py +20 -0
  172. notionary/shared/models/file.py +21 -0
  173. notionary/shared/models/icon.py +28 -0
  174. notionary/shared/models/parent.py +41 -0
  175. notionary/shared/properties/type.py +30 -0
  176. notionary/user/__init__.py +4 -8
  177. notionary/user/base.py +89 -0
  178. notionary/user/bot.py +70 -0
  179. notionary/user/client.py +22 -111
  180. notionary/user/person.py +41 -0
  181. notionary/user/schemas.py +67 -0
  182. notionary/user/service.py +65 -0
  183. notionary/utils/async_retry.py +39 -0
  184. notionary/utils/date.py +51 -0
  185. notionary/utils/fuzzy.py +56 -0
  186. notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
  187. notionary/utils/pagination.py +50 -0
  188. notionary/utils/singleton.py +13 -0
  189. notionary/utils/uuid_utils.py +20 -0
  190. notionary/workspace/__init__.py +3 -0
  191. notionary/workspace/client.py +62 -0
  192. notionary/workspace/query/builder.py +60 -0
  193. notionary/workspace/query/models.py +60 -0
  194. notionary/workspace/query/service.py +93 -0
  195. notionary/workspace/schemas.py +21 -0
  196. notionary/workspace/service.py +116 -0
  197. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
  198. notionary-0.2.28.dist-info/RECORD +200 -0
  199. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
  200. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
  201. notionary/base_notion_client.py +0 -219
  202. notionary/blocks/__init__.py +0 -5
  203. notionary/blocks/_bootstrap.py +0 -271
  204. notionary/blocks/audio/__init__.py +0 -11
  205. notionary/blocks/audio/audio_element.py +0 -158
  206. notionary/blocks/audio/audio_markdown_node.py +0 -24
  207. notionary/blocks/audio/audio_models.py +0 -10
  208. notionary/blocks/base_block_element.py +0 -42
  209. notionary/blocks/bookmark/__init__.py +0 -12
  210. notionary/blocks/bookmark/bookmark_element.py +0 -83
  211. notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
  212. notionary/blocks/bookmark/bookmark_models.py +0 -15
  213. notionary/blocks/breadcrumbs/__init__.py +0 -15
  214. notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
  215. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
  216. notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
  217. notionary/blocks/bulleted_list/__init__.py +0 -15
  218. notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
  219. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
  220. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
  221. notionary/blocks/callout/__init__.py +0 -12
  222. notionary/blocks/callout/callout_element.py +0 -99
  223. notionary/blocks/callout/callout_markdown_node.py +0 -19
  224. notionary/blocks/callout/callout_models.py +0 -33
  225. notionary/blocks/child_database/__init__.py +0 -14
  226. notionary/blocks/child_database/child_database_element.py +0 -59
  227. notionary/blocks/child_database/child_database_models.py +0 -12
  228. notionary/blocks/child_page/__init__.py +0 -9
  229. notionary/blocks/child_page/child_page_element.py +0 -94
  230. notionary/blocks/child_page/child_page_models.py +0 -12
  231. notionary/blocks/code/__init__.py +0 -11
  232. notionary/blocks/code/code_element.py +0 -149
  233. notionary/blocks/code/code_markdown_node.py +0 -80
  234. notionary/blocks/code/code_models.py +0 -94
  235. notionary/blocks/column/__init__.py +0 -25
  236. notionary/blocks/column/column_element.py +0 -65
  237. notionary/blocks/column/column_list_element.py +0 -52
  238. notionary/blocks/column/column_list_markdown_node.py +0 -34
  239. notionary/blocks/column/column_markdown_node.py +0 -42
  240. notionary/blocks/column/column_models.py +0 -26
  241. notionary/blocks/divider/__init__.py +0 -12
  242. notionary/blocks/divider/divider_element.py +0 -41
  243. notionary/blocks/divider/divider_markdown_node.py +0 -11
  244. notionary/blocks/divider/divider_models.py +0 -12
  245. notionary/blocks/embed/__init__.py +0 -12
  246. notionary/blocks/embed/embed_element.py +0 -98
  247. notionary/blocks/embed/embed_markdown_node.py +0 -19
  248. notionary/blocks/embed/embed_models.py +0 -14
  249. notionary/blocks/equation/__init__.py +0 -13
  250. notionary/blocks/equation/equation_element.py +0 -133
  251. notionary/blocks/equation/equation_element_markdown_node.py +0 -23
  252. notionary/blocks/equation/equation_models.py +0 -11
  253. notionary/blocks/file/__init__.py +0 -23
  254. notionary/blocks/file/file_element.py +0 -133
  255. notionary/blocks/file/file_element_markdown_node.py +0 -24
  256. notionary/blocks/file/file_element_models.py +0 -39
  257. notionary/blocks/heading/__init__.py +0 -19
  258. notionary/blocks/heading/heading_element.py +0 -112
  259. notionary/blocks/heading/heading_markdown_node.py +0 -16
  260. notionary/blocks/heading/heading_models.py +0 -29
  261. notionary/blocks/image_block/__init__.py +0 -11
  262. notionary/blocks/image_block/image_element.py +0 -130
  263. notionary/blocks/image_block/image_markdown_node.py +0 -25
  264. notionary/blocks/image_block/image_models.py +0 -10
  265. notionary/blocks/markdown/markdown_builder.py +0 -525
  266. notionary/blocks/markdown/markdown_document_model.py +0 -0
  267. notionary/blocks/markdown/markdown_node.py +0 -25
  268. notionary/blocks/mixins/captions/__init__.py +0 -4
  269. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
  270. notionary/blocks/mixins/captions/caption_mixin.py +0 -92
  271. notionary/blocks/mixins/file_upload/__init__.py +0 -3
  272. notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
  273. notionary/blocks/models.py +0 -174
  274. notionary/blocks/numbered_list/__init__.py +0 -16
  275. notionary/blocks/numbered_list/numbered_list_element.py +0 -65
  276. notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
  277. notionary/blocks/numbered_list/numbered_list_models.py +0 -17
  278. notionary/blocks/paragraph/__init__.py +0 -15
  279. notionary/blocks/paragraph/paragraph_element.py +0 -58
  280. notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
  281. notionary/blocks/paragraph/paragraph_models.py +0 -16
  282. notionary/blocks/pdf/__init__.py +0 -11
  283. notionary/blocks/pdf/pdf_element.py +0 -146
  284. notionary/blocks/pdf/pdf_markdown_node.py +0 -24
  285. notionary/blocks/pdf/pdf_models.py +0 -11
  286. notionary/blocks/quote/__init__.py +0 -14
  287. notionary/blocks/quote/quote_element.py +0 -75
  288. notionary/blocks/quote/quote_markdown_node.py +0 -16
  289. notionary/blocks/quote/quote_models.py +0 -18
  290. notionary/blocks/registry/__init__.py +0 -3
  291. notionary/blocks/registry/block_registry.py +0 -150
  292. notionary/blocks/rich_text/__init__.py +0 -33
  293. notionary/blocks/rich_text/rich_text_models.py +0 -221
  294. notionary/blocks/rich_text/text_inline_formatter.py +0 -456
  295. notionary/blocks/syntax_prompt_builder.py +0 -137
  296. notionary/blocks/table/__init__.py +0 -19
  297. notionary/blocks/table/table_element.py +0 -225
  298. notionary/blocks/table/table_markdown_node.py +0 -42
  299. notionary/blocks/table/table_models.py +0 -28
  300. notionary/blocks/table_of_contents/__init__.py +0 -17
  301. notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
  302. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
  303. notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
  304. notionary/blocks/todo/__init__.py +0 -12
  305. notionary/blocks/todo/todo_element.py +0 -81
  306. notionary/blocks/todo/todo_markdown_node.py +0 -21
  307. notionary/blocks/todo/todo_models.py +0 -18
  308. notionary/blocks/toggle/__init__.py +0 -12
  309. notionary/blocks/toggle/toggle_element.py +0 -112
  310. notionary/blocks/toggle/toggle_markdown_node.py +0 -31
  311. notionary/blocks/toggle/toggle_models.py +0 -17
  312. notionary/blocks/toggleable_heading/__init__.py +0 -11
  313. notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
  314. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
  315. notionary/blocks/types.py +0 -130
  316. notionary/blocks/video/__init__.py +0 -11
  317. notionary/blocks/video/video_element.py +0 -187
  318. notionary/blocks/video/video_element_models.py +0 -10
  319. notionary/blocks/video/video_markdown_node.py +0 -26
  320. notionary/comments/__init__.py +0 -26
  321. notionary/database/__init__.py +0 -4
  322. notionary/database/database.py +0 -480
  323. notionary/database/database_filter_builder.py +0 -173
  324. notionary/database/database_provider.py +0 -227
  325. notionary/database/exceptions.py +0 -13
  326. notionary/database/factory.py +0 -0
  327. notionary/database/models.py +0 -337
  328. notionary/database/notion_database.py +0 -487
  329. notionary/file_upload/__init__.py +0 -7
  330. notionary/page/client.py +0 -124
  331. notionary/page/markdown_whitespace_processor.py +0 -129
  332. notionary/page/models.py +0 -322
  333. notionary/page/notion_page.py +0 -674
  334. notionary/page/page_content_deleting_service.py +0 -117
  335. notionary/page/page_content_writer.py +0 -80
  336. notionary/page/property_formatter.py +0 -99
  337. notionary/page/reader/handler/__init__.py +0 -19
  338. notionary/page/reader/handler/base_block_renderer.py +0 -44
  339. notionary/page/reader/handler/block_processing_context.py +0 -35
  340. notionary/page/reader/handler/block_rendering_context.py +0 -48
  341. notionary/page/reader/handler/column_list_renderer.py +0 -51
  342. notionary/page/reader/handler/column_renderer.py +0 -60
  343. notionary/page/reader/handler/equation_renderer.py +0 -0
  344. notionary/page/reader/handler/line_renderer.py +0 -73
  345. notionary/page/reader/handler/numbered_list_renderer.py +0 -85
  346. notionary/page/reader/handler/toggle_renderer.py +0 -69
  347. notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
  348. notionary/page/reader/page_content_retriever.py +0 -81
  349. notionary/page/search_filter_builder.py +0 -132
  350. notionary/page/utils.py +0 -60
  351. notionary/page/writer/handler/__init__.py +0 -24
  352. notionary/page/writer/handler/code_handler.py +0 -72
  353. notionary/page/writer/handler/column_handler.py +0 -141
  354. notionary/page/writer/handler/column_list_handler.py +0 -139
  355. notionary/page/writer/handler/equation_handler.py +0 -74
  356. notionary/page/writer/handler/line_handler.py +0 -35
  357. notionary/page/writer/handler/line_processing_context.py +0 -54
  358. notionary/page/writer/handler/regular_line_handler.py +0 -86
  359. notionary/page/writer/handler/table_handler.py +0 -66
  360. notionary/page/writer/handler/toggle_handler.py +0 -159
  361. notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
  362. notionary/page/writer/markdown_to_notion_converter.py +0 -139
  363. notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
  364. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  365. notionary/page/writer/notion_text_length_processor.py +0 -150
  366. notionary/schemas/__init__.py +0 -3
  367. notionary/schemas/base.py +0 -73
  368. notionary/shared/__init__.py +0 -3
  369. notionary/shared/name_to_id_resolver.py +0 -203
  370. notionary/telemetry/__init__.py +0 -19
  371. notionary/telemetry/service.py +0 -136
  372. notionary/telemetry/views.py +0 -73
  373. notionary/user/base_notion_user.py +0 -53
  374. notionary/user/models.py +0 -84
  375. notionary/user/notion_bot_user.py +0 -226
  376. notionary/user/notion_user.py +0 -255
  377. notionary/user/notion_user_manager.py +0 -101
  378. notionary/util/__init__.py +0 -15
  379. notionary/util/concurrency_limiter.py +0 -0
  380. notionary/util/factory_decorator.py +0 -0
  381. notionary/util/factory_only.py +0 -37
  382. notionary/util/fuzzy.py +0 -75
  383. notionary/util/page_id_utils.py +0 -27
  384. notionary/util/singleton.py +0 -18
  385. notionary/util/singleton_metaclass.py +0 -22
  386. notionary/workspace.py +0 -105
  387. notionary-0.2.26.dist-info/RECORD +0 -202
@@ -0,0 +1,60 @@
1
+ from ..context import BlockParsingContext, ParentBlockContext
2
+ from .audio import AudioParser
3
+ from .base import LineParser
4
+ from .bookmark import BookmarkParser
5
+ from .breadcrumb import BreadcrumbParser
6
+ from .bulleted_list import BulletedListParser
7
+ from .callout import CalloutParser
8
+ from .caption import CaptionParser
9
+ from .code import CodeParser
10
+ from .column import ColumnParser
11
+ from .column_list import ColumnListParser
12
+ from .divider import DividerParser
13
+ from .embed import EmbedParser
14
+ from .equation import EquationParser
15
+ from .file import FileParser
16
+ from .heading import HeadingParser
17
+ from .image import ImageParser
18
+ from .numbered_list import NumberedListParser
19
+ from .paragraph import ParagraphParser
20
+ from .pdf import PdfParser
21
+ from .quote import QuoteParser
22
+ from .space import SpaceParser
23
+ from .table import TableParser
24
+ from .table_of_contents import TableOfContentsParser
25
+ from .todo import TodoParser
26
+ from .toggle import ToggleParser
27
+ from .toggleable_heading import ToggleableHeadingParser
28
+ from .video import VideoParser
29
+
30
+ __all__ = [
31
+ "AudioParser",
32
+ "BlockParsingContext",
33
+ "BookmarkParser",
34
+ "BreadcrumbParser",
35
+ "BulletedListParser",
36
+ "CalloutParser",
37
+ "CaptionParser",
38
+ "CodeParser",
39
+ "ColumnListParser",
40
+ "ColumnParser",
41
+ "DividerParser",
42
+ "EmbedParser",
43
+ "EquationParser",
44
+ "FileParser",
45
+ "HeadingParser",
46
+ "ImageParser",
47
+ "LineParser",
48
+ "NumberedListParser",
49
+ "ParagraphParser",
50
+ "ParentBlockContext",
51
+ "PdfParser",
52
+ "QuoteParser",
53
+ "SpaceParser",
54
+ "TableOfContentsParser",
55
+ "TableParser",
56
+ "TodoParser",
57
+ "ToggleParser",
58
+ "ToggleableHeadingParser",
59
+ "VideoParser",
60
+ ]
@@ -0,0 +1,40 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import (
4
+ CreateAudioBlock,
5
+ ExternalFile,
6
+ FileData,
7
+ FileType,
8
+ )
9
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
10
+ from notionary.page.content.syntax.service import SyntaxRegistry
11
+
12
+
13
+ class AudioParser(LineParser):
14
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
15
+ super().__init__(syntax_registry)
16
+ self._syntax = syntax_registry.get_audio_syntax()
17
+
18
+ @override
19
+ def _can_handle(self, context: BlockParsingContext) -> bool:
20
+ if context.is_inside_parent_context():
21
+ return False
22
+ return self._syntax.regex_pattern.search(context.line) is not None
23
+
24
+ @override
25
+ async def _process(self, context: BlockParsingContext) -> None:
26
+ url = self._extract_url(context.line)
27
+ if url is None:
28
+ return
29
+
30
+ audio_data = FileData(
31
+ type=FileType.EXTERNAL,
32
+ external=ExternalFile(url=url),
33
+ caption=[],
34
+ )
35
+ block = CreateAudioBlock(audio=audio_data)
36
+ context.result_blocks.append(block)
37
+
38
+ def _extract_url(self, line: str) -> str | None:
39
+ match = self._syntax.regex_pattern.search(line)
40
+ return match.group(1).strip() if match else None
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from notionary.page.content.parser.context import BlockParsingContext
6
+ from notionary.page.content.syntax.service import SyntaxRegistry
7
+
8
+
9
+ class LineParser(ABC):
10
+ def __init__(self, syntax_registry: SyntaxRegistry | None = None) -> None:
11
+ self._next_handler: LineParser | None = None
12
+ self._syntax_registry = syntax_registry
13
+
14
+ def set_next(self, handler: LineParser) -> LineParser:
15
+ self._next_handler = handler
16
+ return handler
17
+
18
+ async def handle(self, context: BlockParsingContext) -> None:
19
+ if self._can_handle(context):
20
+ await self._process(context)
21
+ elif self._next_handler:
22
+ await self._next_handler.handle(context)
23
+
24
+ @abstractmethod
25
+ def _can_handle(self, context: BlockParsingContext) -> bool:
26
+ pass
27
+
28
+ @abstractmethod
29
+ async def _process(self, context: BlockParsingContext) -> None:
30
+ pass
@@ -0,0 +1,33 @@
1
+ """Parser for bookmark blocks."""
2
+
3
+ from typing import override
4
+
5
+ from notionary.blocks.schemas import BookmarkData, CreateBookmarkBlock
6
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
7
+ from notionary.page.content.syntax.service import SyntaxRegistry
8
+
9
+
10
+ class BookmarkParser(LineParser):
11
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
12
+ super().__init__(syntax_registry)
13
+ self._syntax = syntax_registry.get_bookmark_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
+ bookmark_data = BookmarkData(url=url, caption=[])
28
+ block = CreateBookmarkBlock(bookmark=bookmark_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,33 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import BreadcrumbData, CreateBreadcrumbBlock
4
+ from notionary.page.content.parser.parsers.base import (
5
+ BlockParsingContext,
6
+ LineParser,
7
+ )
8
+ from notionary.page.content.syntax.service import SyntaxRegistry
9
+
10
+
11
+ class BreadcrumbParser(LineParser):
12
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
13
+ super().__init__(syntax_registry)
14
+ self._syntax = syntax_registry.get_breadcrumb_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_breadcrumb(context.line)
21
+
22
+ @override
23
+ async def _process(self, context: BlockParsingContext) -> None:
24
+ block = self._create_breadcrumb_block()
25
+ if block:
26
+ context.result_blocks.append(block)
27
+
28
+ def _is_breadcrumb(self, line: str) -> bool:
29
+ return self._syntax.regex_pattern.match(line) is not None
30
+
31
+ def _create_breadcrumb_block(self) -> CreateBreadcrumbBlock:
32
+ breadcrumb_data = BreadcrumbData()
33
+ return CreateBreadcrumbBlock(breadcrumb=breadcrumb_data)
@@ -0,0 +1,41 @@
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 BulletedListItemData, CreateBulletedListItemBlock
7
+ from notionary.page.content.parser.parsers.base import (
8
+ BlockParsingContext,
9
+ LineParser,
10
+ )
11
+ from notionary.page.content.syntax.service import SyntaxRegistry
12
+
13
+
14
+ class BulletedListParser(LineParser):
15
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
16
+ super().__init__(syntax_registry)
17
+ self._syntax = syntax_registry.get_bulleted_list_syntax()
18
+ self._rich_text_converter = rich_text_converter
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.match(context.line) is not None
25
+
26
+ @override
27
+ async def _process(self, context: BlockParsingContext) -> None:
28
+ block = await self._create_bulleted_list_block(context.line)
29
+ if block:
30
+ context.result_blocks.append(block)
31
+
32
+ async def _create_bulleted_list_block(self, text: str) -> CreateBulletedListItemBlock | None:
33
+ match = self._syntax.regex_pattern.match(text)
34
+ if not match:
35
+ return None
36
+
37
+ content = match.group(2)
38
+ rich_text = await self._rich_text_converter.to_rich_text(content)
39
+
40
+ bulleted_list_content = BulletedListItemData(rich_text=rich_text)
41
+ return CreateBulletedListItemBlock(bulleted_list_item=bulleted_list_content)
@@ -0,0 +1,129 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
4
+ from notionary.blocks.schemas import CreateCalloutBlock, CreateCalloutData
5
+ from notionary.page.content.parser.context import ParentBlockContext
6
+ from notionary.page.content.parser.parsers.base import (
7
+ BlockParsingContext,
8
+ LineParser,
9
+ )
10
+ from notionary.page.content.syntax.service import SyntaxRegistry
11
+ from notionary.shared.models.icon import EmojiIcon
12
+
13
+
14
+ class CalloutParser(LineParser):
15
+ DEFAULT_EMOJI = "💡"
16
+
17
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
18
+ super().__init__(syntax_registry)
19
+ self._syntax = syntax_registry.get_callout_syntax()
20
+ self._start_pattern = self._syntax.regex_pattern
21
+ self._end_pattern = self._syntax.end_regex_pattern
22
+ self._rich_text_converter = rich_text_converter
23
+
24
+ @override
25
+ def _can_handle(self, context: BlockParsingContext) -> bool:
26
+ return self._is_callout_start(context) or self._is_callout_end(context) or self._is_callout_content(context)
27
+
28
+ @override
29
+ async def _process(self, context: BlockParsingContext) -> None:
30
+ if self._is_callout_start(context):
31
+ await self._start_callout(context)
32
+
33
+ if self._is_callout_end(context):
34
+ await self._finalize_callout(context)
35
+
36
+ if self._is_callout_content(context):
37
+ self._add_callout_content(context)
38
+
39
+ def _is_callout_start(self, context: BlockParsingContext) -> bool:
40
+ return self._start_pattern.match(context.line) is not None
41
+
42
+ def _is_callout_end(self, context: BlockParsingContext) -> bool:
43
+ if not self._end_pattern.match(context.line):
44
+ return False
45
+
46
+ if not context.parent_stack:
47
+ return False
48
+
49
+ current_parent = context.parent_stack[-1]
50
+ return isinstance(current_parent.block, CreateCalloutBlock)
51
+
52
+ async def _start_callout(self, context: BlockParsingContext) -> None:
53
+ block = await self._create_callout_block(context.line)
54
+ if not block:
55
+ return
56
+
57
+ parent_context = ParentBlockContext(
58
+ block=block,
59
+ child_lines=[],
60
+ )
61
+ context.parent_stack.append(parent_context)
62
+
63
+ async def _create_callout_block(self, line: str) -> CreateCalloutBlock | None:
64
+ match = self._start_pattern.match(line)
65
+ if not match:
66
+ return None
67
+
68
+ emoji_part = match.group(1)
69
+ emoji = emoji_part.strip() if emoji_part else self.DEFAULT_EMOJI
70
+
71
+ # Create callout with empty rich_text initially
72
+ # The actual content will be added as children
73
+ callout_data = CreateCalloutData(
74
+ rich_text=[],
75
+ icon=EmojiIcon(emoji=emoji),
76
+ children=[],
77
+ )
78
+ return CreateCalloutBlock(callout=callout_data)
79
+
80
+ async def _finalize_callout(self, context: BlockParsingContext) -> None:
81
+ callout_context = context.parent_stack.pop()
82
+ await self._assign_callout_children_if_any(callout_context, context)
83
+
84
+ if self._is_nested_in_other_parent_context(context):
85
+ self._assign_to_parent_context(context, callout_context)
86
+ else:
87
+ context.result_blocks.append(callout_context.block)
88
+
89
+ def _is_nested_in_other_parent_context(self, context: BlockParsingContext) -> bool:
90
+ return bool(context.parent_stack)
91
+
92
+ def _assign_to_parent_context(self, context: BlockParsingContext, callout_context: ParentBlockContext) -> None:
93
+ parent_context = context.parent_stack[-1]
94
+ parent_context.add_child_block(callout_context.block)
95
+
96
+ async def _assign_callout_children_if_any(
97
+ self, callout_context: ParentBlockContext, context: BlockParsingContext
98
+ ) -> None:
99
+ all_children = []
100
+
101
+ if callout_context.child_lines:
102
+ children_text = "\n".join(callout_context.child_lines)
103
+ text_blocks = await self._parse_nested_content(children_text, context)
104
+ all_children.extend(text_blocks)
105
+
106
+ # Add any child blocks
107
+ if callout_context.child_blocks:
108
+ all_children.extend(callout_context.child_blocks)
109
+
110
+ callout_context.block.callout.children = all_children
111
+
112
+ def _is_callout_content(self, context: BlockParsingContext) -> bool:
113
+ if not context.parent_stack:
114
+ return False
115
+
116
+ current_parent = context.parent_stack[-1]
117
+ if not isinstance(current_parent.block, CreateCalloutBlock):
118
+ return False
119
+
120
+ return not (self._start_pattern.match(context.line) or self._end_pattern.match(context.line))
121
+
122
+ def _add_callout_content(self, context: BlockParsingContext) -> None:
123
+ context.parent_stack[-1].add_child_line(context.line)
124
+
125
+ async def _parse_nested_content(self, text: str, context: BlockParsingContext) -> list:
126
+ if not text.strip():
127
+ return []
128
+
129
+ return await context.parse_nested_content(text)
@@ -0,0 +1,55 @@
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 BlockCreatePayload
7
+ from notionary.page.content.parser.parsers.base import (
8
+ BlockParsingContext,
9
+ LineParser,
10
+ )
11
+ from notionary.page.content.syntax.service import SyntaxRegistry
12
+
13
+
14
+ class CaptionParser(LineParser):
15
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
16
+ super().__init__(syntax_registry)
17
+ self._syntax = syntax_registry.get_caption_syntax()
18
+ self._rich_text_converter = rich_text_converter
19
+
20
+ @override
21
+ def _can_handle(self, context: BlockParsingContext) -> bool:
22
+ if context.is_inside_parent_context():
23
+ return False
24
+
25
+ if not self._syntax.regex_pattern.match(context.line):
26
+ return False
27
+
28
+ if not context.result_blocks:
29
+ return False
30
+
31
+ previous_block = context.result_blocks[-1]
32
+ return self._block_supports_caption(previous_block)
33
+
34
+ @override
35
+ async def _process(self, context: BlockParsingContext) -> None:
36
+ caption_match = self._syntax.regex_pattern.match(context.line)
37
+ if not caption_match:
38
+ return
39
+
40
+ caption_text = caption_match.group(1)
41
+ caption_rich_text = await self._rich_text_converter.to_rich_text(caption_text)
42
+
43
+ previous_block = context.result_blocks[-1]
44
+ self._attach_caption_to_block(previous_block, caption_rich_text)
45
+
46
+ def _block_supports_caption(self, block: BlockCreatePayload) -> bool:
47
+ block_data = getattr(block, block.type.value, None)
48
+ if block_data is None:
49
+ return False
50
+ return hasattr(block_data, "caption")
51
+
52
+ def _attach_caption_to_block(self, block: BlockCreatePayload, caption_rich_text: list) -> None:
53
+ block_data = getattr(block, block.type.value)
54
+ if hasattr(block_data, "caption"):
55
+ block_data.caption = caption_rich_text
@@ -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, CodeLanguage, CreateCodeBlock
7
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
8
+ from notionary.page.content.syntax.service import SyntaxRegistry
9
+
10
+
11
+ class CodeParser(LineParser):
12
+ DEFAULT_LANGUAGE = CodeLanguage.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) -> CodeLanguage:
71
+ return CodeLanguage.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,117 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import CreateColumnBlock, CreateColumnData, CreateColumnListBlock
4
+ from notionary.page.content.parser.context import ParentBlockContext
5
+ from notionary.page.content.parser.parsers.base import (
6
+ BlockParsingContext,
7
+ LineParser,
8
+ )
9
+ from notionary.page.content.syntax.service import SyntaxRegistry
10
+
11
+
12
+ class ColumnParser(LineParser):
13
+ MIN_WIDTH_RATIO = 0
14
+ MAX_WIDTH_RATIO = 1.0
15
+
16
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
+ super().__init__(syntax_registry)
18
+ self._syntax = syntax_registry.get_column_syntax()
19
+
20
+ @override
21
+ def _can_handle(self, context: BlockParsingContext) -> bool:
22
+ return self._is_column_start(context) or self._is_column_end(context) or self._is_column_content(context)
23
+
24
+ @override
25
+ async def _process(self, context: BlockParsingContext) -> None:
26
+ if self._is_column_start(context):
27
+ await self._start_column(context)
28
+ elif self._is_column_end(context):
29
+ await self._finalize_column(context)
30
+ elif self._is_column_content(context):
31
+ await self._add_column_content(context)
32
+
33
+ def _is_column_start(self, context: BlockParsingContext) -> bool:
34
+ return self._syntax.regex_pattern.match(context.line) is not None
35
+
36
+ def _is_column_end(self, context: BlockParsingContext) -> bool:
37
+ if not self._syntax.end_regex_pattern.match(context.line):
38
+ return False
39
+
40
+ if not context.parent_stack:
41
+ return False
42
+
43
+ current_parent = context.parent_stack[-1]
44
+ return isinstance(current_parent.block, CreateColumnBlock)
45
+
46
+ def _is_column_content(self, context: BlockParsingContext) -> bool:
47
+ if not context.parent_stack:
48
+ return False
49
+
50
+ current_parent = context.parent_stack[-1]
51
+ if not isinstance(current_parent.block, CreateColumnBlock):
52
+ return False
53
+
54
+ line = context.line.strip()
55
+ return not (self._syntax.regex_pattern.match(line) or self._syntax.end_regex_pattern.match(line))
56
+
57
+ async def _add_column_content(self, context: BlockParsingContext) -> None:
58
+ context.parent_stack[-1].add_child_line(context.line)
59
+
60
+ async def _start_column(self, context: BlockParsingContext) -> None:
61
+ block = self._create_column_block(context.line)
62
+ if not block:
63
+ return
64
+
65
+ parent_context = ParentBlockContext(
66
+ block=block,
67
+ child_lines=[],
68
+ )
69
+ context.parent_stack.append(parent_context)
70
+
71
+ def _create_column_block(self, line: str) -> CreateColumnBlock | None:
72
+ match = self._syntax.regex_pattern.match(line)
73
+ if not match:
74
+ return None
75
+
76
+ width_ratio = self._parse_width_ratio(match.group(1))
77
+ column_data = CreateColumnData(width_ratio=width_ratio)
78
+
79
+ return CreateColumnBlock(column=column_data)
80
+
81
+ def _parse_width_ratio(self, ratio_str: str | None) -> float | None:
82
+ if not ratio_str:
83
+ return None
84
+
85
+ try:
86
+ width_ratio = float(ratio_str)
87
+ return width_ratio if self.MIN_WIDTH_RATIO < width_ratio <= self.MAX_WIDTH_RATIO else None
88
+ except ValueError:
89
+ return None
90
+
91
+ async def _finalize_column(self, context: BlockParsingContext) -> None:
92
+ column_context = context.parent_stack.pop()
93
+ await self._assign_column_children(column_context, context)
94
+
95
+ if self._has_column_list_parent(context):
96
+ parent = context.parent_stack[-1]
97
+ parent.add_child_block(column_context.block)
98
+ else:
99
+ context.result_blocks.append(column_context.block)
100
+
101
+ def _has_column_list_parent(self, context: BlockParsingContext) -> bool:
102
+ if not context.parent_stack:
103
+ return False
104
+ return isinstance(context.parent_stack[-1].block, CreateColumnListBlock)
105
+
106
+ async def _assign_column_children(self, column_context: ParentBlockContext, context: BlockParsingContext) -> None:
107
+ all_children = []
108
+
109
+ if column_context.child_lines:
110
+ children_text = "\n".join(column_context.child_lines)
111
+ text_blocks = await context.parse_nested_content(children_text)
112
+ all_children.extend(text_blocks)
113
+
114
+ if column_context.child_blocks:
115
+ all_children.extend(column_context.child_blocks)
116
+
117
+ column_context.block.column.children = all_children