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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. notionary/__init__.py +5 -20
  2. notionary/blocks/__init__.py +4 -4
  3. notionary/blocks/client.py +90 -216
  4. notionary/blocks/enums.py +167 -0
  5. notionary/blocks/rich_text/markdown_rich_text_converter.py +280 -0
  6. notionary/blocks/rich_text/models.py +178 -0
  7. notionary/blocks/rich_text/name_id_resolver/__init__.py +13 -0
  8. notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -0
  9. notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
  10. notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
  11. notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
  12. notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
  13. notionary/blocks/rich_text/rich_text_markdown_converter.py +144 -0
  14. notionary/blocks/rich_text/rich_text_patterns.py +42 -0
  15. notionary/blocks/schemas.py +778 -0
  16. notionary/comments/__init__.py +1 -22
  17. notionary/comments/client.py +52 -187
  18. notionary/comments/factory.py +38 -0
  19. notionary/comments/models.py +5 -127
  20. notionary/comments/schemas.py +240 -0
  21. notionary/comments/service.py +34 -0
  22. notionary/data_source/http/client.py +11 -0
  23. notionary/data_source/http/data_source_instance_client.py +104 -0
  24. notionary/data_source/properties/schemas.py +402 -0
  25. notionary/data_source/query/builder.py +448 -0
  26. notionary/data_source/query/resolver.py +114 -0
  27. notionary/data_source/query/schema.py +302 -0
  28. notionary/data_source/query/validator.py +73 -0
  29. notionary/data_source/schema/registry.py +104 -0
  30. notionary/data_source/schema/service.py +136 -0
  31. notionary/data_source/schemas.py +27 -0
  32. notionary/data_source/service.py +377 -0
  33. notionary/database/client.py +30 -135
  34. notionary/database/database_metadata_update_client.py +19 -0
  35. notionary/database/schemas.py +29 -0
  36. notionary/database/service.py +168 -0
  37. notionary/exceptions/__init__.py +33 -0
  38. notionary/exceptions/api.py +41 -0
  39. notionary/exceptions/base.py +2 -0
  40. notionary/exceptions/block_parsing.py +16 -0
  41. notionary/exceptions/data_source/__init__.py +6 -0
  42. notionary/exceptions/data_source/builder.py +182 -0
  43. notionary/exceptions/data_source/properties.py +34 -0
  44. notionary/exceptions/properties.py +58 -0
  45. notionary/exceptions/search.py +57 -0
  46. notionary/file_upload/client.py +18 -30
  47. notionary/file_upload/models.py +7 -8
  48. notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
  49. notionary/http/client.py +204 -0
  50. notionary/http/models.py +50 -0
  51. notionary/page/blocks/client.py +1 -0
  52. notionary/page/content/factory.py +73 -0
  53. notionary/page/content/markdown/__init__.py +5 -0
  54. notionary/page/content/markdown/builder.py +226 -0
  55. notionary/page/content/markdown/nodes/__init__.py +52 -0
  56. notionary/page/content/markdown/nodes/audio.py +23 -0
  57. notionary/page/content/markdown/nodes/base.py +12 -0
  58. notionary/page/content/markdown/nodes/bookmark.py +25 -0
  59. notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
  60. notionary/page/content/markdown/nodes/bulleted_list.py +41 -0
  61. notionary/page/content/markdown/nodes/callout.py +34 -0
  62. notionary/page/content/markdown/nodes/code.py +28 -0
  63. notionary/page/content/markdown/nodes/columns.py +69 -0
  64. notionary/page/content/markdown/nodes/container.py +64 -0
  65. notionary/page/content/markdown/nodes/divider.py +14 -0
  66. notionary/page/content/markdown/nodes/embed.py +23 -0
  67. notionary/page/content/markdown/nodes/equation.py +19 -0
  68. notionary/page/content/markdown/nodes/file.py +23 -0
  69. notionary/page/content/markdown/nodes/heading.py +36 -0
  70. notionary/page/content/markdown/nodes/image.py +23 -0
  71. notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
  72. notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
  73. notionary/page/content/markdown/nodes/numbered_list.py +38 -0
  74. notionary/page/content/markdown/nodes/paragraph.py +14 -0
  75. notionary/page/content/markdown/nodes/pdf.py +23 -0
  76. notionary/page/content/markdown/nodes/quote.py +27 -0
  77. notionary/page/content/markdown/nodes/space.py +14 -0
  78. notionary/page/content/markdown/nodes/table.py +45 -0
  79. notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
  80. notionary/page/content/markdown/nodes/todo.py +38 -0
  81. notionary/page/content/markdown/nodes/toggle.py +27 -0
  82. notionary/page/content/markdown/nodes/video.py +23 -0
  83. notionary/page/content/parser/context.py +126 -0
  84. notionary/page/content/parser/factory.py +210 -0
  85. notionary/page/content/parser/parsers/__init__.py +58 -0
  86. notionary/page/content/parser/parsers/audio.py +40 -0
  87. notionary/page/content/parser/parsers/base.py +30 -0
  88. notionary/page/content/parser/parsers/bookmark.py +33 -0
  89. notionary/page/content/parser/parsers/breadcrumb.py +33 -0
  90. notionary/page/content/parser/parsers/bulleted_list.py +85 -0
  91. notionary/page/content/parser/parsers/callout.py +100 -0
  92. notionary/page/content/parser/parsers/caption.py +55 -0
  93. notionary/page/content/parser/parsers/code.py +81 -0
  94. notionary/page/content/parser/parsers/column.py +76 -0
  95. notionary/page/content/parser/parsers/column_list.py +81 -0
  96. notionary/page/content/parser/parsers/divider.py +33 -0
  97. notionary/page/content/parser/parsers/embed.py +33 -0
  98. notionary/page/content/parser/parsers/equation.py +65 -0
  99. notionary/page/content/parser/parsers/file.py +42 -0
  100. notionary/page/content/parser/parsers/heading.py +115 -0
  101. notionary/page/content/parser/parsers/image.py +42 -0
  102. notionary/page/content/parser/parsers/numbered_list.py +89 -0
  103. notionary/page/content/parser/parsers/paragraph.py +37 -0
  104. notionary/page/content/parser/parsers/pdf.py +42 -0
  105. notionary/page/content/parser/parsers/quote.py +125 -0
  106. notionary/page/content/parser/parsers/space.py +41 -0
  107. notionary/page/content/parser/parsers/table.py +144 -0
  108. notionary/page/content/parser/parsers/table_of_contents.py +32 -0
  109. notionary/page/content/parser/parsers/todo.py +96 -0
  110. notionary/page/content/parser/parsers/toggle.py +70 -0
  111. notionary/page/content/parser/parsers/video.py +42 -0
  112. notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
  113. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +95 -0
  114. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +114 -0
  115. notionary/page/content/parser/post_processing/port.py +9 -0
  116. notionary/page/content/parser/post_processing/service.py +16 -0
  117. notionary/page/content/parser/pre_processsing/handlers/__init__.py +11 -0
  118. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +130 -0
  119. notionary/page/content/parser/pre_processsing/handlers/indentation.py +84 -0
  120. notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
  121. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +73 -0
  122. notionary/page/content/parser/pre_processsing/service.py +15 -0
  123. notionary/page/content/parser/service.py +78 -0
  124. notionary/page/content/renderer/context.py +51 -0
  125. notionary/page/content/renderer/factory.py +231 -0
  126. notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
  127. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -0
  128. notionary/page/content/renderer/post_processing/port.py +7 -0
  129. notionary/page/content/renderer/post_processing/service.py +15 -0
  130. notionary/page/content/renderer/renderers/__init__.py +55 -0
  131. notionary/page/content/renderer/renderers/audio.py +31 -0
  132. notionary/page/content/renderer/renderers/base.py +31 -0
  133. notionary/page/content/renderer/renderers/bookmark.py +25 -0
  134. notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
  135. notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
  136. notionary/page/content/renderer/renderers/callout.py +50 -0
  137. notionary/page/content/renderer/renderers/captioned_block.py +58 -0
  138. notionary/page/content/renderer/renderers/code.py +34 -0
  139. notionary/page/content/renderer/renderers/column.py +53 -0
  140. notionary/page/content/renderer/renderers/column_list.py +44 -0
  141. notionary/page/content/renderer/renderers/divider.py +22 -0
  142. notionary/page/content/renderer/renderers/embed.py +25 -0
  143. notionary/page/content/renderer/renderers/equation.py +37 -0
  144. notionary/page/content/renderer/renderers/fallback.py +24 -0
  145. notionary/page/content/renderer/renderers/file.py +40 -0
  146. notionary/page/content/renderer/renderers/heading.py +95 -0
  147. notionary/page/content/renderer/renderers/image.py +31 -0
  148. notionary/page/content/renderer/renderers/numbered_list.py +42 -0
  149. notionary/page/content/renderer/renderers/paragraph.py +40 -0
  150. notionary/page/content/renderer/renderers/pdf.py +31 -0
  151. notionary/page/content/renderer/renderers/quote.py +49 -0
  152. notionary/page/content/renderer/renderers/table.py +115 -0
  153. notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
  154. notionary/page/content/renderer/renderers/table_row.py +17 -0
  155. notionary/page/content/renderer/renderers/todo.py +56 -0
  156. notionary/page/content/renderer/renderers/toggle.py +52 -0
  157. notionary/page/content/renderer/renderers/video.py +31 -0
  158. notionary/page/content/renderer/service.py +50 -0
  159. notionary/page/content/service.py +68 -0
  160. notionary/page/content/syntax/__init__.py +4 -0
  161. notionary/page/content/syntax/grammar.py +10 -0
  162. notionary/page/content/syntax/models.py +66 -0
  163. notionary/page/content/syntax/registry.py +393 -0
  164. notionary/page/page_context.py +7 -16
  165. notionary/page/page_http_client.py +15 -0
  166. notionary/page/page_metadata_update_client.py +19 -0
  167. notionary/page/properties/client.py +144 -0
  168. notionary/page/properties/factory.py +26 -0
  169. notionary/page/properties/models.py +308 -0
  170. notionary/page/properties/service.py +261 -0
  171. notionary/page/schemas.py +13 -0
  172. notionary/page/service.py +225 -0
  173. notionary/shared/entity/client.py +29 -0
  174. notionary/shared/entity/dto_parsers.py +53 -0
  175. notionary/shared/entity/entity_metadata_update_client.py +41 -0
  176. notionary/shared/entity/schemas.py +45 -0
  177. notionary/shared/entity/service.py +171 -0
  178. notionary/shared/models/cover.py +20 -0
  179. notionary/shared/models/file.py +21 -0
  180. notionary/shared/models/icon.py +28 -0
  181. notionary/shared/models/parent.py +41 -0
  182. notionary/shared/properties/type.py +30 -0
  183. notionary/shared/typings.py +3 -0
  184. notionary/user/__init__.py +4 -8
  185. notionary/user/base.py +138 -0
  186. notionary/user/bot.py +70 -0
  187. notionary/user/client.py +22 -111
  188. notionary/user/person.py +41 -0
  189. notionary/user/schemas.py +67 -0
  190. notionary/user/service.py +65 -0
  191. notionary/utils/date.py +51 -0
  192. notionary/utils/decorators.py +122 -0
  193. notionary/utils/fuzzy.py +68 -0
  194. notionary/utils/mixins/logging.py +58 -0
  195. notionary/utils/pagination.py +100 -0
  196. notionary/utils/uuid_utils.py +20 -0
  197. notionary/workspace/__init__.py +4 -0
  198. notionary/workspace/client.py +62 -0
  199. notionary/workspace/query/__init__.py +3 -0
  200. notionary/workspace/query/builder.py +60 -0
  201. notionary/workspace/query/models.py +61 -0
  202. notionary/workspace/query/service.py +100 -0
  203. notionary/workspace/schemas.py +21 -0
  204. notionary/workspace/service.py +116 -0
  205. notionary-0.3.0.dist-info/METADATA +201 -0
  206. notionary-0.3.0.dist-info/RECORD +209 -0
  207. {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info}/WHEEL +1 -1
  208. {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info/licenses}/LICENSE +9 -9
  209. notionary/base_notion_client.py +0 -219
  210. notionary/blocks/_bootstrap.py +0 -271
  211. notionary/blocks/audio/__init__.py +0 -11
  212. notionary/blocks/audio/audio_element.py +0 -158
  213. notionary/blocks/audio/audio_markdown_node.py +0 -24
  214. notionary/blocks/audio/audio_models.py +0 -10
  215. notionary/blocks/base_block_element.py +0 -42
  216. notionary/blocks/bookmark/__init__.py +0 -12
  217. notionary/blocks/bookmark/bookmark_element.py +0 -83
  218. notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
  219. notionary/blocks/bookmark/bookmark_models.py +0 -15
  220. notionary/blocks/breadcrumbs/__init__.py +0 -15
  221. notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
  222. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
  223. notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
  224. notionary/blocks/bulleted_list/__init__.py +0 -15
  225. notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
  226. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
  227. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
  228. notionary/blocks/callout/__init__.py +0 -12
  229. notionary/blocks/callout/callout_element.py +0 -99
  230. notionary/blocks/callout/callout_markdown_node.py +0 -19
  231. notionary/blocks/callout/callout_models.py +0 -33
  232. notionary/blocks/child_database/__init__.py +0 -14
  233. notionary/blocks/child_database/child_database_element.py +0 -59
  234. notionary/blocks/child_database/child_database_models.py +0 -12
  235. notionary/blocks/child_page/__init__.py +0 -9
  236. notionary/blocks/child_page/child_page_element.py +0 -94
  237. notionary/blocks/child_page/child_page_models.py +0 -12
  238. notionary/blocks/code/__init__.py +0 -11
  239. notionary/blocks/code/code_element.py +0 -149
  240. notionary/blocks/code/code_markdown_node.py +0 -80
  241. notionary/blocks/code/code_models.py +0 -94
  242. notionary/blocks/column/__init__.py +0 -25
  243. notionary/blocks/column/column_element.py +0 -65
  244. notionary/blocks/column/column_list_element.py +0 -52
  245. notionary/blocks/column/column_list_markdown_node.py +0 -34
  246. notionary/blocks/column/column_markdown_node.py +0 -42
  247. notionary/blocks/column/column_models.py +0 -26
  248. notionary/blocks/divider/__init__.py +0 -12
  249. notionary/blocks/divider/divider_element.py +0 -41
  250. notionary/blocks/divider/divider_markdown_node.py +0 -11
  251. notionary/blocks/divider/divider_models.py +0 -12
  252. notionary/blocks/embed/__init__.py +0 -12
  253. notionary/blocks/embed/embed_element.py +0 -98
  254. notionary/blocks/embed/embed_markdown_node.py +0 -19
  255. notionary/blocks/embed/embed_models.py +0 -14
  256. notionary/blocks/equation/__init__.py +0 -13
  257. notionary/blocks/equation/equation_element.py +0 -133
  258. notionary/blocks/equation/equation_element_markdown_node.py +0 -23
  259. notionary/blocks/equation/equation_models.py +0 -11
  260. notionary/blocks/file/__init__.py +0 -23
  261. notionary/blocks/file/file_element.py +0 -133
  262. notionary/blocks/file/file_element_markdown_node.py +0 -24
  263. notionary/blocks/file/file_element_models.py +0 -39
  264. notionary/blocks/heading/__init__.py +0 -19
  265. notionary/blocks/heading/heading_element.py +0 -112
  266. notionary/blocks/heading/heading_markdown_node.py +0 -16
  267. notionary/blocks/heading/heading_models.py +0 -29
  268. notionary/blocks/image_block/__init__.py +0 -11
  269. notionary/blocks/image_block/image_element.py +0 -130
  270. notionary/blocks/image_block/image_markdown_node.py +0 -25
  271. notionary/blocks/image_block/image_models.py +0 -10
  272. notionary/blocks/markdown/markdown_builder.py +0 -525
  273. notionary/blocks/markdown/markdown_document_model.py +0 -0
  274. notionary/blocks/markdown/markdown_node.py +0 -25
  275. notionary/blocks/mixins/captions/__init__.py +0 -4
  276. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
  277. notionary/blocks/mixins/captions/caption_mixin.py +0 -92
  278. notionary/blocks/mixins/file_upload/__init__.py +0 -3
  279. notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
  280. notionary/blocks/models.py +0 -174
  281. notionary/blocks/numbered_list/__init__.py +0 -16
  282. notionary/blocks/numbered_list/numbered_list_element.py +0 -65
  283. notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
  284. notionary/blocks/numbered_list/numbered_list_models.py +0 -17
  285. notionary/blocks/paragraph/__init__.py +0 -15
  286. notionary/blocks/paragraph/paragraph_element.py +0 -58
  287. notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
  288. notionary/blocks/paragraph/paragraph_models.py +0 -16
  289. notionary/blocks/pdf/__init__.py +0 -11
  290. notionary/blocks/pdf/pdf_element.py +0 -146
  291. notionary/blocks/pdf/pdf_markdown_node.py +0 -24
  292. notionary/blocks/pdf/pdf_models.py +0 -11
  293. notionary/blocks/quote/__init__.py +0 -14
  294. notionary/blocks/quote/quote_element.py +0 -75
  295. notionary/blocks/quote/quote_markdown_node.py +0 -16
  296. notionary/blocks/quote/quote_models.py +0 -18
  297. notionary/blocks/registry/__init__.py +0 -3
  298. notionary/blocks/registry/block_registry.py +0 -150
  299. notionary/blocks/rich_text/__init__.py +0 -33
  300. notionary/blocks/rich_text/rich_text_models.py +0 -221
  301. notionary/blocks/rich_text/text_inline_formatter.py +0 -456
  302. notionary/blocks/syntax_prompt_builder.py +0 -137
  303. notionary/blocks/table/__init__.py +0 -19
  304. notionary/blocks/table/table_element.py +0 -225
  305. notionary/blocks/table/table_markdown_node.py +0 -42
  306. notionary/blocks/table/table_models.py +0 -28
  307. notionary/blocks/table_of_contents/__init__.py +0 -17
  308. notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
  309. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
  310. notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
  311. notionary/blocks/todo/__init__.py +0 -12
  312. notionary/blocks/todo/todo_element.py +0 -81
  313. notionary/blocks/todo/todo_markdown_node.py +0 -21
  314. notionary/blocks/todo/todo_models.py +0 -18
  315. notionary/blocks/toggle/__init__.py +0 -12
  316. notionary/blocks/toggle/toggle_element.py +0 -112
  317. notionary/blocks/toggle/toggle_markdown_node.py +0 -31
  318. notionary/blocks/toggle/toggle_models.py +0 -17
  319. notionary/blocks/toggleable_heading/__init__.py +0 -11
  320. notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
  321. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
  322. notionary/blocks/types.py +0 -130
  323. notionary/blocks/video/__init__.py +0 -11
  324. notionary/blocks/video/video_element.py +0 -187
  325. notionary/blocks/video/video_element_models.py +0 -10
  326. notionary/blocks/video/video_markdown_node.py +0 -26
  327. notionary/database/__init__.py +0 -4
  328. notionary/database/database.py +0 -480
  329. notionary/database/database_filter_builder.py +0 -173
  330. notionary/database/database_provider.py +0 -227
  331. notionary/database/exceptions.py +0 -13
  332. notionary/database/models.py +0 -337
  333. notionary/database/notion_database.py +0 -487
  334. notionary/file_upload/__init__.py +0 -7
  335. notionary/page/client.py +0 -124
  336. notionary/page/markdown_whitespace_processor.py +0 -129
  337. notionary/page/models.py +0 -322
  338. notionary/page/notion_page.py +0 -712
  339. notionary/page/page_content_deleting_service.py +0 -117
  340. notionary/page/page_content_writer.py +0 -80
  341. notionary/page/property_formatter.py +0 -99
  342. notionary/page/reader/handler/__init__.py +0 -19
  343. notionary/page/reader/handler/base_block_renderer.py +0 -44
  344. notionary/page/reader/handler/block_processing_context.py +0 -35
  345. notionary/page/reader/handler/block_rendering_context.py +0 -48
  346. notionary/page/reader/handler/column_list_renderer.py +0 -51
  347. notionary/page/reader/handler/column_renderer.py +0 -60
  348. notionary/page/reader/handler/equation_renderer.py +0 -0
  349. notionary/page/reader/handler/line_renderer.py +0 -73
  350. notionary/page/reader/handler/numbered_list_renderer.py +0 -85
  351. notionary/page/reader/handler/toggle_renderer.py +0 -69
  352. notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
  353. notionary/page/reader/page_content_retriever.py +0 -81
  354. notionary/page/search_filter_builder.py +0 -132
  355. notionary/page/utils.py +0 -60
  356. notionary/page/writer/handler/__init__.py +0 -24
  357. notionary/page/writer/handler/code_handler.py +0 -72
  358. notionary/page/writer/handler/column_handler.py +0 -141
  359. notionary/page/writer/handler/column_list_handler.py +0 -139
  360. notionary/page/writer/handler/equation_handler.py +0 -74
  361. notionary/page/writer/handler/line_handler.py +0 -35
  362. notionary/page/writer/handler/line_processing_context.py +0 -54
  363. notionary/page/writer/handler/regular_line_handler.py +0 -86
  364. notionary/page/writer/handler/table_handler.py +0 -66
  365. notionary/page/writer/handler/toggle_handler.py +0 -159
  366. notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
  367. notionary/page/writer/markdown_to_notion_converter.py +0 -139
  368. notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
  369. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  370. notionary/page/writer/notion_text_length_processor.py +0 -150
  371. notionary/schemas/__init__.py +0 -3
  372. notionary/schemas/base.py +0 -73
  373. notionary/shared/__init__.py +0 -3
  374. notionary/shared/name_to_id_resolver.py +0 -203
  375. notionary/telemetry/__init__.py +0 -19
  376. notionary/telemetry/service.py +0 -136
  377. notionary/telemetry/views.py +0 -73
  378. notionary/user/base_notion_user.py +0 -53
  379. notionary/user/models.py +0 -84
  380. notionary/user/notion_bot_user.py +0 -226
  381. notionary/user/notion_user.py +0 -255
  382. notionary/user/notion_user_manager.py +0 -101
  383. notionary/util/__init__.py +0 -15
  384. notionary/util/concurrency_limiter.py +0 -0
  385. notionary/util/factory_decorator.py +0 -0
  386. notionary/util/factory_only.py +0 -37
  387. notionary/util/fuzzy.py +0 -75
  388. notionary/util/logging_mixin.py +0 -59
  389. notionary/util/page_id_utils.py +0 -27
  390. notionary/util/singleton.py +0 -18
  391. notionary/util/singleton_metaclass.py +0 -22
  392. notionary/workspace.py +0 -105
  393. notionary-0.2.27.dist-info/METADATA +0 -270
  394. notionary-0.2.27.dist-info/RECORD +0 -202
  395. /notionary/{database → user}/factory.py +0 -0
