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,266 @@
1
+ import re
2
+ from collections.abc import Callable
3
+ from dataclasses import dataclass
4
+ from re import Match
5
+ from typing import ClassVar
6
+
7
+ from notionary.blocks.rich_text.models import MentionType, RichText, RichTextType, TextAnnotations
8
+ from notionary.blocks.rich_text.name_id_resolver import (
9
+ DatabaseNameIdResolver,
10
+ NameIdResolver,
11
+ PageNameIdResolver,
12
+ PersonNameIdResolver,
13
+ )
14
+ from notionary.blocks.rich_text.rich_text_patterns import RichTextPatterns
15
+ from notionary.blocks.schemas import BlockColor
16
+
17
+
18
+ @dataclass
19
+ class PatternMatch:
20
+ match: Match
21
+ handler: Callable[[Match], RichText | list[RichText]]
22
+ position: int
23
+
24
+ @property
25
+ def matched_text(self) -> str:
26
+ return self.match.group(0)
27
+
28
+ @property
29
+ def end_position(self) -> int:
30
+ return self.position + len(self.matched_text)
31
+
32
+
33
+ @dataclass
34
+ class PatternHandler:
35
+ pattern: str
36
+ handler: Callable[[Match], RichText | list[RichText]]
37
+
38
+
39
+ class MarkdownRichTextConverter:
40
+ VALID_COLORS: ClassVar[set[str]] = {color.value for color in BlockColor}
41
+
42
+ def __init__(
43
+ self,
44
+ *,
45
+ page_resolver: NameIdResolver | None = None,
46
+ database_resolver: NameIdResolver | None = None,
47
+ person_resolver: NameIdResolver | None = None,
48
+ ):
49
+ self.page_resolver = page_resolver or PageNameIdResolver()
50
+ self.database_resolver = database_resolver or DatabaseNameIdResolver()
51
+ self.person_resolver = person_resolver or PersonNameIdResolver()
52
+ self.format_handlers = self._setup_format_handlers()
53
+
54
+ def _setup_format_handlers(self) -> list[PatternHandler]:
55
+ return [
56
+ PatternHandler(RichTextPatterns.BOLD, self._handle_bold_pattern),
57
+ PatternHandler(RichTextPatterns.ITALIC, self._handle_italic_pattern),
58
+ PatternHandler(RichTextPatterns.ITALIC_UNDERSCORE, self._handle_italic_pattern),
59
+ PatternHandler(RichTextPatterns.UNDERLINE, self._handle_underline_pattern),
60
+ PatternHandler(RichTextPatterns.STRIKETHROUGH, self._handle_strikethrough_pattern),
61
+ PatternHandler(RichTextPatterns.CODE, self._handle_code_pattern),
62
+ PatternHandler(RichTextPatterns.LINK, self._handle_link_pattern),
63
+ PatternHandler(RichTextPatterns.INLINE_EQUATION, self._handle_equation_pattern),
64
+ PatternHandler(RichTextPatterns.COLOR, self._handle_color_pattern),
65
+ PatternHandler(RichTextPatterns.PAGE_MENTION, self._handle_page_mention_pattern),
66
+ PatternHandler(RichTextPatterns.DATABASE_MENTION, self._handle_database_mention_pattern),
67
+ PatternHandler(RichTextPatterns.USER_MENTION, self._handle_user_mention_pattern),
68
+ ]
69
+
70
+ async def to_rich_text(self, text: str) -> list[RichText]:
71
+ if not text:
72
+ return []
73
+ return await self._split_text_into_segments(text)
74
+
75
+ async def _split_text_into_segments(self, text: str) -> list[RichText]:
76
+ segments: list[RichText] = []
77
+ remaining_text = text
78
+
79
+ while remaining_text:
80
+ pattern_match = self._find_earliest_pattern_match(remaining_text)
81
+
82
+ if not pattern_match:
83
+ segments.append(RichText.from_plain_text(remaining_text))
84
+ break
85
+
86
+ plain_text_before = remaining_text[: pattern_match.position]
87
+ if plain_text_before:
88
+ segments.append(RichText.from_plain_text(plain_text_before))
89
+
90
+ pattern_result = await self._process_pattern_match(pattern_match)
91
+ self._add_pattern_result_to_segments(segments, pattern_result)
92
+
93
+ remaining_text = remaining_text[pattern_match.end_position :]
94
+
95
+ return segments
96
+
97
+ def _find_earliest_pattern_match(self, text: str) -> PatternMatch | None:
98
+ """Find the pattern that appears earliest in the text."""
99
+ earliest_match = None
100
+ earliest_position = len(text)
101
+
102
+ for pattern_handler in self.format_handlers:
103
+ match = re.search(pattern_handler.pattern, text)
104
+ if match and match.start() < earliest_position:
105
+ earliest_match = PatternMatch(match=match, handler=pattern_handler.handler, position=match.start())
106
+ earliest_position = match.start()
107
+
108
+ return earliest_match
109
+
110
+ async def _process_pattern_match(self, pattern_match: PatternMatch) -> RichText | list[RichText]:
111
+ handler_method = pattern_match.handler
112
+
113
+ if self._is_async_handler(handler_method):
114
+ return await handler_method(pattern_match.match)
115
+ else:
116
+ return handler_method(pattern_match.match)
117
+
118
+ def _is_async_handler(self, handler_method: Callable) -> bool:
119
+ async_handlers = {
120
+ self._handle_page_mention_pattern,
121
+ self._handle_database_mention_pattern,
122
+ self._handle_color_pattern, # Color pattern needs async for recursive parsing
123
+ self._handle_user_mention_pattern,
124
+ }
125
+ return handler_method in async_handlers
126
+
127
+ def _add_pattern_result_to_segments(
128
+ self, segments: list[RichText], pattern_result: RichText | list[RichText]
129
+ ) -> None:
130
+ if isinstance(pattern_result, list):
131
+ segments.extend(pattern_result)
132
+ elif pattern_result:
133
+ segments.append(pattern_result)
134
+
135
+ async def _handle_color_pattern(self, match: Match) -> list[RichText]:
136
+ color, content = match.group(1).lower(), match.group(2)
137
+
138
+ if color not in self.VALID_COLORS:
139
+ return [RichText.from_plain_text(f"({match.group(1)}:{content})")]
140
+
141
+ parsed_segments = await self._split_text_into_segments(content)
142
+
143
+ colored_segments = []
144
+ for segment in parsed_segments:
145
+ if segment.type == RichTextType.TEXT:
146
+ colored_segment = self._apply_color_to_text_segment(segment, color)
147
+ colored_segments.append(colored_segment)
148
+ else:
149
+ colored_segments.append(segment)
150
+
151
+ return colored_segments
152
+
153
+ def _apply_color_to_text_segment(self, segment: RichText, color: str) -> RichText:
154
+ if segment.type != RichTextType.TEXT:
155
+ return segment
156
+
157
+ has_link = segment.text and segment.text.link
158
+
159
+ if has_link:
160
+ return self._apply_color_to_link_segment(segment, color)
161
+ else:
162
+ return self._apply_color_to_plain_text_segment(segment, color)
163
+
164
+ def _apply_color_to_link_segment(self, segment: RichText, color: str) -> RichText:
165
+ formatting = self._extract_formatting_attributes(segment.annotations)
166
+
167
+ return RichText.for_link(segment.plain_text, segment.text.link.url, color=color, **formatting)
168
+
169
+ def _apply_color_to_plain_text_segment(self, segment: RichText, color: str) -> RichText:
170
+ if segment.type != RichTextType.TEXT:
171
+ return segment
172
+
173
+ formatting = self._extract_formatting_attributes(segment.annotations)
174
+
175
+ return RichText.from_plain_text(segment.plain_text, color=color, **formatting)
176
+
177
+ def _extract_formatting_attributes(self, annotations: TextAnnotations) -> dict[str, bool]:
178
+ if not annotations:
179
+ return {
180
+ "bold": False,
181
+ "italic": False,
182
+ "strikethrough": False,
183
+ "underline": False,
184
+ "code": False,
185
+ }
186
+
187
+ return {
188
+ "bold": annotations.bold,
189
+ "italic": annotations.italic,
190
+ "strikethrough": annotations.strikethrough,
191
+ "underline": annotations.underline,
192
+ "code": annotations.code,
193
+ }
194
+
195
+ async def _handle_page_mention_pattern(self, match: Match) -> RichText:
196
+ identifier = match.group(1)
197
+ return await self._create_mention_or_fallback(
198
+ identifier=identifier,
199
+ resolve_func=self.page_resolver.resolve_name_to_id,
200
+ create_mention_func=RichText.mention_page,
201
+ mention_type=MentionType.PAGE,
202
+ )
203
+
204
+ async def _handle_database_mention_pattern(self, match: Match) -> RichText:
205
+ identifier = match.group(1)
206
+ return await self._create_mention_or_fallback(
207
+ identifier=identifier,
208
+ resolve_func=self.database_resolver.resolve_name_to_id,
209
+ create_mention_func=RichText.mention_database,
210
+ mention_type=MentionType.DATABASE,
211
+ )
212
+
213
+ async def _handle_user_mention_pattern(self, match: Match) -> RichText:
214
+ identifier = match.group(1)
215
+ return await self._create_mention_or_fallback(
216
+ identifier=identifier,
217
+ resolve_func=self.person_resolver.resolve_name_to_id,
218
+ create_mention_func=RichText.mention_user,
219
+ mention_type=MentionType.USER,
220
+ )
221
+
222
+ async def _create_mention_or_fallback(
223
+ self,
224
+ identifier: str,
225
+ resolve_func: Callable[[str], str | None],
226
+ create_mention_func: Callable[[str], RichText],
227
+ mention_type: MentionType,
228
+ ) -> RichText:
229
+ try:
230
+ resolved_id = await resolve_func(identifier)
231
+
232
+ if resolved_id:
233
+ return create_mention_func(resolved_id)
234
+ else:
235
+ return self._create_unresolved_mention_fallback(identifier, mention_type)
236
+
237
+ except Exception:
238
+ # If resolution throws an error, fallback to plain text
239
+ return self._create_unresolved_mention_fallback(identifier, mention_type)
240
+
241
+ def _create_unresolved_mention_fallback(self, identifier: str, mention_type: MentionType) -> RichText:
242
+ fallback_text = f"@{mention_type.value}[{identifier}]"
243
+ return RichText.for_caption(fallback_text)
244
+
245
+ def _handle_bold_pattern(self, match: Match) -> RichText:
246
+ return RichText.from_plain_text(match.group(1), bold=True)
247
+
248
+ def _handle_italic_pattern(self, match: Match) -> RichText:
249
+ return RichText.from_plain_text(match.group(1), italic=True)
250
+
251
+ def _handle_underline_pattern(self, match: Match) -> RichText:
252
+ return RichText.from_plain_text(match.group(1), underline=True)
253
+
254
+ def _handle_strikethrough_pattern(self, match: Match) -> RichText:
255
+ return RichText.from_plain_text(match.group(1), strikethrough=True)
256
+
257
+ def _handle_code_pattern(self, match: Match) -> RichText:
258
+ return RichText.from_plain_text(match.group(1), code=True)
259
+
260
+ def _handle_link_pattern(self, match: Match) -> RichText:
261
+ link_text, url = match.group(1), match.group(2)
262
+ return RichText.for_link(link_text, url)
263
+
264
+ def _handle_equation_pattern(self, match: Match) -> RichText:
265
+ expression = match.group(1)
266
+ return RichText.equation_inline(expression)
@@ -0,0 +1,164 @@
1
+ from enum import StrEnum
2
+ from typing import Self
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from notionary.blocks.enums import BlockColor
7
+
8
+
9
+ class RichTextType(StrEnum):
10
+ TEXT = "text"
11
+ MENTION = "mention"
12
+ EQUATION = "equation"
13
+
14
+
15
+ class MentionType(StrEnum):
16
+ USER = "user"
17
+ PAGE = "page"
18
+ DATABASE = "database"
19
+ DATE = "date"
20
+ LINK_PREVIEW = "link_preview"
21
+ TEMPLATE_MENTION = "template_mention"
22
+
23
+
24
+ class TemplateMentionType(StrEnum):
25
+ USER = "template_mention_user"
26
+ DATE = "template_mention_date"
27
+
28
+
29
+ class TextAnnotations(BaseModel):
30
+ bold: bool = False
31
+ italic: bool = False
32
+ strikethrough: bool = False
33
+ underline: bool = False
34
+ code: bool = False
35
+ color: BlockColor | None = None
36
+
37
+
38
+ class LinkObject(BaseModel):
39
+ url: str
40
+
41
+
42
+ class TextContent(BaseModel):
43
+ content: str
44
+ link: LinkObject | None = None
45
+
46
+
47
+ class EquationObject(BaseModel):
48
+ expression: str
49
+
50
+
51
+ class MentionUserRef(BaseModel):
52
+ id: str # Notion user id
53
+
54
+
55
+ class MentionPageRef(BaseModel):
56
+ id: str
57
+
58
+
59
+ class MentionDatabaseRef(BaseModel):
60
+ id: str
61
+
62
+
63
+ class MentionLinkPreview(BaseModel):
64
+ url: str
65
+
66
+
67
+ class MentionDate(BaseModel):
68
+ # entspricht Notion date object (start Pflicht, end/time_zone optional)
69
+ start: str # ISO 8601 date or datetime
70
+ end: str | None = None
71
+ time_zone: str | None = None
72
+
73
+
74
+ class MentionTemplateMention(BaseModel):
75
+ # Notion hat zwei Template-Mention-Typen
76
+ type: TemplateMentionType
77
+
78
+
79
+ class MentionObject(BaseModel):
80
+ type: MentionType
81
+ user: MentionUserRef | None = None
82
+ page: MentionPageRef | None = None
83
+ database: MentionDatabaseRef | None = None
84
+ date: MentionDate | None = None
85
+ link_preview: MentionLinkPreview | None = None
86
+ template_mention: MentionTemplateMention | None = None
87
+
88
+
89
+ class RichText(BaseModel):
90
+ type: RichTextType = RichTextType.TEXT
91
+
92
+ text: TextContent | None = None
93
+ annotations: TextAnnotations | None = None
94
+ plain_text: str = ""
95
+ href: str | None = None
96
+
97
+ mention: MentionObject | None = None
98
+
99
+ equation: EquationObject | None = None
100
+
101
+ @classmethod
102
+ def from_plain_text(cls, content: str, **ann) -> Self:
103
+ return cls(
104
+ type=RichTextType.TEXT,
105
+ text=TextContent(content=content),
106
+ annotations=TextAnnotations(**ann) if ann else TextAnnotations(),
107
+ plain_text=content,
108
+ )
109
+
110
+ @classmethod
111
+ def for_caption(cls, content: str) -> Self:
112
+ return cls(
113
+ type=RichTextType.TEXT,
114
+ text=TextContent(content=content),
115
+ annotations=None,
116
+ plain_text=content,
117
+ )
118
+
119
+ @classmethod
120
+ def for_code_block(cls, content: str) -> Self:
121
+ # keine annotations setzen → Notion Code-Highlight bleibt an
122
+ return cls.for_caption(content)
123
+
124
+ @classmethod
125
+ def for_link(cls, content: str, url: str, **ann) -> Self:
126
+ return cls(
127
+ type=RichTextType.TEXT,
128
+ text=TextContent(content=content, link=LinkObject(url=url)),
129
+ annotations=TextAnnotations(**ann) if ann else TextAnnotations(),
130
+ plain_text=content,
131
+ )
132
+
133
+ @classmethod
134
+ def mention_user(cls, user_id: str) -> Self:
135
+ return cls(
136
+ type=RichTextType.MENTION,
137
+ mention=MentionObject(type=MentionType.USER, user=MentionUserRef(id=user_id)),
138
+ annotations=TextAnnotations(),
139
+ )
140
+
141
+ @classmethod
142
+ def mention_page(cls, page_id: str) -> Self:
143
+ return cls(
144
+ type=RichTextType.MENTION,
145
+ mention=MentionObject(type=MentionType.PAGE, page=MentionPageRef(id=page_id)),
146
+ annotations=TextAnnotations(),
147
+ )
148
+
149
+ @classmethod
150
+ def mention_database(cls, database_id: str) -> Self:
151
+ return cls(
152
+ type=RichTextType.MENTION,
153
+ mention=MentionObject(type=MentionType.DATABASE, database=MentionDatabaseRef(id=database_id)),
154
+ annotations=TextAnnotations(),
155
+ )
156
+
157
+ @classmethod
158
+ def equation_inline(cls, expression: str) -> Self:
159
+ return cls(
160
+ type=RichTextType.EQUATION,
161
+ equation=EquationObject(expression=expression),
162
+ annotations=TextAnnotations(),
163
+ plain_text=expression,
164
+ )
@@ -0,0 +1,11 @@
1
+ from .database import DatabaseNameIdResolver
2
+ from .page import PageNameIdResolver
3
+ from .person import PersonNameIdResolver
4
+ from .port import NameIdResolver
5
+
6
+ __all__ = [
7
+ "DatabaseNameIdResolver",
8
+ "NameIdResolver",
9
+ "PageNameIdResolver",
10
+ "PersonNameIdResolver",
11
+ ]
@@ -0,0 +1,31 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.name_id_resolver.port import NameIdResolver
4
+ from notionary.workspace.query.service import WorkspaceQueryService
5
+
6
+
7
+ class DatabaseNameIdResolver(NameIdResolver):
8
+ def __init__(self, search_service: WorkspaceQueryService | None = None) -> None:
9
+ self.search_service = search_service or WorkspaceQueryService()
10
+
11
+ @override
12
+ async def resolve_name_to_id(self, name: str) -> str | None:
13
+ if not name:
14
+ return None
15
+
16
+ cleaned_name = name.strip()
17
+ database = await self.search_service.find_database(query=cleaned_name)
18
+ return database.id if database else None
19
+
20
+ @override
21
+ async def resolve_id_to_name(self, database_id: str) -> str | None:
22
+ if not database_id:
23
+ return None
24
+
25
+ try:
26
+ from notionary import NotionDatabase
27
+
28
+ database = await NotionDatabase.from_id(database_id)
29
+ return database.title if database else None
30
+ except Exception:
31
+ return None
@@ -0,0 +1,34 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.name_id_resolver.port import NameIdResolver
4
+ from notionary.workspace.query.service import WorkspaceQueryService
5
+
6
+
7
+ class PageNameIdResolver(NameIdResolver):
8
+ def __init__(self, search_service: WorkspaceQueryService | None = None) -> None:
9
+ self.search_service = search_service or WorkspaceQueryService()
10
+
11
+ @override
12
+ async def resolve_name_to_id(self, name: str) -> str | None:
13
+ if not name:
14
+ return None
15
+
16
+ cleaned_name = name.strip()
17
+ return await self._resolve_page_id(cleaned_name)
18
+
19
+ @override
20
+ async def resolve_id_to_name(self, page_id: str) -> str | None:
21
+ if not page_id:
22
+ return None
23
+
24
+ try:
25
+ from notionary import NotionPage
26
+
27
+ page = await NotionPage.from_id(page_id)
28
+ return page.title if page else None
29
+ except Exception:
30
+ return None
31
+
32
+ async def _resolve_page_id(self, name: str) -> str | None:
33
+ page = await self.search_service.find_page(query=name)
34
+ return page.id if page else None
@@ -0,0 +1,37 @@
1
+ from typing import override
2
+
3
+ from notionary.blocks.rich_text.name_id_resolver.port import NameIdResolver
4
+ from notionary.user.client import UserHttpClient
5
+ from notionary.user.person import PersonUser
6
+
7
+
8
+ class PersonNameIdResolver(NameIdResolver):
9
+ def __init__(self, person_user_factory=None, http_client: UserHttpClient | None = None) -> None:
10
+ if person_user_factory is None:
11
+ person_user_factory = PersonUser
12
+ self.person_user_factory = person_user_factory
13
+ self.http_client = http_client
14
+
15
+ @override
16
+ async def resolve_name_to_id(self, name: str | None) -> str | None:
17
+ if not name or not name.strip():
18
+ return None
19
+
20
+ name = name.strip()
21
+
22
+ try:
23
+ user = await self.person_user_factory.from_name(name, self.http_client)
24
+ return user.id if user else None
25
+ except Exception:
26
+ return None
27
+
28
+ @override
29
+ async def resolve_id_to_name(self, user_id: str | None) -> str | None:
30
+ if not user_id or not user_id.strip():
31
+ return None
32
+
33
+ try:
34
+ user = await self.person_user_factory.from_id(user_id.strip(), self.http_client)
35
+ return user.name if user else None
36
+ except Exception:
37
+ return None
@@ -0,0 +1,11 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class NameIdResolver(ABC):
5
+ @abstractmethod
6
+ def resolve_name_to_id(self, name: str) -> str | None:
7
+ pass
8
+
9
+ @abstractmethod
10
+ def resolve_id_to_name(self, id: str) -> str | None:
11
+ pass
@@ -0,0 +1,132 @@
1
+ from typing import ClassVar
2
+
3
+ from notionary.blocks.rich_text.models import (
4
+ MentionDate,
5
+ MentionType,
6
+ RichText,
7
+ RichTextType,
8
+ )
9
+ from notionary.blocks.rich_text.name_id_resolver import (
10
+ DatabaseNameIdResolver,
11
+ NameIdResolver,
12
+ PageNameIdResolver,
13
+ PersonNameIdResolver,
14
+ )
15
+ from notionary.blocks.schemas import BlockColor
16
+
17
+
18
+ class RichTextToMarkdownConverter:
19
+ VALID_COLORS: ClassVar[set[str]] = {color.value for color in BlockColor}
20
+
21
+ def __init__(
22
+ self,
23
+ *,
24
+ page_resolver: NameIdResolver | None = None,
25
+ database_resolver: NameIdResolver | None = None,
26
+ person_resolver: NameIdResolver | None = None,
27
+ ) -> None:
28
+ self.page_resolver = page_resolver or PageNameIdResolver()
29
+ self.database_resolver = database_resolver or DatabaseNameIdResolver()
30
+ self.person_resolver = person_resolver or PersonNameIdResolver()
31
+
32
+ async def to_markdown(self, rich_text: list[RichText]) -> str:
33
+ if not rich_text:
34
+ return ""
35
+
36
+ parts: list[str] = []
37
+
38
+ for rich_obj in rich_text:
39
+ formatted_text = await self._convert_rich_text_to_markdown(rich_obj)
40
+ parts.append(formatted_text)
41
+
42
+ return "".join(parts)
43
+
44
+ async def _convert_rich_text_to_markdown(self, obj: RichText) -> str:
45
+ if obj.type == RichTextType.EQUATION and obj.equation:
46
+ return f"${obj.equation.expression}$"
47
+
48
+ if obj.type == RichTextType.MENTION:
49
+ mention_markdown = await self._extract_mention_markdown(obj)
50
+ if mention_markdown:
51
+ return mention_markdown
52
+
53
+ content = obj.plain_text or (obj.text.content if obj.text else "")
54
+ return self._apply_text_formatting_to_content(obj, content)
55
+
56
+ async def _extract_mention_markdown(self, obj: RichText) -> str | None:
57
+ if not obj.mention:
58
+ return None
59
+
60
+ mention = obj.mention
61
+
62
+ if mention.type == MentionType.PAGE and mention.page:
63
+ return await self._extract_page_mention_markdown(mention.page.id)
64
+
65
+ elif mention.type == MentionType.DATABASE and mention.database:
66
+ return await self._extract_database_mention_markdown(mention.database.id)
67
+
68
+ elif mention.type == MentionType.USER and mention.user:
69
+ return await self._extract_user_mention_markdown(mention.user.id)
70
+
71
+ elif mention.type == MentionType.DATE and mention.date:
72
+ return self._extract_date_mention_markdown(mention.date)
73
+
74
+ return None
75
+
76
+ async def _extract_page_mention_markdown(self, page_id: str) -> str:
77
+ page_name = await self.page_resolver.resolve_id_to_name(page_id)
78
+ return f"@page[{page_name or page_id}]"
79
+
80
+ async def _extract_database_mention_markdown(self, database_id: str) -> str:
81
+ database_name = await self.database_resolver.resolve_id_to_name(database_id)
82
+ return f"@database[{database_name or database_id}]"
83
+
84
+ async def _extract_user_mention_markdown(self, user_id: str) -> str:
85
+ user_name = await self.person_resolver.resolve_id_to_name(user_id)
86
+ return f"@user[{user_name or user_id}]"
87
+
88
+ def _extract_date_mention_markdown(self, date_mention: MentionDate) -> str:
89
+ date_range = date_mention.start
90
+ if date_mention.end:
91
+ date_range += f"–{date_mention.end}"
92
+ return f"@date[{date_range}]"
93
+
94
+ def _apply_text_formatting_to_content(self, obj: RichText, content: str) -> str:
95
+ if obj.text and obj.text.link:
96
+ content = f"[{content}]({obj.text.link.url})"
97
+
98
+ if not obj.annotations:
99
+ return content
100
+
101
+ annotations = obj.annotations
102
+
103
+ if annotations.code:
104
+ content = f"`{content}`"
105
+ if annotations.strikethrough:
106
+ content = f"~~{content}~~"
107
+ if annotations.underline:
108
+ content = f"__{content}__"
109
+ if annotations.italic:
110
+ content = f"*{content}*"
111
+ if annotations.bold:
112
+ content = f"**{content}**"
113
+
114
+ if annotations.color != BlockColor.DEFAULT and annotations.color in self.VALID_COLORS:
115
+ content = f"({annotations.color}:{content})"
116
+
117
+ return content
118
+
119
+
120
+ async def convert_rich_text_to_markdown(
121
+ rich_text: list[RichText],
122
+ *,
123
+ page_resolver: NameIdResolver | None = None,
124
+ database_resolver: NameIdResolver | None = None,
125
+ person_resolver: NameIdResolver | None = None,
126
+ ) -> str:
127
+ converter = RichTextToMarkdownConverter(
128
+ page_resolver=page_resolver,
129
+ database_resolver=database_resolver,
130
+ person_resolver=person_resolver,
131
+ )
132
+ return await converter.to_markdown(rich_text)