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,67 @@
1
+ from enum import StrEnum
2
+ from typing import Annotated, Literal
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class UserType(StrEnum):
8
+ PERSON = "person"
9
+ BOT = "bot"
10
+
11
+
12
+ class WorkspaceOwnerType(StrEnum):
13
+ USER = "user"
14
+ WORKSPACE = "workspace"
15
+
16
+
17
+ class PersonUserDto(BaseModel):
18
+ email: str | None = None
19
+
20
+
21
+ class BotOwnerDto(BaseModel):
22
+ type: WorkspaceOwnerType
23
+ workspace: bool | None = None
24
+
25
+
26
+ class WorkspaceLimits(BaseModel):
27
+ max_file_upload_size_in_bytes: int
28
+
29
+
30
+ class BotUserDto(BaseModel):
31
+ owner: BotOwnerDto | None = None
32
+ workspace_name: str | None = None
33
+ workspace_limits: WorkspaceLimits | None = None
34
+
35
+
36
+ class NotionUserBase(BaseModel):
37
+ object: Literal["user"] = "user"
38
+ id: str
39
+
40
+ type: UserType
41
+
42
+ name: str | None = None
43
+ avatar_url: str | None = None
44
+
45
+
46
+ class PersonUserResponseDto(NotionUserBase):
47
+ type: Literal[UserType.PERSON] = UserType.PERSON
48
+ person: PersonUserDto
49
+
50
+
51
+ class BotUserResponseDto(NotionUserBase):
52
+ type: Literal[UserType.BOT] = UserType.BOT
53
+ bot: BotUserDto
54
+
55
+
56
+ UserResponseDto = Annotated[PersonUserResponseDto | BotUserResponseDto, Field(discriminator="type")]
57
+
58
+
59
+ class NotionUsersListResponse(BaseModel):
60
+ results: list[UserResponseDto]
61
+ next_cursor: str | None = None
62
+ has_more: bool
63
+
64
+
65
+ class PartialUserDto(BaseModel):
66
+ object: Literal["user"] = "user"
67
+ id: str
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import AsyncIterator
4
+
5
+ from notionary.user import BotUser, PersonUser
6
+ from notionary.user.client import UserHttpClient
7
+ from notionary.user.schemas import UserType
8
+
9
+
10
+ class UserService:
11
+ def __init__(self, client: UserHttpClient | None = None) -> None:
12
+ self._client = client or UserHttpClient()
13
+
14
+ async def list_users(self) -> list[PersonUser]:
15
+ all_users = await self._client.get_all_workspace_users()
16
+ person_users = [user for user in all_users if user.type == UserType.PERSON]
17
+
18
+ return [PersonUser.from_dto(user) for user in person_users]
19
+
20
+ async def list_users_stream(self) -> AsyncIterator[PersonUser]:
21
+ all_users = await self._client.get_all_workspace_users()
22
+ for user in all_users:
23
+ if user.type == UserType.PERSON:
24
+ yield PersonUser.from_dto(user)
25
+
26
+ async def list_bot_users(self) -> list[BotUser]:
27
+ all_users = await self._client.get_all_workspace_users()
28
+ bot_users = [user for user in all_users if user.type == UserType.BOT]
29
+
30
+ return [BotUser.from_dto(user) for user in bot_users]
31
+
32
+ async def list_bot_users_stream(self) -> AsyncIterator[BotUser]:
33
+ all_users = await self._client.get_all_workspace_users()
34
+ for user in all_users:
35
+ if user.type == UserType.BOT:
36
+ yield BotUser.from_dto(user)
37
+
38
+ async def search_users(self, query: str) -> list[PersonUser]:
39
+ all_person_users = await self.list_users()
40
+ query_lower = query.lower()
41
+
42
+ return [
43
+ user
44
+ for user in all_person_users
45
+ if query_lower in (user.name or "").lower() or query_lower in (user.email or "").lower()
46
+ ]
47
+
48
+ async def search_users_stream(self, query: str) -> AsyncIterator[PersonUser]:
49
+ query_lower = query.lower()
50
+
51
+ async for user in self.list_users_stream():
52
+ if query_lower in (user.name or "").lower() or query_lower in (user.email or "").lower():
53
+ yield user
54
+
55
+ async def get_current_bot(self) -> BotUser:
56
+ bot_dto = await self._client.get_current_integration_bot()
57
+ return BotUser.from_dto(bot_dto)
58
+
59
+ async def get_user_by_id(self, user_id: str) -> PersonUser | BotUser | None:
60
+ user_dto = await self._client.get_user_by_id(user_id)
61
+
62
+ if user_dto.type == UserType.PERSON:
63
+ return PersonUser.from_dto(user_dto)
64
+ else:
65
+ return BotUser.from_dto(user_dto)
@@ -0,0 +1,39 @@
1
+ import asyncio
2
+ import functools
3
+ from collections.abc import Callable
4
+ from typing import Any
5
+
6
+
7
+ def async_retry(
8
+ max_retries: int = 3,
9
+ initial_delay: float = 1.0,
10
+ backoff_factor: float = 2.0,
11
+ retry_on_exceptions: tuple[type[Exception], ...] | None = None,
12
+ ):
13
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
14
+ @functools.wraps(func)
15
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
16
+ delay = initial_delay
17
+ last_exception = None
18
+
19
+ for attempt in range(max_retries + 1):
20
+ try:
21
+ return await func(*args, **kwargs)
22
+ except Exception as e:
23
+ last_exception = e
24
+
25
+ # If specific exceptions are defined, only retry those
26
+ if retry_on_exceptions is not None and not isinstance(e, retry_on_exceptions):
27
+ raise
28
+
29
+ if attempt == max_retries:
30
+ raise
31
+
32
+ await asyncio.sleep(delay)
33
+ delay *= backoff_factor
34
+
35
+ raise last_exception
36
+
37
+ return wrapper
38
+
39
+ return decorator
@@ -0,0 +1,51 @@
1
+ from datetime import datetime
2
+
3
+
4
+ def parse_date(date_str: str) -> str:
5
+ supported_formats = _get_supported_date_formats()
6
+
7
+ for date_format in supported_formats:
8
+ parsed_date = _try_parse_date_with_format(date_str, date_format)
9
+ if _date_was_successfully_parsed(parsed_date):
10
+ return _convert_to_iso_format(parsed_date)
11
+
12
+ _raise_invalid_date_format_error(date_str)
13
+
14
+
15
+ def _get_supported_date_formats() -> list[str]:
16
+ return [
17
+ "%Y-%m-%d", # ISO: 2024-12-31
18
+ "%d.%m.%Y", # German: 31.12.2024
19
+ "%m/%d/%Y", # US with slash: 12/31/2024
20
+ "%m-%d-%Y", # US with dash: 12-31-2024
21
+ "%d/%m/%Y", # Day first with slash: 31/12/2024
22
+ "%d-%m-%Y", # Day first with dash: 31-12-2024
23
+ "%d-%b-%Y", # Short month: 31-Dec-2024
24
+ "%d %b %Y", # Short month with space: 31 Dec 2024
25
+ "%d-%B-%Y", # Full month: 31-December-2024
26
+ "%d %B %Y", # Full month with space: 31 December 2024
27
+ ]
28
+
29
+
30
+ def _try_parse_date_with_format(date_str: str, date_format: str) -> datetime | None:
31
+ try:
32
+ return datetime.strptime(date_str, date_format)
33
+ except ValueError:
34
+ return None
35
+
36
+
37
+ def _date_was_successfully_parsed(parsed_date: datetime | None) -> bool:
38
+ return parsed_date is not None
39
+
40
+
41
+ def _convert_to_iso_format(parsed_date: datetime) -> str:
42
+ return parsed_date.strftime("%Y-%m-%d")
43
+
44
+
45
+ def _raise_invalid_date_format_error(date_str: str) -> None:
46
+ error_message = (
47
+ f"Invalid date format: '{date_str}'. "
48
+ f"Supported formats: YYYY-MM-DD, DD.MM.YYYY, MM/DD/YYYY, DD/MM/YYYY, "
49
+ f"DD-Mon-YYYY, DD Month YYYY"
50
+ )
51
+ raise ValueError(error_message)
@@ -0,0 +1,56 @@
1
+ import difflib
2
+ from collections.abc import Callable
3
+ from dataclasses import dataclass
4
+ from typing import Any, Generic, TypeVar
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class _MatchResult(Generic[T]):
11
+ item: Any
12
+ similarity: float
13
+
14
+
15
+ def find_best_match(
16
+ query: str,
17
+ items: list[T],
18
+ text_extractor: Callable[[T], str],
19
+ min_similarity: float | None = 0.0,
20
+ ) -> T | None:
21
+ min_similarity = 0.0 if min_similarity is None else min_similarity
22
+
23
+ matches = _find_best_matches(query, items, text_extractor, min_similarity, limit=1)
24
+ return matches[0].item if matches else None
25
+
26
+
27
+ def _find_best_matches(
28
+ query: str,
29
+ items: list[T],
30
+ text_extractor: Callable[[T], str],
31
+ min_similarity: float = 0.0,
32
+ limit: int | None = None,
33
+ ) -> list[_MatchResult[T]]:
34
+ results = []
35
+
36
+ for item in items:
37
+ text = text_extractor(item)
38
+ similarity = _calculate_similarity(query, text)
39
+
40
+ if similarity >= min_similarity:
41
+ results.append(_MatchResult(item=item, similarity=similarity))
42
+
43
+ results = _sort_by_highest_similarity_first(results)
44
+
45
+ if limit:
46
+ return results[:limit]
47
+
48
+ return results
49
+
50
+
51
+ def _sort_by_highest_similarity_first(results: list[_MatchResult]) -> list[_MatchResult]:
52
+ return sorted(results, key=lambda x: x.similarity, reverse=True)
53
+
54
+
55
+ def _calculate_similarity(query: str, target: str) -> float:
56
+ return difflib.SequenceMatcher(None, query.lower().strip(), target.lower().strip()).ratio()
@@ -1,16 +1,9 @@
1
1
  import logging