@@ -0,0 +1,302 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import StrEnum
4
+ from typing import Self
5
+
6
+ from pydantic import BaseModel, ValidationInfo, field_validator, model_serializer, model_validator
7
+
8
+ from notionary.shared.properties.type import PropertyType
9
+ from notionary.shared.typings import JsonDict
10
+
11
+
12
+ class FieldType(StrEnum):
13
+ STRING = "string"
14
+ NUMBER = "number"
15
+ BOOLEAN = "boolean"
16
+ DATE = "date"
17
+ DATETIME = "datetime"
18
+ ARRAY = "array"
19
+ RELATION = "relation"
20
+ PEOPLE = "people"
21
+
22
+
23
+ class StringOperator(StrEnum):
24
+ EQUALS = "equals"
25
+ DOES_NOT_EQUAL = "does_not_equal"
26
+ CONTAINS = "contains"
27
+ DOES_NOT_CONTAIN = "does_not_contain"
28
+ STARTS_WITH = "starts_with"
29
+ ENDS_WITH = "ends_with"
30
+ IS_EMPTY = "is_empty"
31
+ IS_NOT_EMPTY = "is_not_empty"
32
+
33
+
34
+ class NumberOperator(StrEnum):
35
+ EQUALS = "equals"
36
+ DOES_NOT_EQUAL = "does_not_equal"
37
+ GREATER_THAN = "greater_than"
38
+ GREATER_THAN_OR_EQUAL_TO = "greater_than_or_equal_to"
39
+ LESS_THAN = "less_than"
40
+ LESS_THAN_OR_EQUAL_TO = "less_than_or_equal_to"
41
+ IS_EMPTY = "is_empty"
42
+ IS_NOT_EMPTY = "is_not_empty"
43
+
44
+
45
+ class BooleanOperator(StrEnum):
46
+ IS_TRUE = "is_true"
47
+ IS_FALSE = "is_false"
48
+
49
+
50
+ class SelectOperator(StrEnum):
51
+ EQUALS = "equals"
52
+ DOES_NOT_EQUAL = "does_not_equal"
53
+ IS_EMPTY = "is_empty"
54
+ IS_NOT_EMPTY = "is_not_empty"
55
+
56
+
57
+ class DateOperator(StrEnum):
58
+ EQUALS = "equals"
59
+ BEFORE = "before"
60
+ AFTER = "after"
61
+ ON_OR_BEFORE = "on_or_before"
62
+ ON_OR_AFTER = "on_or_after"
63
+ IS_EMPTY = "is_empty"
64
+ IS_NOT_EMPTY = "is_not_empty"
65
+
66
+
67
+ class ArrayOperator(StrEnum):
68
+ CONTAINS = "contains"
69
+ DOES_NOT_CONTAIN = "does_not_contain"
70
+ IS_EMPTY = "is_empty"
71
+ IS_NOT_EMPTY = "is_not_empty"
72
+
73
+
74
+ class LogicalOperator(StrEnum):
75
+ AND = "and"
76
+ OR = "or"
77
+
78
+
79
+ class SortDirection(StrEnum):
80
+ ASCENDING = "ascending"
81
+ DESCENDING = "descending"
82
+
83
+
84
+ class TimestampType(StrEnum):
85
+ CREATED_TIME = "created_time"
86
+ LAST_EDITED_TIME = "last_edited_time"
87
+
88
+
89
+ class TimeUnit(StrEnum):
90
+ DAYS = "days"
91
+ WEEKS = "weeks"
92
+ MONTHS = "months"
93
+ YEARS = "years"
94
+
95
+
96
+ type Operator = StringOperator | NumberOperator | BooleanOperator | DateOperator | ArrayOperator
97
+ type FilterValue = str | int | float | bool | list[str | int | float]
98
+
99
+
100
+ class FilterCondition(BaseModel):
101
+ field: str
102
+ field_type: FieldType
103
+ operator: Operator
104
+ value: FilterValue | None = None
105
+ time_value: int | None = None
106
+ time_unit: TimeUnit | None = None
107
+
108
+ @model_validator(mode="after")
109
+ def validate_operator_and_value(self) -> Self:
110
+ self._validate_no_value_operators()
111
+ self._validate_value_required_operators()
112
+ self._validate_value_type_matches_field_type()
113
+ return self
114
+
115
+ def _validate_no_value_operators(self) -> None:
116
+ no_value_ops = {
117
+ StringOperator.IS_EMPTY,
118
+ StringOperator.IS_NOT_EMPTY,
119
+ NumberOperator.IS_EMPTY,
120
+ NumberOperator.IS_NOT_EMPTY,
121
+ BooleanOperator.IS_TRUE,
122
+ BooleanOperator.IS_FALSE,
123
+ DateOperator.IS_EMPTY,
124
+ DateOperator.IS_NOT_EMPTY,
125
+ ArrayOperator.IS_EMPTY,
126
+ ArrayOperator.IS_NOT_EMPTY,
127
+ }
128
+ if self.operator in no_value_ops and self.value is not None:
129
+ raise ValueError(f"Operator '{self.operator}' does not expect a value")
130
+
131
+ def _validate_value_required_operators(self) -> None:
132
+ operators_to_skip = {
133
+ StringOperator.IS_EMPTY,
134
+ StringOperator.IS_NOT_EMPTY,
135
+ NumberOperator.IS_EMPTY,
136
+ NumberOperator.IS_NOT_EMPTY,
137
+ BooleanOperator.IS_TRUE,
138
+ BooleanOperator.IS_FALSE,
139
+ DateOperator.IS_EMPTY,
140
+ DateOperator.IS_NOT_EMPTY,
141
+ ArrayOperator.IS_EMPTY,
142
+ ArrayOperator.IS_NOT_EMPTY,
143
+ }
144
+
145
+ is_skipped_operator = self.operator in operators_to_skip
146
+ if not is_skipped_operator and self.value is None:
147
+ raise ValueError(f"Operator '{self.operator}' requires a value")
148
+
149
+ def _validate_value_type_matches_field_type(self) -> None:
150
+ if self.value is None:
151
+ return
152
+
153
+ if self.field_type == FieldType.STRING:
154
+ self._ensure_value_is_string()
155
+ elif self.field_type == FieldType.NUMBER:
156
+ self._ensure_value_is_number()
157
+ elif self.field_type == FieldType.BOOLEAN:
158
+ self._ensure_value_is_boolean()
159
+ elif self.field_type in (FieldType.DATE, FieldType.DATETIME) or self.field_type in (
160
+ FieldType.ARRAY,
161
+ FieldType.RELATION,
162
+ FieldType.PEOPLE,
163
+ ):
164
+ self._ensure_value_is_string()
165
+
166
+ def _ensure_value_is_string(self) -> None:
167
+ if not isinstance(self.value, str):
168
+ raise ValueError(
169
+ f"Value for field type '{self.field_type}' must be a string, got {type(self.value).__name__}"
170
+ )
171
+
172
+ def _ensure_value_is_number(self) -> None:
173
+ if not isinstance(self.value, (int, float)):
174
+ raise ValueError(
175
+ f"Value for field type '{self.field_type}' must be a number (int or float), "
176
+ f"got {type(self.value).__name__}"
177
+ )
178
+
179
+ def _ensure_value_is_boolean(self) -> None:
180
+ if not isinstance(self.value, bool):
181
+ raise ValueError(
182
+ f"Value for field type '{self.field_type}' must be a boolean, got {type(self.value).__name__}"
183
+ )
184
+
185
+ @field_validator("operator")
186
+ @classmethod
187
+ def validate_operator_for_field_type(
188
+ cls,
189
+ value: Operator,
190
+ info: ValidationInfo,
191
+ ) -> Operator:
192
+ if "field_type" not in info.data:
193
+ return value
194
+
195
+ field_type: FieldType = info.data["field_type"]
196
+ operator_value = value if isinstance(value, str) else value.value
197
+
198
+ if not cls._is_operator_valid_for_field_type(operator_value, field_type):
199
+ raise ValueError(f"Operator '{operator_value}' is not valid for field type '{field_type}'")
200
+
201
+ return value
202
+
203
+ @staticmethod
204
+ def _is_operator_valid_for_field_type(operator: str, field_type: FieldType) -> bool:
205
+ valid_operators: dict[FieldType, list[str]] = {
206
+ FieldType.STRING: [op.value for op in StringOperator],
207
+ FieldType.NUMBER: [op.value for op in NumberOperator],
208
+ FieldType.BOOLEAN: [op.value for op in BooleanOperator],
209
+ FieldType.DATE: [op.value for op in DateOperator],
210
+ FieldType.DATETIME: [op.value for op in DateOperator],
211
+ FieldType.ARRAY: [op.value for op in ArrayOperator],
212
+ FieldType.RELATION: [op.value for op in ArrayOperator],
213
+ FieldType.PEOPLE: [op.value for op in ArrayOperator],
214
+ }
215
+
216
+ return operator in valid_operators.get(field_type, [])
217
+
218
+
219
+ class OrGroupMarker(BaseModel):
220
+ conditions: list[FilterCondition]
221
+
222
+
223
+ type InternalFilterCondition = FilterCondition | OrGroupMarker
224
+
225
+
226
+ class PropertyFilter(BaseModel):
227
+ property: str
228
+ property_type: PropertyType
229
+ operator: Operator
230
+ value: FilterValue | None = None
231
+
232
+ @model_validator(mode="after")
233
+ def validate_value_type(self) -> Self:
234
+ if self.value is None:
235
+ return self
236
+
237
+ if self.property_type in (PropertyType.PEOPLE, PropertyType.RELATION) and not isinstance(self.value, str):
238
+ raise ValueError(
239
+ f"Value for property type '{self.property_type.value}' must be a string, "
240
+ f"got {type(self.value).__name__}"
241
+ )
242
+
243
+ return self
244
+
245
+ @model_serializer
246
+ def serialize_model(self) -> JsonDict:
247
+ property_type_str = self.property_type.value
248
+ operator_str = self.operator.value
249
+ filter_value = self.value
250
+
251
+ if isinstance(self.operator, BooleanOperator):
252
+ operator_str = "equals"
253
+ filter_value = self.operator == BooleanOperator.IS_TRUE
254
+
255
+ return {
256
+ "property": self.property,
257
+ property_type_str: {operator_str: filter_value if filter_value is not None else True},
258
+ }
259
+
260
+
261
+ class CompoundFilter(BaseModel):
262
+ operator: LogicalOperator
263
+ filters: list[PropertyFilter | CompoundFilter]
264
+
265
+ @model_serializer
266
+ def serialize_model(self) -> JsonDict:
267
+ operator_str = self.operator.value
268
+ return {operator_str: [f.model_dump() for f in self.filters]}
269
+
270
+
271
+ type NotionFilter = PropertyFilter | CompoundFilter
272
+
273
+
274
+ class PropertySort(BaseModel):
275
+ property: str
276
+ direction: SortDirection
277
+
278
+
279
+ class TimestampSort(BaseModel):
280
+ timestamp: TimestampType
281
+ direction: SortDirection
282
+
283
+
284
+ type NotionSort = PropertySort | TimestampSort
285
+
286
+
287
+ class DataSourceQueryParams(BaseModel):
288
+ filter: NotionFilter | None = None
289
+ sorts: list[NotionSort] | None = None
290
+ page_size: int | None = None
291
+
292
+ @model_serializer
293
+ def to_api_params(self) -> JsonDict:
294
+ result: JsonDict = {}
295
+
296
+ if self.filter is not None:
297
+ result["filter"] = self.filter.model_dump()
298
+
299
+ if self.sorts is not None and len(self.sorts) > 0:
300
+ result["sorts"] = [sort.model_dump() for sort in self.sorts]
301
+
302
+ return result
@@ -0,0 +1,73 @@
1
+ from typing import ClassVar
2
+
3
+ from notionary.data_source.properties.schemas import DataSourceProperty
4
+ from notionary.data_source.query.schema import (
5
+ ArrayOperator,
6
+ BooleanOperator,
7
+ DateOperator,
8
+ NumberOperator,
9
+ Operator,
10
+ SelectOperator,
11
+ StringOperator,
12
+ )
13
+ from notionary.exceptions.data_source.builder import InvalidOperatorForPropertyType
14
+ from notionary.shared.properties.type import PropertyType
15
+
16
+
17
+ class QueryValidator:
18
+ _PROPERTY_TYPE_OPERATORS: ClassVar[dict[PropertyType, list[type[Operator]]]] = {
19
+ PropertyType.TITLE: [StringOperator],
20
+ PropertyType.RICH_TEXT: [StringOperator],
21
+ PropertyType.URL: [StringOperator],
22
+ PropertyType.EMAIL: [StringOperator],
23
+ PropertyType.PHONE_NUMBER: [StringOperator],
24
+ PropertyType.SELECT: [SelectOperator],
25
+ PropertyType.STATUS: [SelectOperator],
26
+ PropertyType.MULTI_SELECT: [ArrayOperator],
27
+ PropertyType.NUMBER: [NumberOperator],
28
+ PropertyType.DATE: [DateOperator],
29
+ PropertyType.CREATED_TIME: [DateOperator],
30
+ PropertyType.LAST_EDITED_TIME: [DateOperator],
31
+ PropertyType.PEOPLE: [ArrayOperator],
32
+ PropertyType.CREATED_BY: [ArrayOperator],
33
+ PropertyType.LAST_EDITED_BY: [ArrayOperator],
34
+ PropertyType.RELATION: [ArrayOperator],
35
+ PropertyType.CHECKBOX: [BooleanOperator],
36
+ }
37
+
38
+ def validate_operator_for_property(
39
+ self, property_name: str, property_obj: DataSourceProperty, operator: Operator
40
+ ) -> None:
41
+ if not self._is_operator_valid_for_property_type(property_obj.type, operator):
42
+ valid_operators = self._get_valid_operators_for_property_type(property_obj.type)
43
+ raise InvalidOperatorForPropertyType(
44
+ property_name=property_name,
45
+ property_type=property_obj.type,
46
+ operator=operator,
47
+ valid_operators=valid_operators,
48
+ )
49
+
50
+ def _is_operator_valid_for_property_type(self, property_type: PropertyType, operator: Operator) -> bool:
51
+ allowed_operator_types = self._PROPERTY_TYPE_OPERATORS.get(property_type, [])
52
+ valid_operator_values = self._get_operator_values_from_types(allowed_operator_types)
53
+ return operator.value in valid_operator_values
54
+
55
+ def _get_operator_values_from_types(self, operator_types: list[type[Operator]]) -> set[str]:
56
+ values: set[str] = set()
57
+ for operator_type in operator_types:
58
+ for operator in operator_type:
59
+ values.add(operator.value)
60
+ return values
61
+
62
+ def _get_valid_operators_for_property_type(self, property_type: PropertyType) -> list[Operator]:
63
+ allowed_operator_types = self._PROPERTY_TYPE_OPERATORS.get(property_type, [])
64
+ return self._collect_all_operators_from_types(allowed_operator_types)
65
+
66
+ def _collect_all_operators_from_types(self, operator_types: list[type[Operator]]) -> list[Operator]:
67
+ operators: list[Operator] = []
68
+ for operator_type in operator_types:
69
+ operators.extend(self._get_all_enum_values(operator_type))
70
+ return operators
71
+
72
+ def _get_all_enum_values(self, operator_type: type[Operator]) -> list[Operator]:
73
+ return list(operator_type)
@@ -0,0 +1,104 @@
1
+ from dataclasses import dataclass
2
+
3
+ from notionary.shared.properties.type import PropertyType
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class PropertyTypeDescriptor:
8
+ display_name: str
9
+ description: str
10
+
11
+
12
+ class DatabasePropertyTypeDescriptorRegistry:
13
+ def __init__(self):
14
+ self._DESCRIPTORS = {
15
+ PropertyType.TITLE: PropertyTypeDescriptor(
16
+ display_name="Title", description="Required field for the main heading of the entry"
17
+ ),
18
+ PropertyType.RICH_TEXT: PropertyTypeDescriptor(
19
+ display_name="Rich Text", description="Free-form text field for additional information"
20
+ ),
21
+ PropertyType.NUMBER: PropertyTypeDescriptor(display_name="Number", description="Numeric value field"),
22
+ PropertyType.CHECKBOX: PropertyTypeDescriptor(
23
+ display_name="Checkbox", description="Boolean value (true/false)"
24
+ ),
25
+ PropertyType.DATE: PropertyTypeDescriptor(display_name="Date", description="Date or date range field"),
26
+ PropertyType.URL: PropertyTypeDescriptor(display_name="URL", description="Web address field"),
27
+ PropertyType.EMAIL: PropertyTypeDescriptor(display_name="Email", description="Email address field"),
28
+ PropertyType.PHONE_NUMBER: PropertyTypeDescriptor(
29
+ display_name="Phone Number", description="Phone number field"
30
+ ),
31
+ PropertyType.FILES: PropertyTypeDescriptor(
32
+ display_name="Files & Media", description="Upload or link to files"
33
+ ),
34
+ PropertyType.PEOPLE: PropertyTypeDescriptor(display_name="People", description="Reference to Notion users"),
35
+ PropertyType.SELECT: PropertyTypeDescriptor(
36
+ display_name="Single Select", description="Choose one option from available choices"
37
+ ),
38
+ PropertyType.MULTI_SELECT: PropertyTypeDescriptor(
39
+ display_name="Multi Select", description="Choose multiple options from available choices"
40
+ ),
41
+ PropertyType.STATUS: PropertyTypeDescriptor(
42
+ display_name="Status", description="Track status with predefined options"
43
+ ),
44
+ PropertyType.RELATION: PropertyTypeDescriptor(
45
+ display_name="Relation", description="Link to entries in another database"
46
+ ),
47
+ PropertyType.CREATED_TIME: PropertyTypeDescriptor(
48
+ display_name="Created Time",
49
+ description="Automatically set when the page is created",
50
+ ),
51
+ PropertyType.CREATED_BY: PropertyTypeDescriptor(
52
+ display_name="Created By",
53
+ description="Automatically set to the user who created the page",
54
+ ),
55
+ PropertyType.LAST_EDITED_TIME: PropertyTypeDescriptor(
56
+ display_name="Last Edited Time",
57
+ description="Automatically updated when the page is modified",
58
+ ),
59
+ PropertyType.LAST_EDITED_BY: PropertyTypeDescriptor(
60
+ display_name="Last Edited By",
61
+ description="Automatically set to the user who last edited the page",
62
+ ),
63
+ PropertyType.LAST_VISITED_TIME: PropertyTypeDescriptor(
64
+ display_name="Last Visited Time",
65
+ description="Automatically updated when the page is visited",
66
+ ),
67
+ PropertyType.FORMULA: PropertyTypeDescriptor(
68
+ display_name="Formula",
69
+ description="Computed value based on other properties",
70
+ ),
71
+ PropertyType.ROLLUP: PropertyTypeDescriptor(
72
+ display_name="Rollup",
73
+ description="Aggregate values from related database entries",
74
+ ),
75
+ PropertyType.BUTTON: PropertyTypeDescriptor(
76
+ display_name="Button",
77
+ description="Interactive button that triggers an action",
78
+ ),
79
+ PropertyType.LOCATION: PropertyTypeDescriptor(
80
+ display_name="Location",
81
+ description="Geographic location field",
82
+ ),
83
+ PropertyType.PLACE: PropertyTypeDescriptor(
84
+ display_name="Place",
85
+ description="Place or venue information",
86
+ ),
87
+ PropertyType.VERIFICATION: PropertyTypeDescriptor(
88
+ display_name="Verification",
89
+ description="Verification status field",
90
+ ),
91
+ PropertyType.UNIQUE_ID: PropertyTypeDescriptor(
92
+ display_name="Unique ID",
93
+ description="Auto-generated unique identifier",
94
+ ),
95
+ }
96
+
97
+ def get_descriptor(self, property_type: PropertyType) -> PropertyTypeDescriptor:
98
+ return self._DESCRIPTORS.get(
99
+ property_type,
100
+ PropertyTypeDescriptor(display_name=self._format_unknown_type_name(property_type), description=""),
101
+ )
102
+
103
+ def _format_unknown_type_name(self, property_type: PropertyType) -> str:
104
+ return property_type.value.replace("_", " ").title()
@@ -0,0 +1,136 @@
1
+ from collections.abc import Awaitable, Callable
2
+
3
+ from notionary.blocks.rich_text.name_id_resolver import DataSourceNameIdResolver
4
+ from notionary.data_source.properties.schemas import (
5
+ DataSourceMultiSelectProperty,
6
+ DataSourceProperty,
7
+ DataSourceRelationProperty,
8
+ DataSourceSelectProperty,
9
+ DataSourceStatusProperty,
10
+ )
11
+ from notionary.data_source.schema.registry import DatabasePropertyTypeDescriptorRegistry, PropertyTypeDescriptor
12
+ from notionary.shared.properties.type import PropertyType
13
+
14
+
15
+ class PropertyFormatter:
16
+ INDENTATION = " - "
17
+
18
+ def __init__(
19
+ self,
20
+ relation_options_fetcher: Callable[[DataSourceRelationProperty], Awaitable[list[str]]],
21
+ type_descriptor_registry: DatabasePropertyTypeDescriptorRegistry | None = None,
22
+ data_source_resolver: DataSourceNameIdResolver | None = None,
23
+ ) -> None:
24
+ self._relation_options_fetcher = relation_options_fetcher
25
+ self._type_descriptor_registry = type_descriptor_registry or DatabasePropertyTypeDescriptorRegistry()
26
+ self._data_source_resolver = data_source_resolver or DataSourceNameIdResolver()
27
+
28
+ async def format_property(self, prop: DataSourceProperty) -> list[str]:
29
+ specific_details = await self._format_property_specific_details(prop)
30
+
31
+ if specific_details:
32
+ return [*specific_details, *self._format_custom_description(prop)]
33
+
34
+ descriptor = self._type_descriptor_registry.get_descriptor(prop.type)
35
+ return [*self._format_property_description(descriptor), *self._format_custom_description(prop)]
36
+
37
+ def _format_property_description(self, descriptor: PropertyTypeDescriptor) -> list[str]:
38
+ if not descriptor.description:
39
+ return []
40
+ return [f"{self.INDENTATION}{descriptor.description}"]
41
+
42
+ async def _format_property_specific_details(self, prop: DataSourceProperty) -> list[str]:
43
+ if isinstance(prop, DataSourceSelectProperty):
44
+ return self._format_available_options("Choose one option from", prop.option_names)
45
+
46
+ if isinstance(prop, DataSourceMultiSelectProperty):
47
+ return self._format_available_options("Choose multiple options from", prop.option_names)
48
+
49
+ if isinstance(prop, DataSourceStatusProperty):
50
+ return self._format_available_options("Available statuses", prop.option_names)
51
+
52
+ if isinstance(prop, DataSourceRelationProperty):
53
+ return await self._format_relation_details(prop)
54
+
55
+ return []
56
+
57
+ def _format_custom_description(self, prop: DataSourceProperty) -> list[str]:
58
+ if not prop.description:
59
+ return []
60
+ return [f"{self.INDENTATION}Description: {prop.description}"]
61
+
62
+ def _format_available_options(self, label: str, options: list[str]) -> list[str]:
63
+ options_text = ", ".join(options)
64
+ return [f"{self.INDENTATION}{label}: {options_text}"]
65
+
66
+ async def _format_relation_details(self, prop: DataSourceRelationProperty) -> list[str]:
67
+ if not prop.related_data_source_id:
68
+ return []
69
+
70
+ data_source_name = await self._data_source_resolver.resolve_id_to_name(prop.related_data_source_id)
71
+ data_source_display = data_source_name or prop.related_data_source_id
72
+ lines = [f"{self.INDENTATION}Links to datasource: {data_source_display}"]
73
+
74
+ available_entries = await self._fetch_relation_entries(prop)
75
+ if available_entries:
76
+ entries_text = ", ".join(available_entries)
77
+ lines.append(f"{self.INDENTATION}Available entries: {entries_text}")
78
+
79
+ return lines
80
+
81
+ async def _fetch_relation_entries(self, prop: DataSourceRelationProperty) -> list[str] | None:
82
+ try:
83
+ return await self._relation_options_fetcher(prop)
84
+ except Exception:
85
+ return None
86
+
87
+
88
+ class DataSourcePropertySchemaFormatter:
89
+ def __init__(
90
+ self,
91
+ relation_options_fetcher: Callable[[DataSourceRelationProperty], Awaitable[list[str]]] | None = None,
92
+ data_source_resolver: DataSourceNameIdResolver | None = None,
93
+ ) -> None:
94
+ self._property_formatter = PropertyFormatter(
95
+ relation_options_fetcher, data_source_resolver=data_source_resolver
96
+ )
97
+
98
+ async def format(self, title: str, description: str | None, properties: dict[str, DataSourceProperty]) -> str:
99
+ lines = self._format_header(title, description)
100
+ lines.append("Properties:")
101
+ lines.append("")
102
+ lines.extend(await self._format_properties(properties))
103
+
104
+ return "\n".join(lines)
105
+
106
+ def _format_header(self, title: str, description: str | None) -> list[str]:
107
+ lines = [f"Data Source: {title}", ""]
108
+
109
+ if description:
110
+ lines.append(f"Description: {description}")
111
+ lines.append("")
112
+
113
+ return lines
114
+
115
+ async def _format_properties(self, properties: dict[str, DataSourceProperty]) -> list[str]:
116
+ lines = []
117
+ sorted_properties = self._sort_with_title_first(properties)
118
+
119
+ for index, (name, prop) in enumerate(sorted_properties, start=1):
120
+ lines.extend(await self._format_single_property(index, name, prop))
121
+
122
+ return lines
123
+
124
+ def _sort_with_title_first(self, properties: dict[str, DataSourceProperty]) -> list[tuple[str, DataSourceProperty]]:
125
+ return sorted(properties.items(), key=lambda item: (self._is_not_title_property(item[1]), item[0]))
126
+
127
+ def _is_not_title_property(self, prop: DataSourceProperty) -> bool:
128
+ return prop.type != PropertyType.TITLE
129
+
130
+ async def _format_single_property(self, index: int, name: str, prop: DataSourceProperty) -> list[str]:
131
+ lines = [f"{index}. - Property Name: '{name}'", f" - Property Type: '{prop.type.value}'"]
132
+
133
+ lines.extend(await self._property_formatter.format_property(prop))
134
+ lines.append("")
135
+
136
+ return lines
@@ -0,0 +1,27 @@
1
+ from pydantic import BaseModel
2
+
3
+ from notionary.blocks.rich_text.models import RichText
4
+ from notionary.data_source.properties.schemas import DiscriminatedDataSourceProperty
5
+ from notionary.page.schemas import NotionPageDto
6
+ from notionary.shared.entity.schemas import EntityResponseDto, NotionEntityUpdateDto
7
+ from notionary.shared.models.parent import Parent
8
+
9
+
10
+ class UpdateDataSourceDto(NotionEntityUpdateDto):
11
+ title: list[RichText]
12
+ description: list[RichText]
13
+ archived: bool
14
+
15
+
16
+ class QueryDataSourceResponse(BaseModel):
17
+ results: list[NotionPageDto]
18
+ next_cursor: str | None = None
19
+ has_more: bool
20
+
21
+
22
+ class DataSourceDto(EntityResponseDto):
23
+ database_parent: Parent
24
+ title: list[RichText]
25
+ description: list[RichText]
26
+ archived: bool
27
+ properties: dict[str, DiscriminatedDataSourceProperty]