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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. notionary/__init__.py +5 -20
  2. notionary/blocks/client.py +87 -215
  3. notionary/blocks/enums.py +167 -0
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
  5. notionary/blocks/rich_text/models.py +164 -0
  6. notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
  7. notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
  8. notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
  9. notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
  10. notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
  11. notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
  12. notionary/blocks/rich_text/rich_text_patterns.py +39 -0
  13. notionary/blocks/schemas.py +746 -0
  14. notionary/comments/client.py +52 -187
  15. notionary/comments/factory.py +40 -0
  16. notionary/comments/models.py +5 -127
  17. notionary/comments/schemas.py +240 -0
  18. notionary/comments/service.py +34 -0
  19. notionary/data_source/http/client.py +11 -0
  20. notionary/data_source/http/data_source_instance_client.py +94 -0
  21. notionary/data_source/properties/models.py +406 -0
  22. notionary/data_source/query/builder.py +429 -0
  23. notionary/data_source/query/resolver.py +114 -0
  24. notionary/data_source/query/schema.py +304 -0
  25. notionary/data_source/query/validator.py +73 -0
  26. notionary/data_source/schemas.py +27 -0
  27. notionary/data_source/service.py +353 -0
  28. notionary/database/client.py +30 -135
  29. notionary/database/database_metadata_update_client.py +19 -0
  30. notionary/database/schemas.py +29 -0
  31. notionary/database/service.py +169 -0
  32. notionary/exceptions/__init__.py +33 -0
  33. notionary/exceptions/api.py +41 -0
  34. notionary/exceptions/base.py +2 -0
  35. notionary/exceptions/block_parsing.py +16 -0
  36. notionary/exceptions/data_source/__init__.py +6 -0
  37. notionary/exceptions/data_source/builder.py +182 -0
  38. notionary/exceptions/data_source/properties.py +34 -0
  39. notionary/exceptions/properties.py +58 -0
  40. notionary/exceptions/search.py +33 -0
  41. notionary/file_upload/client.py +18 -30
  42. notionary/file_upload/models.py +7 -8
  43. notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
  44. notionary/http/client.py +205 -0
  45. notionary/http/models.py +49 -0
  46. notionary/page/blocks/client.py +1 -0
  47. notionary/page/content/factory.py +68 -0
  48. notionary/page/content/markdown/__init__.py +5 -0
  49. notionary/page/content/markdown/builder.py +304 -0
  50. notionary/page/content/markdown/nodes/__init__.py +54 -0
  51. notionary/page/content/markdown/nodes/audio.py +23 -0
  52. notionary/page/content/markdown/nodes/base.py +12 -0
  53. notionary/page/content/markdown/nodes/bookmark.py +25 -0
  54. notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
  55. notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
  56. notionary/page/content/markdown/nodes/callout.py +32 -0
  57. notionary/page/content/markdown/nodes/code.py +30 -0
  58. notionary/page/content/markdown/nodes/columns.py +51 -0
  59. notionary/page/content/markdown/nodes/divider.py +14 -0
  60. notionary/page/content/markdown/nodes/embed.py +23 -0
  61. notionary/page/content/markdown/nodes/equation.py +19 -0
  62. notionary/page/content/markdown/nodes/file.py +23 -0
  63. notionary/page/content/markdown/nodes/heading.py +16 -0
  64. notionary/page/content/markdown/nodes/image.py +23 -0
  65. notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
  66. notionary/page/content/markdown/nodes/numbered_list.py +15 -0
  67. notionary/page/content/markdown/nodes/paragraph.py +14 -0
  68. notionary/page/content/markdown/nodes/pdf.py +23 -0
  69. notionary/page/content/markdown/nodes/quote.py +15 -0
  70. notionary/page/content/markdown/nodes/space.py +14 -0
  71. notionary/page/content/markdown/nodes/table.py +45 -0
  72. notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
  73. notionary/page/content/markdown/nodes/todo.py +22 -0
  74. notionary/page/content/markdown/nodes/toggle.py +28 -0
  75. notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
  76. notionary/page/content/markdown/nodes/video.py +23 -0
  77. notionary/page/content/parser/context.py +49 -0
  78. notionary/page/content/parser/factory.py +219 -0
  79. notionary/page/content/parser/parsers/__init__.py +60 -0
  80. notionary/page/content/parser/parsers/audio.py +40 -0
  81. notionary/page/content/parser/parsers/base.py +30 -0
  82. notionary/page/content/parser/parsers/bookmark.py +33 -0
  83. notionary/page/content/parser/parsers/breadcrumb.py +33 -0
  84. notionary/page/content/parser/parsers/bulleted_list.py +41 -0
  85. notionary/page/content/parser/parsers/callout.py +129 -0
  86. notionary/page/content/parser/parsers/caption.py +55 -0
  87. notionary/page/content/parser/parsers/code.py +81 -0
  88. notionary/page/content/parser/parsers/column.py +117 -0
  89. notionary/page/content/parser/parsers/column_list.py +81 -0
  90. notionary/page/content/parser/parsers/divider.py +33 -0
  91. notionary/page/content/parser/parsers/embed.py +33 -0
  92. notionary/page/content/parser/parsers/equation.py +65 -0
  93. notionary/page/content/parser/parsers/file.py +42 -0
  94. notionary/page/content/parser/parsers/heading.py +58 -0
  95. notionary/page/content/parser/parsers/image.py +42 -0
  96. notionary/page/content/parser/parsers/numbered_list.py +45 -0
  97. notionary/page/content/parser/parsers/paragraph.py +36 -0
  98. notionary/page/content/parser/parsers/pdf.py +42 -0
  99. notionary/page/content/parser/parsers/quote.py +65 -0
  100. notionary/page/content/parser/parsers/space.py +35 -0
  101. notionary/page/content/parser/parsers/table.py +144 -0
  102. notionary/page/content/parser/parsers/table_of_contents.py +32 -0
  103. notionary/page/content/parser/parsers/todo.py +58 -0
  104. notionary/page/content/parser/parsers/toggle.py +127 -0
  105. notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
  106. notionary/page/content/parser/parsers/video.py +42 -0
  107. notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
  108. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
  109. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
  110. notionary/page/content/parser/post_processing/port.py +9 -0
  111. notionary/page/content/parser/post_processing/service.py +16 -0
  112. notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
  113. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
  114. notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
  115. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
  116. notionary/page/content/parser/pre_processsing/service.py +15 -0
  117. notionary/page/content/parser/service.py +69 -0
  118. notionary/page/content/renderer/context.py +48 -0
  119. notionary/page/content/renderer/factory.py +240 -0
  120. notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
  121. notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
  122. notionary/page/content/renderer/post_processing/port.py +7 -0
  123. notionary/page/content/renderer/post_processing/service.py +15 -0
  124. notionary/page/content/renderer/renderers/__init__.py +57 -0
  125. notionary/page/content/renderer/renderers/audio.py +31 -0
  126. notionary/page/content/renderer/renderers/base.py +31 -0
  127. notionary/page/content/renderer/renderers/bookmark.py +25 -0
  128. notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
  129. notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
  130. notionary/page/content/renderer/renderers/callout.py +65 -0
  131. notionary/page/content/renderer/renderers/captioned_block.py +58 -0
  132. notionary/page/content/renderer/renderers/code.py +34 -0
  133. notionary/page/content/renderer/renderers/column.py +44 -0
  134. notionary/page/content/renderer/renderers/column_list.py +31 -0
  135. notionary/page/content/renderer/renderers/divider.py +22 -0
  136. notionary/page/content/renderer/renderers/embed.py +25 -0
  137. notionary/page/content/renderer/renderers/equation.py +37 -0
  138. notionary/page/content/renderer/renderers/fallback.py +24 -0
  139. notionary/page/content/renderer/renderers/file.py +40 -0
  140. notionary/page/content/renderer/renderers/heading.py +69 -0
  141. notionary/page/content/renderer/renderers/image.py +31 -0
  142. notionary/page/content/renderer/renderers/numbered_list.py +41 -0
  143. notionary/page/content/renderer/renderers/paragraph.py +40 -0
  144. notionary/page/content/renderer/renderers/pdf.py +31 -0
  145. notionary/page/content/renderer/renderers/quote.py +49 -0
  146. notionary/page/content/renderer/renderers/table.py +115 -0
  147. notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
  148. notionary/page/content/renderer/renderers/table_row.py +17 -0
  149. notionary/page/content/renderer/renderers/todo.py +56 -0
  150. notionary/page/content/renderer/renderers/toggle.py +53 -0
  151. notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
  152. notionary/page/content/renderer/renderers/video.py +31 -0
  153. notionary/page/content/renderer/service.py +50 -0
  154. notionary/page/content/service.py +65 -0
  155. notionary/page/content/syntax/models.py +68 -0
  156. notionary/page/content/syntax/service.py +453 -0
  157. notionary/page/page_context.py +7 -16
  158. notionary/page/page_http_client.py +15 -0
  159. notionary/page/page_metadata_update_client.py +19 -0
  160. notionary/page/properties/client.py +144 -0
  161. notionary/page/properties/factory.py +26 -0
  162. notionary/page/properties/models.py +307 -0
  163. notionary/page/properties/service.py +257 -0
  164. notionary/page/schemas.py +13 -0
  165. notionary/page/service.py +222 -0
  166. notionary/shared/entity/client.py +29 -0
  167. notionary/shared/entity/dto_parsers.py +53 -0
  168. notionary/shared/entity/entity_metadata_update_client.py +41 -0
  169. notionary/shared/entity/schemas.py +45 -0
  170. notionary/shared/entity/service.py +171 -0
  171. notionary/shared/models/cover.py +20 -0
  172. notionary/shared/models/file.py +21 -0
  173. notionary/shared/models/icon.py +28 -0
  174. notionary/shared/models/parent.py +41 -0
  175. notionary/shared/properties/type.py +30 -0
  176. notionary/user/__init__.py +4 -8
  177. notionary/user/base.py +89 -0
  178. notionary/user/bot.py +70 -0
  179. notionary/user/client.py +22 -111
  180. notionary/user/person.py +41 -0
  181. notionary/user/schemas.py +67 -0
  182. notionary/user/service.py +65 -0
  183. notionary/utils/async_retry.py +39 -0
  184. notionary/utils/date.py +51 -0
  185. notionary/utils/fuzzy.py +56 -0
  186. notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
  187. notionary/utils/pagination.py +50 -0
  188. notionary/utils/singleton.py +13 -0
  189. notionary/utils/uuid_utils.py +20 -0
  190. notionary/workspace/__init__.py +3 -0
  191. notionary/workspace/client.py +62 -0
  192. notionary/workspace/query/builder.py +60 -0
  193. notionary/workspace/query/models.py +60 -0
  194. notionary/workspace/query/service.py +93 -0
  195. notionary/workspace/schemas.py +21 -0
  196. notionary/workspace/service.py +116 -0
  197. {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
  198. notionary-0.2.28.dist-info/RECORD +200 -0
  199. {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
  200. {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
  201. notionary/base_notion_client.py +0 -219
  202. notionary/blocks/__init__.py +0 -5
  203. notionary/blocks/_bootstrap.py +0 -271
  204. notionary/blocks/audio/__init__.py +0 -11
  205. notionary/blocks/audio/audio_element.py +0 -158
  206. notionary/blocks/audio/audio_markdown_node.py +0 -24
  207. notionary/blocks/audio/audio_models.py +0 -10
  208. notionary/blocks/base_block_element.py +0 -42
  209. notionary/blocks/bookmark/__init__.py +0 -12
  210. notionary/blocks/bookmark/bookmark_element.py +0 -83
  211. notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
  212. notionary/blocks/bookmark/bookmark_models.py +0 -15
  213. notionary/blocks/breadcrumbs/__init__.py +0 -15
  214. notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
  215. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
  216. notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
  217. notionary/blocks/bulleted_list/__init__.py +0 -15
  218. notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
  219. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
  220. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
  221. notionary/blocks/callout/__init__.py +0 -12
  222. notionary/blocks/callout/callout_element.py +0 -99
  223. notionary/blocks/callout/callout_markdown_node.py +0 -19
  224. notionary/blocks/callout/callout_models.py +0 -33
  225. notionary/blocks/child_database/__init__.py +0 -14
  226. notionary/blocks/child_database/child_database_element.py +0 -59
  227. notionary/blocks/child_database/child_database_models.py +0 -12
  228. notionary/blocks/child_page/__init__.py +0 -9
  229. notionary/blocks/child_page/child_page_element.py +0 -94
  230. notionary/blocks/child_page/child_page_models.py +0 -12
  231. notionary/blocks/code/__init__.py +0 -11
  232. notionary/blocks/code/code_element.py +0 -149
  233. notionary/blocks/code/code_markdown_node.py +0 -80
  234. notionary/blocks/code/code_models.py +0 -94
  235. notionary/blocks/column/__init__.py +0 -25
  236. notionary/blocks/column/column_element.py +0 -65
  237. notionary/blocks/column/column_list_element.py +0 -52
  238. notionary/blocks/column/column_list_markdown_node.py +0 -34
  239. notionary/blocks/column/column_markdown_node.py +0 -42
  240. notionary/blocks/column/column_models.py +0 -26
  241. notionary/blocks/divider/__init__.py +0 -12
  242. notionary/blocks/divider/divider_element.py +0 -41
  243. notionary/blocks/divider/divider_markdown_node.py +0 -11
  244. notionary/blocks/divider/divider_models.py +0 -12
  245. notionary/blocks/embed/__init__.py +0 -12
  246. notionary/blocks/embed/embed_element.py +0 -98
  247. notionary/blocks/embed/embed_markdown_node.py +0 -19
  248. notionary/blocks/embed/embed_models.py +0 -14
  249. notionary/blocks/equation/__init__.py +0 -13
  250. notionary/blocks/equation/equation_element.py +0 -133
  251. notionary/blocks/equation/equation_element_markdown_node.py +0 -23
  252. notionary/blocks/equation/equation_models.py +0 -11
  253. notionary/blocks/file/__init__.py +0 -23
  254. notionary/blocks/file/file_element.py +0 -133
  255. notionary/blocks/file/file_element_markdown_node.py +0 -24
  256. notionary/blocks/file/file_element_models.py +0 -39
  257. notionary/blocks/heading/__init__.py +0 -19
  258. notionary/blocks/heading/heading_element.py +0 -112
  259. notionary/blocks/heading/heading_markdown_node.py +0 -16
  260. notionary/blocks/heading/heading_models.py +0 -29
  261. notionary/blocks/image_block/__init__.py +0 -11
  262. notionary/blocks/image_block/image_element.py +0 -130
  263. notionary/blocks/image_block/image_markdown_node.py +0 -25
  264. notionary/blocks/image_block/image_models.py +0 -10
  265. notionary/blocks/markdown/markdown_builder.py +0 -525
  266. notionary/blocks/markdown/markdown_document_model.py +0 -0
  267. notionary/blocks/markdown/markdown_node.py +0 -25
  268. notionary/blocks/mixins/captions/__init__.py +0 -4
  269. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
  270. notionary/blocks/mixins/captions/caption_mixin.py +0 -92
  271. notionary/blocks/mixins/file_upload/__init__.py +0 -3
  272. notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
  273. notionary/blocks/models.py +0 -174
  274. notionary/blocks/numbered_list/__init__.py +0 -16
  275. notionary/blocks/numbered_list/numbered_list_element.py +0 -65
  276. notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
  277. notionary/blocks/numbered_list/numbered_list_models.py +0 -17
  278. notionary/blocks/paragraph/__init__.py +0 -15
  279. notionary/blocks/paragraph/paragraph_element.py +0 -58
  280. notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
  281. notionary/blocks/paragraph/paragraph_models.py +0 -16
  282. notionary/blocks/pdf/__init__.py +0 -11
  283. notionary/blocks/pdf/pdf_element.py +0 -146
  284. notionary/blocks/pdf/pdf_markdown_node.py +0 -24
  285. notionary/blocks/pdf/pdf_models.py +0 -11
  286. notionary/blocks/quote/__init__.py +0 -14
  287. notionary/blocks/quote/quote_element.py +0 -75
  288. notionary/blocks/quote/quote_markdown_node.py +0 -16
  289. notionary/blocks/quote/quote_models.py +0 -18
  290. notionary/blocks/registry/__init__.py +0 -3
  291. notionary/blocks/registry/block_registry.py +0 -150
  292. notionary/blocks/rich_text/__init__.py +0 -33
  293. notionary/blocks/rich_text/rich_text_models.py +0 -221
  294. notionary/blocks/rich_text/text_inline_formatter.py +0 -456
  295. notionary/blocks/syntax_prompt_builder.py +0 -137
  296. notionary/blocks/table/__init__.py +0 -19
  297. notionary/blocks/table/table_element.py +0 -225
  298. notionary/blocks/table/table_markdown_node.py +0 -42
  299. notionary/blocks/table/table_models.py +0 -28
  300. notionary/blocks/table_of_contents/__init__.py +0 -17
  301. notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
  302. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
  303. notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
  304. notionary/blocks/todo/__init__.py +0 -12
  305. notionary/blocks/todo/todo_element.py +0 -81
  306. notionary/blocks/todo/todo_markdown_node.py +0 -21
  307. notionary/blocks/todo/todo_models.py +0 -18
  308. notionary/blocks/toggle/__init__.py +0 -12
  309. notionary/blocks/toggle/toggle_element.py +0 -112
  310. notionary/blocks/toggle/toggle_markdown_node.py +0 -31
  311. notionary/blocks/toggle/toggle_models.py +0 -17
  312. notionary/blocks/toggleable_heading/__init__.py +0 -11
  313. notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
  314. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
  315. notionary/blocks/types.py +0 -130
  316. notionary/blocks/video/__init__.py +0 -11
  317. notionary/blocks/video/video_element.py +0 -187
  318. notionary/blocks/video/video_element_models.py +0 -10
  319. notionary/blocks/video/video_markdown_node.py +0 -26
  320. notionary/comments/__init__.py +0 -26
  321. notionary/database/__init__.py +0 -4
  322. notionary/database/database.py +0 -480
  323. notionary/database/database_filter_builder.py +0 -173
  324. notionary/database/database_provider.py +0 -227
  325. notionary/database/exceptions.py +0 -13
  326. notionary/database/factory.py +0 -0
  327. notionary/database/models.py +0 -337
  328. notionary/database/notion_database.py +0 -487
  329. notionary/file_upload/__init__.py +0 -7
  330. notionary/page/client.py +0 -124
  331. notionary/page/markdown_whitespace_processor.py +0 -129
  332. notionary/page/models.py +0 -322
  333. notionary/page/notion_page.py +0 -712
  334. notionary/page/page_content_deleting_service.py +0 -117
  335. notionary/page/page_content_writer.py +0 -80
  336. notionary/page/property_formatter.py +0 -99
  337. notionary/page/reader/handler/__init__.py +0 -19
  338. notionary/page/reader/handler/base_block_renderer.py +0 -44
  339. notionary/page/reader/handler/block_processing_context.py +0 -35
  340. notionary/page/reader/handler/block_rendering_context.py +0 -48
  341. notionary/page/reader/handler/column_list_renderer.py +0 -51
  342. notionary/page/reader/handler/column_renderer.py +0 -60
  343. notionary/page/reader/handler/equation_renderer.py +0 -0
  344. notionary/page/reader/handler/line_renderer.py +0 -73
  345. notionary/page/reader/handler/numbered_list_renderer.py +0 -85
  346. notionary/page/reader/handler/toggle_renderer.py +0 -69
  347. notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
  348. notionary/page/reader/page_content_retriever.py +0 -81
  349. notionary/page/search_filter_builder.py +0 -132
  350. notionary/page/utils.py +0 -60
  351. notionary/page/writer/handler/__init__.py +0 -24
  352. notionary/page/writer/handler/code_handler.py +0 -72
  353. notionary/page/writer/handler/column_handler.py +0 -141
  354. notionary/page/writer/handler/column_list_handler.py +0 -139
  355. notionary/page/writer/handler/equation_handler.py +0 -74
  356. notionary/page/writer/handler/line_handler.py +0 -35
  357. notionary/page/writer/handler/line_processing_context.py +0 -54
  358. notionary/page/writer/handler/regular_line_handler.py +0 -86
  359. notionary/page/writer/handler/table_handler.py +0 -66
  360. notionary/page/writer/handler/toggle_handler.py +0 -159
  361. notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
  362. notionary/page/writer/markdown_to_notion_converter.py +0 -139
  363. notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
  364. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  365. notionary/page/writer/notion_text_length_processor.py +0 -150
  366. notionary/schemas/__init__.py +0 -3
  367. notionary/schemas/base.py +0 -73
  368. notionary/shared/__init__.py +0 -3
  369. notionary/shared/name_to_id_resolver.py +0 -203
  370. notionary/telemetry/__init__.py +0 -19
  371. notionary/telemetry/service.py +0 -136
  372. notionary/telemetry/views.py +0 -73
  373. notionary/user/base_notion_user.py +0 -53
  374. notionary/user/models.py +0 -84
  375. notionary/user/notion_bot_user.py +0 -226
  376. notionary/user/notion_user.py +0 -255
  377. notionary/user/notion_user_manager.py +0 -101
  378. notionary/util/__init__.py +0 -15
  379. notionary/util/concurrency_limiter.py +0 -0
  380. notionary/util/factory_decorator.py +0 -0
  381. notionary/util/factory_only.py +0 -37
  382. notionary/util/fuzzy.py +0 -75
  383. notionary/util/page_id_utils.py +0 -27
  384. notionary/util/singleton.py +0 -18
  385. notionary/util/singleton_metaclass.py +0 -22
  386. notionary/workspace.py +0 -105
  387. notionary-0.2.27.dist-info/RECORD +0 -202
@@ -0,0 +1,144 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
4
+ from notionary.blocks.rich_text.models import RichText
5
+ from notionary.blocks.schemas import CreateTableBlock, CreateTableData, CreateTableRowBlock, TableRowData
6
+ from notionary.page.content.parser.parsers import BlockParsingContext, LineParser
7
+ from notionary.page.content.syntax.service import SyntaxRegistry
8
+
9
+
10
+ class TableParser(LineParser):
11
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
12
+ super().__init__(syntax_registry)
13
+ self._syntax = syntax_registry.get_table_syntax()
14
+ self._separator_syntax = syntax_registry.get_table_row_syntax()
15
+ self.rich_text_converter = rich_text_converter
16
+
17
+ @override
18
+ def _can_handle(self, context: BlockParsingContext) -> bool:
19
+ if context.is_inside_parent_context():
20
+ return False
21
+ return self._is_table_start(context)
22
+
23
+ @override
24
+ async def _process(self, context: BlockParsingContext) -> None:
25
+ if not self._is_table_start(context):
26
+ return
27
+
28
+ await self._process_complete_table(context)
29
+
30
+ def _is_table_start(self, context: BlockParsingContext) -> bool:
31
+ return self._syntax.regex_pattern.match(context.line) is not None
32
+
33
+ async def _process_complete_table(self, context: BlockParsingContext) -> None:
34
+ table_lines = [context.line]
35
+ remaining_lines = context.get_remaining_lines()
36
+ lines_consumed = self._collect_table_lines(table_lines, remaining_lines)
37
+
38
+ block = await self._create_table_block(table_lines)
39
+
40
+ if block:
41
+ context.lines_consumed = lines_consumed
42
+ context.result_blocks.append(block)
43
+
44
+ def _collect_table_lines(self, table_lines: list[str], remaining_lines: list[str]) -> int:
45
+ lines_consumed = 0
46
+
47
+ for index, line in enumerate(remaining_lines):
48
+ line_stripped = line.strip()
49
+
50
+ if not line_stripped:
51
+ table_lines.append(line)
52
+ continue
53
+
54
+ if self._is_table_line(line_stripped):
55
+ table_lines.append(line)
56
+ else:
57
+ lines_consumed = index
58
+ break
59
+ else:
60
+ lines_consumed = len(remaining_lines)
61
+
62
+ return lines_consumed
63
+
64
+ def _is_table_line(self, line: str) -> bool:
65
+ return self._syntax.regex_pattern.match(line) or self._separator_syntax.regex_pattern.match(line)
66
+
67
+ async def _create_table_block(self, table_lines: list[str]) -> CreateTableBlock | None:
68
+ if not table_lines:
69
+ return None
70
+
71
+ first_row = self._find_first_table_row(table_lines)
72
+ if not first_row:
73
+ return None
74
+
75
+ header_cells = self._parse_table_row(first_row)
76
+ column_count = len(header_cells)
77
+
78
+ table_rows, has_separator = await self._process_table_rows(table_lines)
79
+
80
+ table_data = CreateTableData(
81
+ table_width=column_count,
82
+ has_column_header=has_separator,
83
+ has_row_header=False,
84
+ children=table_rows,
85
+ )
86
+
87
+ return CreateTableBlock(table=table_data)
88
+
89
+ def _find_first_table_row(self, table_lines: list[str]) -> str | None:
90
+ for line in table_lines:
91
+ line_stripped = line.strip()
92
+ if line_stripped and self._syntax.regex_pattern.match(line_stripped):
93
+ return line_stripped
94
+ return None
95
+
96
+ async def _process_table_rows(self, table_lines: list[str]) -> tuple[list[CreateTableRowBlock], bool]:
97
+ table_rows = []
98
+ has_separator = False
99
+
100
+ for line in table_lines:
101
+ line_stripped = line.strip()
102
+
103
+ if not line_stripped:
104
+ continue
105
+
106
+ if self._is_separator_line(line_stripped):
107
+ has_separator = True
108
+ continue
109
+
110
+ if self._syntax.regex_pattern.match(line_stripped):
111
+ table_row = await self._create_table_row(line_stripped)
112
+ table_rows.append(table_row)
113
+
114
+ return table_rows, has_separator
115
+
116
+ def _is_separator_line(self, line: str) -> bool:
117
+ return self._separator_syntax.regex_pattern.match(line) is not None
118
+
119
+ async def _create_table_row(self, line: str) -> CreateTableRowBlock:
120
+ cells = self._parse_table_row(line)
121
+ rich_text_cells = await self._convert_cells_to_rich_text(cells)
122
+ table_row_data = TableRowData(cells=rich_text_cells)
123
+ return CreateTableRowBlock(table_row=table_row_data)
124
+
125
+ async def _convert_cells_to_rich_text(self, cells: list[str]) -> list[list[RichText]]:
126
+ rich_text_cells = []
127
+
128
+ for cell in cells:
129
+ rich_text = await self.rich_text_converter.to_rich_text(cell)
130
+ rich_text_cells.append(rich_text)
131
+
132
+ return rich_text_cells
133
+
134
+ def _parse_table_row(self, row_text: str) -> list[str]:
135
+ """Parse a table row by splitting on the table delimiter from SyntaxRegistry."""
136
+ row_content = row_text.strip()
137
+ delimiter = self._syntax.start_delimiter
138
+
139
+ if row_content.startswith(delimiter):
140
+ row_content = row_content[1:]
141
+ if row_content.endswith(delimiter):
142
+ row_content = row_content[:-1]
143
+
144
+ return row_content.split(delimiter)
@@ -0,0 +1,32 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.schemas import BlockColor, CreateTableOfContentsBlock, TableOfContentsData
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 TableOfContentsParser(LineParser):
12
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
13
+ super().__init__(syntax_registry)
14
+ self._syntax = syntax_registry.get_table_of_contents_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_toc(context.line)
21
+
22
+ @override
23
+ async def _process(self, context: BlockParsingContext) -> None:
24
+ block = self._create_toc_block()
25
+ context.result_blocks.append(block)
26
+
27
+ def _is_toc(self, line: str) -> bool:
28
+ return self._syntax.regex_pattern.match(line) is not None
29
+
30
+ def _create_toc_block(self) -> CreateTableOfContentsBlock:
31
+ toc_data = TableOfContentsData(color=BlockColor.DEFAULT)
32
+ return CreateTableOfContentsBlock(table_of_contents=toc_data)
@@ -0,0 +1,58 @@
1
+ """Parser for todo/checkbox blocks."""
2
+
3
+ from typing import override
4
+
5
+ from notionary.blocks.rich_text.markdown_rich_text_converter import (
6
+ MarkdownRichTextConverter,
7
+ )
8
+ from notionary.blocks.schemas import BlockColor, CreateToDoBlock, ToDoData
9
+ from notionary.page.content.parser.parsers.base import (
10
+ BlockParsingContext,
11
+ LineParser,
12
+ )
13
+ from notionary.page.content.syntax.service import SyntaxRegistry
14
+
15
+
16
+ class TodoParser(LineParser):
17
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
18
+ super().__init__(syntax_registry)
19
+ self._syntax = syntax_registry.get_todo_syntax()
20
+ self._syntax_done = syntax_registry.get_todo_done_syntax()
21
+ self._rich_text_converter = rich_text_converter
22
+
23
+ @override
24
+ def _can_handle(self, context: BlockParsingContext) -> bool:
25
+ if context.is_inside_parent_context():
26
+ return False
27
+
28
+ return (
29
+ self._syntax.regex_pattern.match(context.line) is not None
30
+ or self._syntax_done.regex_pattern.match(context.line) is not None
31
+ )
32
+
33
+ @override
34
+ async def _process(self, context: BlockParsingContext) -> None:
35
+ block = await self._create_todo_block(context.line)
36
+ if block:
37
+ context.result_blocks.append(block)
38
+
39
+ async def _create_todo_block(self, text: str) -> CreateToDoBlock | None:
40
+ done_match = self._syntax_done.regex_pattern.match(text)
41
+ todo_match = None if done_match else self._syntax.regex_pattern.match(text)
42
+
43
+ if done_match:
44
+ content = done_match.group(1)
45
+ checked = True
46
+ elif todo_match:
47
+ content = todo_match.group(1)
48
+ checked = False
49
+ else:
50
+ return None
51
+
52
+ rich_text = await self._rich_text_converter.to_rich_text(content)
53
+ todo_content = ToDoData(
54
+ rich_text=rich_text,
55
+ checked=checked,
56
+ color=BlockColor.DEFAULT,
57
+ )
58
+ return CreateToDoBlock(to_do=todo_content)
@@ -0,0 +1,127 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.markdown_rich_text_converter import MarkdownRichTextConverter
4
+ from notionary.blocks.schemas import BlockColor, CreateToggleBlock, CreateToggleData
5
+ from notionary.page.content.parser.parsers import (
6
+ BlockParsingContext,
7
+ LineParser,
8
+ ParentBlockContext,
9
+ )
10
+ from notionary.page.content.syntax.service import SyntaxRegistry
11
+
12
+
13
+ class ToggleParser(LineParser):
14
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
15
+ super().__init__(syntax_registry)
16
+ self._syntax = syntax_registry.get_toggle_syntax()
17
+ self._heading_syntax = syntax_registry.get_toggleable_heading_syntax()
18
+ self._rich_text_converter = rich_text_converter
19
+
20
+ @override
21
+ def _can_handle(self, context: BlockParsingContext) -> bool:
22
+ return self._is_toggle_start(context) or self._is_toggle_end(context) or self._is_toggle_content(context)
23
+
24
+ @override
25
+ async def _process(self, context: BlockParsingContext) -> None:
26
+ if self._is_toggle_start(context):
27
+ await self._start_toggle(context)
28
+
29
+ if self._is_toggle_end(context):
30
+ await self._finalize_toggle(context)
31
+
32
+ if self._is_toggle_content(context):
33
+ self._add_toggle_content(context)
34
+
35
+ def _is_toggle_start(self, context: BlockParsingContext) -> bool:
36
+ if not self._syntax.regex_pattern.match(context.line):
37
+ return False
38
+
39
+ # Exclude toggleable heading patterns to be more resilient to wrong order of chain
40
+ return not self.is_heading_start(context.line)
41
+
42
+ def is_heading_start(self, line: str) -> bool:
43
+ return self._heading_syntax.regex_pattern.match(line) is not None
44
+
45
+ def _is_toggle_end(self, context: BlockParsingContext) -> bool:
46
+ if not self._syntax.end_regex_pattern.match(context.line):
47
+ return False
48
+
49
+ if not context.parent_stack:
50
+ return False
51
+
52
+ current_parent = context.parent_stack[-1]
53
+ return isinstance(current_parent.block, CreateToggleBlock)
54
+
55
+ async def _start_toggle(self, context: BlockParsingContext) -> None:
56
+ block = await self._create_toggle_block(context.line)
57
+ if not block:
58
+ return
59
+
60
+ parent_context = ParentBlockContext(
61
+ block=block,
62
+ child_lines=[],
63
+ )
64
+ context.parent_stack.append(parent_context)
65
+
66
+ async def _create_toggle_block(self, line: str) -> CreateToggleBlock | None:
67
+ if not (match := self._syntax.regex_pattern.match(line)):
68
+ return None
69
+
70
+ title = match.group(1).strip()
71
+ rich_text = await self._rich_text_converter.to_rich_text(title)
72
+
73
+ toggle_content = CreateToggleData(rich_text=rich_text, color=BlockColor.DEFAULT, children=[])
74
+ return CreateToggleBlock(toggle=toggle_content)
75
+
76
+ async def _finalize_toggle(self, context: BlockParsingContext) -> None:
77
+ toggle_context = context.parent_stack.pop()
78
+ await self._assign_toggle_children_if_any(toggle_context, context)
79
+
80
+ if self._is_nested_in_other_parent_context(context):
81
+ self._assign_to_parent_context(context, toggle_context)
82
+ else:
83
+ context.result_blocks.append(toggle_context.block)
84
+
85
+ def _is_nested_in_other_parent_context(self, context: BlockParsingContext) -> bool:
86
+ return context.parent_stack
87
+
88
+ def _assign_to_parent_context(self, context: BlockParsingContext, toggle_context: ParentBlockContext) -> None:
89
+ parent_context = context.parent_stack[-1]
90
+ parent_context.add_child_block(toggle_context.block)
91
+
92
+ async def _assign_toggle_children_if_any(
93
+ self, toggle_context: ParentBlockContext, context: BlockParsingContext
94
+ ) -> None:
95
+ all_children = []
96
+
97
+ # Process text lines
98
+ if toggle_context.child_lines:
99
+ children_text = "\n".join(toggle_context.child_lines)
100
+ text_blocks = await self._parse_nested_content(children_text, context)
101
+ all_children.extend(text_blocks)
102
+
103
+ if toggle_context.child_blocks:
104
+ all_children.extend(toggle_context.child_blocks)
105
+
106
+ toggle_context.block.toggle.children = all_children
107
+
108
+ def _is_toggle_content(self, context: BlockParsingContext) -> bool:
109
+ if not context.parent_stack:
110
+ return False
111
+
112
+ current_parent = context.parent_stack[-1]
113
+ if not isinstance(current_parent.block, CreateToggleBlock):
114
+ return False
115
+
116
+ return not (
117
+ self._syntax.regex_pattern.match(context.line) or self._syntax.end_regex_pattern.match(context.line)
118
+ )
119
+
120
+ def _add_toggle_content(self, context: BlockParsingContext) -> None:
121
+ context.parent_stack[-1].add_child_line(context.line)
122
+
123
+ async def _parse_nested_content(self, text: str, context: BlockParsingContext) -> list:
124
+ if not text.strip():
125
+ return []
126
+
127
+ return await context.parse_nested_content(text)
@@ -0,0 +1,150 @@
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 import (
15
+ BlockParsingContext,
16
+ LineParser,
17
+ ParentBlockContext,
18
+ )
19
+ from notionary.page.content.syntax.service import SyntaxRegistry
20
+
21
+
22
+ class ToggleableHeadingParser(LineParser):
23
+ MIN_HEADING_LEVEL = 1
24
+ MAX_HEADING_LEVEL = 3
25
+
26
+ HEADING_BLOCK_TYPES = (CreateHeading1Block, CreateHeading2Block, CreateHeading3Block)
27
+
28
+ def __init__(self, syntax_registry: SyntaxRegistry, rich_text_converter: MarkdownRichTextConverter) -> None:
29
+ super().__init__(syntax_registry)
30
+ self._syntax = syntax_registry.get_toggleable_heading_syntax()
31
+ self._rich_text_converter = rich_text_converter
32
+
33
+ @override
34
+ def _can_handle(self, context: BlockParsingContext) -> bool:
35
+ return self._is_heading_start(context) or self._is_heading_end(context) or self._is_heading_content(context)
36
+
37
+ @override
38
+ async def _process(self, context: BlockParsingContext) -> None:
39
+ if self._is_heading_start(context):
40
+ await self._start_toggleable_heading(context)
41
+ elif self._is_heading_end(context):
42
+ await self._finalize_toggleable_heading(context)
43
+ elif self._is_heading_content(context):
44
+ await self._add_heading_content(context)
45
+
46
+ def _is_heading_start(self, context: BlockParsingContext) -> bool:
47
+ return self._syntax.regex_pattern.match(context.line) is not None
48
+
49
+ def _is_heading_end(self, context: BlockParsingContext) -> bool:
50
+ if not self._syntax.end_regex_pattern.match(context.line):
51
+ return False
52
+ return self._has_heading_on_stack(context)
53
+
54
+ def _is_heading_content(self, context: BlockParsingContext) -> bool:
55
+ if not self._has_heading_on_stack(context):
56
+ return False
57
+
58
+ return not (
59
+ self._syntax.regex_pattern.match(context.line) or self._syntax.end_regex_pattern.match(context.line)
60
+ )
61
+
62
+ def _has_heading_on_stack(self, context: BlockParsingContext) -> bool:
63
+ if not context.parent_stack:
64
+ return False
65
+ current_parent = context.parent_stack[-1]
66
+ return isinstance(current_parent.block, self.HEADING_BLOCK_TYPES)
67
+
68
+ async def _start_toggleable_heading(self, context: BlockParsingContext) -> None:
69
+ block = await self._create_heading_block(context.line)
70
+ if not block:
71
+ return
72
+
73
+ parent_context = ParentBlockContext(
74
+ block=block,
75
+ child_lines=[],
76
+ )
77
+ context.parent_stack.append(parent_context)
78
+
79
+ async def _create_heading_block(self, line: str) -> CreateHeadingBlock | None:
80
+ match = self._syntax.regex_pattern.match(line)
81
+ if not match:
82
+ return None
83
+
84
+ level = len(match.group("level"))
85
+ content = match.group(2).strip()
86
+
87
+ if not self._is_valid_heading(level, content):
88
+ return None
89
+
90
+ heading_data = await self._build_heading_data(content)
91
+
92
+ if level == 1:
93
+ return CreateHeading1Block(heading_1=heading_data)
94
+ elif level == 2:
95
+ return CreateHeading2Block(heading_2=heading_data)
96
+ else:
97
+ return CreateHeading3Block(heading_3=heading_data)
98
+
99
+ def _is_valid_heading(self, level: int, content: str) -> bool:
100
+ return self.MIN_HEADING_LEVEL <= level <= self.MAX_HEADING_LEVEL and bool(content)
101
+
102
+ async def _build_heading_data(self, content: str) -> CreateHeadingData:
103
+ rich_text = await self._rich_text_converter.to_rich_text(content)
104
+ return CreateHeadingData(rich_text=rich_text, color=BlockColor.DEFAULT, is_toggleable=True, children=[])
105
+
106
+ async def _add_heading_content(self, context: BlockParsingContext) -> None:
107
+ context.parent_stack[-1].add_child_line(context.line)
108
+
109
+ async def _finalize_toggleable_heading(self, context: BlockParsingContext) -> None:
110
+ heading_context = context.parent_stack.pop()
111
+ await self._assign_children(heading_context, context)
112
+ self._add_to_parent_or_result(heading_context.block, context)
113
+
114
+ async def _assign_children(self, heading_context: ParentBlockContext, context: BlockParsingContext) -> None:
115
+ children = await self._collect_children(heading_context, context)
116
+ self._set_heading_children(heading_context.block, children)
117
+
118
+ async def _collect_children(
119
+ self, heading_context: ParentBlockContext, context: BlockParsingContext
120
+ ) -> list[BlockCreatePayload]:
121
+ children = []
122
+
123
+ if heading_context.child_lines:
124
+ text = "\n".join(heading_context.child_lines)
125
+ text_blocks = await self._parse_nested_content(text, context)
126
+ children.extend(text_blocks)
127
+
128
+ if heading_context.child_blocks:
129
+ children.extend(heading_context.child_blocks)
130
+
131
+ return children
132
+
133
+ def _set_heading_children(self, block: CreateHeadingBlock, children: list[BlockCreatePayload]) -> None:
134
+ if block.type == BlockType.HEADING_1:
135
+ block.heading_1.children = children
136
+ elif block.type == BlockType.HEADING_2:
137
+ block.heading_2.children = children
138
+ elif block.type == BlockType.HEADING_3:
139
+ block.heading_3.children = children
140
+
141
+ def _add_to_parent_or_result(self, block: CreateHeadingBlock, context: BlockParsingContext) -> None:
142
+ if context.parent_stack:
143
+ context.parent_stack[-1].add_child_block(block)
144
+ else:
145
+ context.result_blocks.append(block)
146
+
147
+ async def _parse_nested_content(self, text: str, context: BlockParsingContext) -> list:
148
+ if not text.strip():
149
+ return []
150
+ return await context.parse_nested_content(text)
@@ -0,0 +1,42 @@
1
+ """Parser for video blocks."""
2
+
3
+ from typing import override
4
+
5
+ from notionary.blocks.schemas import (
6
+ CreateVideoBlock,
7
+ ExternalFile,
8
+ FileData,
9
+ FileType,
10
+ )
11
+ from notionary.page.content.parser.parsers.base import BlockParsingContext, LineParser
12
+ from notionary.page.content.syntax.service import SyntaxRegistry
13
+
14
+
15
+ class VideoParser(LineParser):
16
+ def __init__(self, syntax_registry: SyntaxRegistry) -> None:
17
+ super().__init__(syntax_registry)
18
+ self._syntax = syntax_registry.get_video_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
+ video_data = FileData(
33
+ type=FileType.EXTERNAL,
34
+ external=ExternalFile(url=url),
35
+ caption=[],
36
+ )
37
+ block = CreateVideoBlock(video=video_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,5 @@
1
+ from .rich_text_length_truncation import RichTextLengthTruncationPostProcessor
2
+
3
+ __all__ = [
4
+ "RichTextLengthTruncationPostProcessor",
5
+ ]
@@ -0,0 +1,93 @@
1
+ """
2
+ Handles request limits for rich texts (see https://developers.notion.com/reference/request-limits)
3
+ """
4
+
5
+ from typing import Any, override
6
+
7
+ from notionary.blocks.rich_text.models import RichText, RichTextType
8
+ from notionary.blocks.schemas import BlockCreatePayload
9
+ from notionary.page.content.parser.post_processing.port import PostProcessor
10
+ from notionary.utils.mixins.logging import LoggingMixin
11
+
12
+ type _NestedBlockList = BlockCreatePayload | list["_NestedBlockList"]
13
+
14
+
15
+ class RichTextLengthTruncationPostProcessor(PostProcessor, LoggingMixin):
16
+ NOTION_MAX_LENGTH = 2000
17
+
18
+ def __init__(self, max_text_length: int = NOTION_MAX_LENGTH) -> None:
19
+ self.max_text_length = max_text_length
20
+
21
+ @override
22
+ def process(self, blocks: list[BlockCreatePayload]) -> list[BlockCreatePayload]:
23
+ if not blocks:
24
+ return blocks
25
+
26
+ flattened_blocks = self._flatten_blocks(blocks)
27
+ return [self._process_block(block) for block in flattened_blocks]
28
+
29
+ def _flatten_blocks(self, blocks: list[_NestedBlockList]) -> list[BlockCreatePayload]:
30
+ flattened: list[BlockCreatePayload] = []
31
+
32
+ for item in blocks:
33
+ if isinstance(item, list):
34
+ flattened.extend(self._flatten_blocks(item))
35
+ else:
36
+ flattened.append(item)
37
+
38
+ return flattened
39
+
40
+ def _process_block(self, block: BlockCreatePayload) -> BlockCreatePayload:
41
+ block_copy = block.model_copy(deep=True)
42
+ content = self._get_block_content(block_copy)
43
+
44
+ if content is not None:
45
+ self._truncate_content(content)
46
+
47
+ return block_copy
48
+
49
+ def _get_block_content(self, block: BlockCreatePayload) -> Any | None:
50
+ content = getattr(block, block.type.value, None)
51
+
52
+ if content is None:
53
+ return None
54
+
55
+ if hasattr(content, "rich_text") or hasattr(content, "children"):
56
+ return content
57
+
58
+ return None
59
+
60
+ def _truncate_content(self, content: object) -> None:
61
+ if hasattr(content, "rich_text"):
62
+ self._truncate_rich_text_list(content.rich_text)
63
+
64
+ if hasattr(content, "caption"):
65
+ self._truncate_rich_text_list(content.caption)
66
+
67
+ if hasattr(content, "children"):
68
+ for child in content.children:
69
+ child_content = self._get_block_content(child)
70
+ if child_content:
71
+ self._truncate_content(child_content)
72
+
73
+ def _truncate_rich_text_list(self, rich_text_list: list[RichText]) -> None:
74
+ for rich_text in rich_text_list:
75
+ if not self._is_text_type(rich_text):
76
+ continue
77
+
78
+ content = rich_text.text.content
79
+ if len(content) > self.max_text_length:
80
+ self.logger.warning(
81
+ "Truncating text content from %d to %d characters",
82
+ len(content),
83
+ self.max_text_length,
84
+ )
85
+ truncated_content = self._create_truncated_text_with_ellipsis(content)
86
+ rich_text.text.content = truncated_content
87
+
88
+ def _create_truncated_text_with_ellipsis(self, content: str) -> str:
89
+ cutoff = self.max_text_length - 3
90
+ return content[:cutoff] + "..."
91
+
92
+ def _is_text_type(self, rich_text: RichText) -> bool:
93
+ return rich_text.type == RichTextType.TEXT and rich_text.text and rich_text.text.content