2
2
  import os
3
- from typing import ClassVar, Optional
3
+ from typing import ClassVar
4
4
 
5
- from dotenv import load_dotenv
6
5
 
7
- load_dotenv()
8
-
9
-
10
- def setup_logging():
11
- """
12
- Sets up logging configuration for the application.
13
- """
6
+ def _setup_logging() -> None:
14
7
  log_level = os.getenv("LOG_LEVEL", "WARNING").upper()
15
8
  logging.basicConfig(
16
9
  level=getattr(logging, log_level),
@@ -20,18 +13,13 @@ def setup_logging():
20
13
  logging.getLogger("httpx").setLevel(logging.WARNING)
21
14
 
22
15
 
23
- setup_logging()
16
+ _setup_logging()
24
17
 
25
18
 
26
19
  class LoggingMixin:
27
- # Class attribute with proper typing
28
20
  logger: ClassVar[logging.Logger] = None
29
21
 
30
22
  def __init_subclass__(cls, **kwargs):
31
- """
32
- This method is called when a class inherits from LoggingMixin.
33
- It automatically sets up the logger as a class attribute.
34
- """
35
23
  super().__init_subclass__(**kwargs)
36
24
  cls.logger = logging.getLogger(cls.__name__)
37
25
 
@@ -43,7 +31,7 @@ class LoggingMixin:
43
31
  return self._logger
44
32
 
45
33
  @staticmethod
46
- def _get_class_name_from_frame(frame) -> Optional[str]:
34
+ def _get_class_name_from_frame(frame) -> str | None:
47
35
  local_vars = frame.f_locals
48
36
  if "self" in local_vars:
49
37
  return local_vars["self"].__class__.__name__
@@ -0,0 +1,50 @@
1
+ from collections.abc import AsyncGenerator, Callable, Coroutine
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class PaginatedResponse(BaseModel):
8
+ results: list[Any]
9
+ has_more: bool
10
+ next_cursor: str | None
11
+
12
+
13
+ async def _fetch_pages(
14
+ api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
15
+ **kwargs,
16
+ ) -> AsyncGenerator[PaginatedResponse]:
17
+ next_cursor = None
18
+ has_more = True
19
+
20
+ while has_more:
21
+ current_kwargs = kwargs.copy()
22
+ if next_cursor:
23
+ current_kwargs["start_cursor"] = next_cursor
24
+
25
+ response = await api_call(**current_kwargs)
26
+ yield response
27
+
28
+ has_more = response.has_more
29
+ next_cursor = response.next_cursor
30
+
31
+
32
+ async def paginate_notion_api(
33
+ api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
34
+ **kwargs,
35
+ ) -> list[Any]:
36
+ all_results = []
37
+ async for page in _fetch_pages(api_call, **kwargs):
38
+ if page.results:
39
+ all_results.extend(page.results)
40
+ return all_results
41
+
42
+
43
+ async def paginate_notion_api_generator(
44
+ api_call: Callable[..., Coroutine[Any, Any, PaginatedResponse]],
45
+ **kwargs,
46
+ ) -> AsyncGenerator[Any]:
47
+ async for page in _fetch_pages(api_call, **kwargs):
48
+ if page.results:
49
+ for item in page.results:
50
+ yield item
@@ -0,0 +1,13 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
4
+
5
+ def singleton(cls) -> Callable[..., Any]:
6
+ instances = {}
7
+
8
+ def get_instance(*args, **kwargs) -> Any:
9
+ if cls not in instances:
10
+ instances[cls] = cls(*args, **kwargs)
11
+ return instances[cls]
12
+
13
+ return get_instance
@@ -0,0 +1,20 @@
1
+ import re
2
+
3
+
4
+ def extract_uuid(source: str) -> str | None:
5
+ UUID_RAW_PATTERN = r"([a-f0-9]{32})"
6
+
7
+ if _is_valid_uuid(source):
8
+ return source
9
+
10
+ match = re.search(UUID_RAW_PATTERN, source.lower())
11
+ if not match:
12
+ return None
13
+
14
+ uuid_raw = match.group(1)
15
+ return f"{uuid_raw[0:8]}-{uuid_raw[8:12]}-{uuid_raw[12:16]}-{uuid_raw[16:20]}-{uuid_raw[20:32]}"
16
+
17
+
18
+ def _is_valid_uuid(uuid: str) -> bool:
19
+ UUID_PATTERN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$"
20
+ return bool(re.match(UUID_PATTERN, uuid.lower()))
@@ -0,0 +1,3 @@
1
+ from .service import NotionWorkspace
2
+
3
+ __all__ = ["NotionWorkspace"]
@@ -0,0 +1,62 @@
1
+ from collections.abc import AsyncGenerator
2
+ from typing import Any
3
+
4
+ from notionary.data_source.schemas import DataSourceDto
5
+ from notionary.http.client import NotionHttpClient
6
+ from notionary.page.schemas import NotionPageDto
7
+ from notionary.utils.pagination import paginate_notion_api_generator
8
+ from notionary.workspace.query.models import WorkspaceQueryConfig
9
+ from notionary.workspace.schemas import DataSourceSearchResponse, PageSearchResponse
10
+
11
+
12
+ class WorkspaceClient:
13
+ DEFAULT_PAGE_SIZE = 100
14
+
15
+ def __init__(self, http_client: NotionHttpClient | None = None) -> None:
16
+ self._http_client = http_client or NotionHttpClient()
17
+
18
+ async def query_pages_stream(
19
+ self,
20
+ search_config: WorkspaceQueryConfig,
21
+ ) -> AsyncGenerator[NotionPageDto]:
22
+ async for page in paginate_notion_api_generator(
23
+ self._query_pages,
24
+ search_config=search_config,
25
+ ):
26
+ yield page
27
+
28
+ async def query_data_sources_stream(
29
+ self,
30
+ search_config: WorkspaceQueryConfig,
31
+ ) -> AsyncGenerator[DataSourceDto]:
32
+ async for data_source in paginate_notion_api_generator(
33
+ self._query_data_sources,
34
+ search_config=search_config,
35
+ ):
36
+ yield data_source
37
+
38
+ async def _query_pages(
39
+ self,
40
+ search_config: WorkspaceQueryConfig,
41
+ start_cursor: str | None = None,
42
+ ) -> PageSearchResponse:
43
+ if start_cursor:
44
+ search_config.start_cursor = start_cursor
45
+
46
+ response = await self._execute_search(search_config)
47
+ return PageSearchResponse.model_validate(response)
48
+
49
+ async def _query_data_sources(
50
+ self,
51
+ search_config: WorkspaceQueryConfig,
52
+ start_cursor: str | None = None,
53
+ ) -> DataSourceSearchResponse:
54
+ if start_cursor:
55
+ search_config.start_cursor = start_cursor
56
+
57
+ response = await self._execute_search(search_config)
58
+ return DataSourceSearchResponse.model_validate(response)
59
+
60
+ async def _execute_search(self, config: WorkspaceQueryConfig) -> dict[str, Any]:
61
+ serialized_config = config.model_dump(exclude_none=True, by_alias=True)
62
+ return await self._http_client.post("search", serialized_config)
@@ -0,0 +1,60 @@
1
+ from typing import Self
2
+
3
+ from notionary.workspace.query.models import (
4
+ SortDirection,
5
+ SortTimestamp,
6
+ WorkspaceQueryConfig,
7
+ WorkspaceQueryObjectType,
8
+ )
9
+
10
+
11
+ class WorkspaceQueryConfigBuilder:
12
+ def __init__(self, config: WorkspaceQueryConfig = None) -> None:
13
+ self.config = config or WorkspaceQueryConfig()
14
+
15
+ def with_query(self, query: str) -> Self:
16
+ self.config.query = query
17
+ return self
18
+
19
+ def with_pages_only(self) -> Self:
20
+ self.config.object_type = WorkspaceQueryObjectType.PAGE
21
+ return self
22
+
23
+ def with_data_sources_only(self) -> Self:
24
+ self.config.object_type = WorkspaceQueryObjectType.DATA_SOURCE
25
+ return self
26
+
27
+ def with_sort_direction(self, direction: SortDirection) -> Self:
28
+ self.config.sort_direction = direction
29
+ return self
30
+
31
+ def with_sort_ascending(self) -> Self:
32
+ return self.with_sort_direction(SortDirection.ASCENDING)
33
+
34
+ def with_sort_descending(self) -> Self:
35
+ return self.with_sort_direction(SortDirection.DESCENDING)
36
+
37
+ def with_sort_timestamp(self, timestamp: SortTimestamp) -> Self:
38
+ self.config.sort_timestamp = timestamp
39
+ return self
40
+
41
+ def with_sort_by_created_time(self) -> Self:
42
+ return self.with_sort_timestamp(SortTimestamp.CREATED_TIME)
43
+
44
+ def with_sort_by_last_edited(self) -> Self:
45
+ return self.with_sort_timestamp(SortTimestamp.LAST_EDITED_TIME)
46
+
47
+ def with_page_size(self, size: int) -> Self:
48
+ self.config.page_size = min(size, 100)
49
+ return self
50
+
51
+ def with_start_cursor(self, cursor: str | None) -> Self:
52
+ self.config.start_cursor = cursor
53
+ return self
54
+
55
+ def without_cursor(self) -> Self:
56
+ self.config.start_cursor = None
57
+ return self
58
+
59
+ def build(self) -> WorkspaceQueryConfig:
60
+ return self.config
@@ -0,0 +1,60 @@
1
+ from enum import StrEnum
2
+ from typing import Any
3
+
4
+ from pydantic import BaseModel, Field, field_validator, model_serializer
5
+
6
+
7
+ class SortDirection(StrEnum):
8
+ ASCENDING = "ascending"
9
+ DESCENDING = "descending"
10
+
11
+
12
+ class SortTimestamp(StrEnum):
13
+ LAST_EDITED_TIME = "last_edited_time"
14
+ CREATED_TIME = "created_time"
15
+
16
+
17
+ class WorkspaceQueryObjectType(StrEnum):
18
+ PAGE = "page"
19
+ DATA_SOURCE = "data_source"
20
+
21
+
22
+ class WorkspaceQueryConfig(BaseModel):
23
+ query: str | None = None
24
+ object_type: WorkspaceQueryObjectType | None = None
25
+ sort_direction: SortDirection = SortDirection.DESCENDING
26
+ sort_timestamp: SortTimestamp = SortTimestamp.LAST_EDITED_TIME
27
+ page_size: int = Field(default=100, ge=1, le=100)
28
+ start_cursor: str | None = None
29
+
30
+ @field_validator("query")
31
+ @classmethod
32
+ def replace_empty_query_with_none(cls, value: str | None) -> str | None:
33
+ if value is not None and not value.strip():
34
+ return None
35
+ return value
36
+
37
+ @model_serializer
38
+ def serialize_model(self) -> dict[str, Any]:
39
+ search_dict: dict[str, Any] = {}
40
+
41
+ if self.query:
42
+ search_dict["query"] = self.query
43
+
44
+ if self.object_type:
45
+ search_dict["filter"] = {
46
+ "property": "object",
47
+ "value": self.object_type.value,
48
+ }
49
+
50
+ search_dict["sort"] = {
51
+ "direction": self.sort_direction.value,
52
+ "timestamp": self.sort_timestamp.value,
53
+ }
54
+
55
+ search_dict["page_size"] = self.page_size
56
+
57
+ if self.start_cursor:
58
+ search_dict["start_cursor"] = self.start_cursor
59
+
60
+ return search_dict