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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. notionary/__init__.py +5 -20
  2. notionary/blocks/client.py +87 -215
  3. notionary/blocks/enums.py +167 -0
  4. notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
  5. notionary/blocks/rich_text/models.py +164 -0
  6. notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
  7. notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
  8. notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
  9. notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
  10. notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
  11. notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
  12. notionary/blocks/rich_text/rich_text_patterns.py +39 -0
  13. notionary/blocks/schemas.py +746 -0
  14. notionary/comments/client.py +52 -187
  15. notionary/comments/factory.py +40 -0
  16. notionary/comments/models.py +5 -127
  17. notionary/comments/schemas.py +240 -0
  18. notionary/comments/service.py +34 -0
  19. notionary/data_source/http/client.py +11 -0
  20. notionary/data_source/http/data_source_instance_client.py +94 -0
  21. notionary/data_source/properties/models.py +406 -0
  22. notionary/data_source/query/builder.py +429 -0
  23. notionary/data_source/query/resolver.py +114 -0
  24. notionary/data_source/query/schema.py +304 -0
  25. notionary/data_source/query/validator.py +73 -0
  26. notionary/data_source/schemas.py +27 -0
  27. notionary/data_source/service.py +353 -0
  28. notionary/database/client.py +30 -135
  29. notionary/database/database_metadata_update_client.py +19 -0
  30. notionary/database/schemas.py +29 -0
  31. notionary/database/service.py +169 -0
  32. notionary/exceptions/__init__.py +33 -0
  33. notionary/exceptions/api.py +41 -0
  34. notionary/exceptions/base.py +2 -0
  35. notionary/exceptions/block_parsing.py +16 -0
  36. notionary/exceptions/data_source/__init__.py +6 -0
  37. notionary/exceptions/data_source/builder.py +182 -0
  38. notionary/exceptions/data_source/properties.py +34 -0
  39. notionary/exceptions/properties.py +58 -0
  40. notionary/exceptions/search.py +33 -0
  41. notionary/file_upload/client.py +18 -30
  42. notionary/file_upload/models.py +7 -8
  43. notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
  44. notionary/http/client.py +205 -0
  45. notionary/http/models.py +49 -0
  46. notionary/page/blocks/client.py +1 -0
  47. notionary/page/content/factory.py +68 -0
  48. notionary/page/content/markdown/__init__.py +5 -0
  49. notionary/page/content/markdown/builder.py +304 -0
  50. notionary/page/content/markdown/nodes/__init__.py +54 -0
  51. notionary/page/content/markdown/nodes/audio.py +23 -0
  52. notionary/page/content/markdown/nodes/base.py +12 -0
  53. notionary/page/content/markdown/nodes/bookmark.py +25 -0
  54. notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
  55. notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
  56. notionary/page/content/markdown/nodes/callout.py +32 -0
  57. notionary/page/content/markdown/nodes/code.py +30 -0
  58. notionary/page/content/markdown/nodes/columns.py +51 -0
  59. notionary/page/content/markdown/nodes/divider.py +14 -0
  60. notionary/page/content/markdown/nodes/embed.py +23 -0
  61. notionary/page/content/markdown/nodes/equation.py +19 -0
  62. notionary/page/content/markdown/nodes/file.py +23 -0
  63. notionary/page/content/markdown/nodes/heading.py +16 -0
  64. notionary/page/content/markdown/nodes/image.py +23 -0
  65. notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
  66. notionary/page/content/markdown/nodes/numbered_list.py +15 -0
  67. notionary/page/content/markdown/nodes/paragraph.py +14 -0
  68. notionary/page/content/markdown/nodes/pdf.py +23 -0
  69. notionary/page/content/markdown/nodes/quote.py +15 -0
  70. notionary/page/content/markdown/nodes/space.py +14 -0
  71. notionary/page/content/markdown/nodes/table.py +45 -0
  72. notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
  73. notionary/page/content/markdown/nodes/todo.py +22 -0
  74. notionary/page/content/markdown/nodes/toggle.py +28 -0
  75. notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
  76. notionary/page/content/markdown/nodes/video.py +23 -0
  77. notionary/page/content/parser/context.py +49 -0
  78. notionary/page/content/parser/factory.py +219 -0
  79. notionary/page/content/parser/parsers/__init__.py +60 -0
  80. notionary/page/content/parser/parsers/audio.py +40 -0
  81. notionary/page/content/parser/parsers/base.py +30 -0
  82. notionary/page/content/parser/parsers/bookmark.py +33 -0
  83. notionary/page/content/parser/parsers/breadcrumb.py +33 -0
  84. notionary/page/content/parser/parsers/bulleted_list.py +41 -0
  85. notionary/page/content/parser/parsers/callout.py +129 -0
  86. notionary/page/content/parser/parsers/caption.py +55 -0
  87. notionary/page/content/parser/parsers/code.py +81 -0
  88. notionary/page/content/parser/parsers/column.py +117 -0
  89. notionary/page/content/parser/parsers/column_list.py +81 -0
  90. notionary/page/content/parser/parsers/divider.py +33 -0
  91. notionary/page/content/parser/parsers/embed.py +33 -0
  92. notionary/page/content/parser/parsers/equation.py +65 -0
  93. notionary/page/content/parser/parsers/file.py +42 -0
  94. notionary/page/content/parser/parsers/heading.py +58 -0
  95. notionary/page/content/parser/parsers/image.py +42 -0
  96. notionary/page/content/parser/parsers/numbered_list.py +45 -0
  97. notionary/page/content/parser/parsers/paragraph.py +36 -0
  98. notionary/page/content/parser/parsers/pdf.py +42 -0
  99. notionary/page/content/parser/parsers/quote.py +65 -0
  100. notionary/page/content/parser/parsers/space.py +35 -0
  101. notionary/page/content/parser/parsers/table.py +144 -0
  102. notionary/page/content/parser/parsers/table_of_contents.py +32 -0
  103. notionary/page/content/parser/parsers/todo.py +58 -0
  104. notionary/page/content/parser/parsers/toggle.py +127 -0
  105. notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
  106. notionary/page/content/parser/parsers/video.py +42 -0
  107. notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
  108. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
  109. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
  110. notionary/page/content/parser/post_processing/port.py +9 -0
  111. notionary/page/content/parser/post_processing/service.py +16 -0
  112. notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
  113. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
  114. notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
  115. notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
  116. notionary/page/content/parser/pre_processsing/service.py +15 -0
  117. notionary/page/content/parser/service.py +69 -0
  118. notionary/page/content/renderer/context.py +48 -0
  119. notionary/page/content/renderer/factory.py +240 -0
  120. notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
  121. notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
  122. notionary/page/content/renderer/post_processing/port.py +7 -0
  123. notionary/page/content/renderer/post_processing/service.py +15 -0
  124. notionary/page/content/renderer/renderers/__init__.py +57 -0
  125. notionary/page/content/renderer/renderers/audio.py +31 -0
  126. notionary/page/content/renderer/renderers/base.py +31 -0
  127. notionary/page/content/renderer/renderers/bookmark.py +25 -0
  128. notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
  129. notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
  130. notionary/page/content/renderer/renderers/callout.py +65 -0
  131. notionary/page/content/renderer/renderers/captioned_block.py +58 -0
  132. notionary/page/content/renderer/renderers/code.py +34 -0
  133. notionary/page/content/renderer/renderers/column.py +44 -0
  134. notionary/page/content/renderer/renderers/column_list.py +31 -0
  135. notionary/page/content/renderer/renderers/divider.py +22 -0
  136. notionary/page/content/renderer/renderers/embed.py +25 -0
  137. notionary/page/content/renderer/renderers/equation.py +37 -0
  138. notionary/page/content/renderer/renderers/fallback.py +24 -0
  139. notionary/page/content/renderer/renderers/file.py +40 -0
  140. notionary/page/content/renderer/renderers/heading.py +69 -0
  141. notionary/page/content/renderer/renderers/image.py +31 -0
  142. notionary/page/content/renderer/renderers/numbered_list.py +41 -0
  143. notionary/page/content/renderer/renderers/paragraph.py +40 -0
  144. notionary/page/content/renderer/renderers/pdf.py +31 -0
  145. notionary/page/content/renderer/renderers/quote.py +49 -0
  146. notionary/page/content/renderer/renderers/table.py +115 -0
  147. notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
  148. notionary/page/content/renderer/renderers/table_row.py +17 -0
  149. notionary/page/content/renderer/renderers/todo.py +56 -0
  150. notionary/page/content/renderer/renderers/toggle.py +53 -0
  151. notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
  152. notionary/page/content/renderer/renderers/video.py +31 -0
  153. notionary/page/content/renderer/service.py +50 -0
  154. notionary/page/content/service.py +65 -0
  155. notionary/page/content/syntax/models.py +68 -0
  156. notionary/page/content/syntax/service.py +453 -0
  157. notionary/page/page_context.py +7 -16
  158. notionary/page/page_http_client.py +15 -0
  159. notionary/page/page_metadata_update_client.py +19 -0
  160. notionary/page/properties/client.py +144 -0
  161. notionary/page/properties/factory.py +26 -0
  162. notionary/page/properties/models.py +307 -0
  163. notionary/page/properties/service.py +257 -0
  164. notionary/page/schemas.py +13 -0
  165. notionary/page/service.py +222 -0
  166. notionary/shared/entity/client.py +29 -0
  167. notionary/shared/entity/dto_parsers.py +53 -0
  168. notionary/shared/entity/entity_metadata_update_client.py +41 -0
  169. notionary/shared/entity/schemas.py +45 -0
  170. notionary/shared/entity/service.py +171 -0
  171. notionary/shared/models/cover.py +20 -0
  172. notionary/shared/models/file.py +21 -0
  173. notionary/shared/models/icon.py +28 -0
  174. notionary/shared/models/parent.py +41 -0
  175. notionary/shared/properties/type.py +30 -0
  176. notionary/user/__init__.py +4 -8
  177. notionary/user/base.py +89 -0
  178. notionary/user/bot.py +70 -0
  179. notionary/user/client.py +22 -111
  180. notionary/user/person.py +41 -0
  181. notionary/user/schemas.py +67 -0
  182. notionary/user/service.py +65 -0
  183. notionary/utils/async_retry.py +39 -0
  184. notionary/utils/date.py +51 -0
  185. notionary/utils/fuzzy.py +56 -0
  186. notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
  187. notionary/utils/pagination.py +50 -0
  188. notionary/utils/singleton.py +13 -0
  189. notionary/utils/uuid_utils.py +20 -0
  190. notionary/workspace/__init__.py +3 -0
  191. notionary/workspace/client.py +62 -0
  192. notionary/workspace/query/builder.py +60 -0
  193. notionary/workspace/query/models.py +60 -0
  194. notionary/workspace/query/service.py +93 -0
  195. notionary/workspace/schemas.py +21 -0
  196. notionary/workspace/service.py +116 -0
  197. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
  198. notionary-0.2.28.dist-info/RECORD +200 -0
  199. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
  200. {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
  201. notionary/base_notion_client.py +0 -219
  202. notionary/blocks/__init__.py +0 -5
  203. notionary/blocks/_bootstrap.py +0 -271
  204. notionary/blocks/audio/__init__.py +0 -11
  205. notionary/blocks/audio/audio_element.py +0 -158
  206. notionary/blocks/audio/audio_markdown_node.py +0 -24
  207. notionary/blocks/audio/audio_models.py +0 -10
  208. notionary/blocks/base_block_element.py +0 -42
  209. notionary/blocks/bookmark/__init__.py +0 -12
  210. notionary/blocks/bookmark/bookmark_element.py +0 -83
  211. notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
  212. notionary/blocks/bookmark/bookmark_models.py +0 -15
  213. notionary/blocks/breadcrumbs/__init__.py +0 -15
  214. notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
  215. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
  216. notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
  217. notionary/blocks/bulleted_list/__init__.py +0 -15
  218. notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
  219. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
  220. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
  221. notionary/blocks/callout/__init__.py +0 -12
  222. notionary/blocks/callout/callout_element.py +0 -99
  223. notionary/blocks/callout/callout_markdown_node.py +0 -19
  224. notionary/blocks/callout/callout_models.py +0 -33
  225. notionary/blocks/child_database/__init__.py +0 -14
  226. notionary/blocks/child_database/child_database_element.py +0 -59
  227. notionary/blocks/child_database/child_database_models.py +0 -12
  228. notionary/blocks/child_page/__init__.py +0 -9
  229. notionary/blocks/child_page/child_page_element.py +0 -94
  230. notionary/blocks/child_page/child_page_models.py +0 -12
  231. notionary/blocks/code/__init__.py +0 -11
  232. notionary/blocks/code/code_element.py +0 -149
  233. notionary/blocks/code/code_markdown_node.py +0 -80
  234. notionary/blocks/code/code_models.py +0 -94
  235. notionary/blocks/column/__init__.py +0 -25
  236. notionary/blocks/column/column_element.py +0 -65
  237. notionary/blocks/column/column_list_element.py +0 -52
  238. notionary/blocks/column/column_list_markdown_node.py +0 -34
  239. notionary/blocks/column/column_markdown_node.py +0 -42
  240. notionary/blocks/column/column_models.py +0 -26
  241. notionary/blocks/divider/__init__.py +0 -12
  242. notionary/blocks/divider/divider_element.py +0 -41
  243. notionary/blocks/divider/divider_markdown_node.py +0 -11
  244. notionary/blocks/divider/divider_models.py +0 -12
  245. notionary/blocks/embed/__init__.py +0 -12
  246. notionary/blocks/embed/embed_element.py +0 -98
  247. notionary/blocks/embed/embed_markdown_node.py +0 -19
  248. notionary/blocks/embed/embed_models.py +0 -14
  249. notionary/blocks/equation/__init__.py +0 -13
  250. notionary/blocks/equation/equation_element.py +0 -133
  251. notionary/blocks/equation/equation_element_markdown_node.py +0 -23
  252. notionary/blocks/equation/equation_models.py +0 -11
  253. notionary/blocks/file/__init__.py +0 -23
  254. notionary/blocks/file/file_element.py +0 -133
  255. notionary/blocks/file/file_element_markdown_node.py +0 -24
  256. notionary/blocks/file/file_element_models.py +0 -39
  257. notionary/blocks/heading/__init__.py +0 -19
  258. notionary/blocks/heading/heading_element.py +0 -112
  259. notionary/blocks/heading/heading_markdown_node.py +0 -16
  260. notionary/blocks/heading/heading_models.py +0 -29
  261. notionary/blocks/image_block/__init__.py +0 -11
  262. notionary/blocks/image_block/image_element.py +0 -130
  263. notionary/blocks/image_block/image_markdown_node.py +0 -25
  264. notionary/blocks/image_block/image_models.py +0 -10
  265. notionary/blocks/markdown/markdown_builder.py +0 -525
  266. notionary/blocks/markdown/markdown_document_model.py +0 -0
  267. notionary/blocks/markdown/markdown_node.py +0 -25
  268. notionary/blocks/mixins/captions/__init__.py +0 -4
  269. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
  270. notionary/blocks/mixins/captions/caption_mixin.py +0 -92
  271. notionary/blocks/mixins/file_upload/__init__.py +0 -3
  272. notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
  273. notionary/blocks/models.py +0 -174
  274. notionary/blocks/numbered_list/__init__.py +0 -16
  275. notionary/blocks/numbered_list/numbered_list_element.py +0 -65
  276. notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
  277. notionary/blocks/numbered_list/numbered_list_models.py +0 -17
  278. notionary/blocks/paragraph/__init__.py +0 -15
  279. notionary/blocks/paragraph/paragraph_element.py +0 -58
  280. notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
  281. notionary/blocks/paragraph/paragraph_models.py +0 -16
  282. notionary/blocks/pdf/__init__.py +0 -11
  283. notionary/blocks/pdf/pdf_element.py +0 -146
  284. notionary/blocks/pdf/pdf_markdown_node.py +0 -24
  285. notionary/blocks/pdf/pdf_models.py +0 -11
  286. notionary/blocks/quote/__init__.py +0 -14
  287. notionary/blocks/quote/quote_element.py +0 -75
  288. notionary/blocks/quote/quote_markdown_node.py +0 -16
  289. notionary/blocks/quote/quote_models.py +0 -18
  290. notionary/blocks/registry/__init__.py +0 -3
  291. notionary/blocks/registry/block_registry.py +0 -150
  292. notionary/blocks/rich_text/__init__.py +0 -33
  293. notionary/blocks/rich_text/rich_text_models.py +0 -221
  294. notionary/blocks/rich_text/text_inline_formatter.py +0 -456
  295. notionary/blocks/syntax_prompt_builder.py +0 -137
  296. notionary/blocks/table/__init__.py +0 -19
  297. notionary/blocks/table/table_element.py +0 -225
  298. notionary/blocks/table/table_markdown_node.py +0 -42
  299. notionary/blocks/table/table_models.py +0 -28
  300. notionary/blocks/table_of_contents/__init__.py +0 -17
  301. notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
  302. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
  303. notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
  304. notionary/blocks/todo/__init__.py +0 -12
  305. notionary/blocks/todo/todo_element.py +0 -81
  306. notionary/blocks/todo/todo_markdown_node.py +0 -21
  307. notionary/blocks/todo/todo_models.py +0 -18
  308. notionary/blocks/toggle/__init__.py +0 -12
  309. notionary/blocks/toggle/toggle_element.py +0 -112
  310. notionary/blocks/toggle/toggle_markdown_node.py +0 -31
  311. notionary/blocks/toggle/toggle_models.py +0 -17
  312. notionary/blocks/toggleable_heading/__init__.py +0 -11
  313. notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
  314. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
  315. notionary/blocks/types.py +0 -130
  316. notionary/blocks/video/__init__.py +0 -11
  317. notionary/blocks/video/video_element.py +0 -187
  318. notionary/blocks/video/video_element_models.py +0 -10
  319. notionary/blocks/video/video_markdown_node.py +0 -26
  320. notionary/comments/__init__.py +0 -26
  321. notionary/database/__init__.py +0 -4
  322. notionary/database/database.py +0 -480
  323. notionary/database/database_filter_builder.py +0 -173
  324. notionary/database/database_provider.py +0 -227
  325. notionary/database/exceptions.py +0 -13
  326. notionary/database/factory.py +0 -0
  327. notionary/database/models.py +0 -337
  328. notionary/database/notion_database.py +0 -487
  329. notionary/file_upload/__init__.py +0 -7
  330. notionary/page/client.py +0 -124
  331. notionary/page/markdown_whitespace_processor.py +0 -129
  332. notionary/page/models.py +0 -322
  333. notionary/page/notion_page.py +0 -674
  334. notionary/page/page_content_deleting_service.py +0 -117
  335. notionary/page/page_content_writer.py +0 -80
  336. notionary/page/property_formatter.py +0 -99
  337. notionary/page/reader/handler/__init__.py +0 -19
  338. notionary/page/reader/handler/base_block_renderer.py +0 -44
  339. notionary/page/reader/handler/block_processing_context.py +0 -35
  340. notionary/page/reader/handler/block_rendering_context.py +0 -48
  341. notionary/page/reader/handler/column_list_renderer.py +0 -51
  342. notionary/page/reader/handler/column_renderer.py +0 -60
  343. notionary/page/reader/handler/equation_renderer.py +0 -0
  344. notionary/page/reader/handler/line_renderer.py +0 -73
  345. notionary/page/reader/handler/numbered_list_renderer.py +0 -85
  346. notionary/page/reader/handler/toggle_renderer.py +0 -69
  347. notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
  348. notionary/page/reader/page_content_retriever.py +0 -81
  349. notionary/page/search_filter_builder.py +0 -132
  350. notionary/page/utils.py +0 -60
  351. notionary/page/writer/handler/__init__.py +0 -24
  352. notionary/page/writer/handler/code_handler.py +0 -72
  353. notionary/page/writer/handler/column_handler.py +0 -141
  354. notionary/page/writer/handler/column_list_handler.py +0 -139
  355. notionary/page/writer/handler/equation_handler.py +0 -74
  356. notionary/page/writer/handler/line_handler.py +0 -35
  357. notionary/page/writer/handler/line_processing_context.py +0 -54
  358. notionary/page/writer/handler/regular_line_handler.py +0 -86
  359. notionary/page/writer/handler/table_handler.py +0 -66
  360. notionary/page/writer/handler/toggle_handler.py +0 -159
  361. notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
  362. notionary/page/writer/markdown_to_notion_converter.py +0 -139
  363. notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
  364. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  365. notionary/page/writer/notion_text_length_processor.py +0 -150
  366. notionary/schemas/__init__.py +0 -3
  367. notionary/schemas/base.py +0 -73
  368. notionary/shared/__init__.py +0 -3
  369. notionary/shared/name_to_id_resolver.py +0 -203
  370. notionary/telemetry/__init__.py +0 -19
  371. notionary/telemetry/service.py +0 -136
  372. notionary/telemetry/views.py +0 -73
  373. notionary/user/base_notion_user.py +0 -53
  374. notionary/user/models.py +0 -84
  375. notionary/user/notion_bot_user.py +0 -226
  376. notionary/user/notion_user.py +0 -255
  377. notionary/user/notion_user_manager.py +0 -101
  378. notionary/util/__init__.py +0 -15
  379. notionary/util/concurrency_limiter.py +0 -0
  380. notionary/util/factory_decorator.py +0 -0
  381. notionary/util/factory_only.py +0 -37
  382. notionary/util/fuzzy.py +0 -75
  383. notionary/util/page_id_utils.py +0 -27
  384. notionary/util/singleton.py +0 -18
  385. notionary/util/singleton_metaclass.py +0 -22
  386. notionary/workspace.py +0 -105
  387. notionary-0.2.26.dist-info/RECORD +0 -202
@@ -0,0 +1,171 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from abc import ABC, abstractmethod
5
+ from collections.abc import Sequence
6
+ from typing import TYPE_CHECKING, Self
7
+
8
+ from notionary.shared.entity.entity_metadata_update_client import EntityMetadataUpdateClient
9
+ from notionary.user.schemas import PartialUserDto
10
+ from notionary.user.service import UserService
11
+ from notionary.utils.mixins.logging import LoggingMixin
12
+ from notionary.utils.uuid_utils import extract_uuid
13
+
14
+ if TYPE_CHECKING:
15
+ from notionary.user.base import BaseUser
16
+
17
+
18
+ class Entity(LoggingMixin, ABC):
19
+ def __init__(
20
+ self,
21
+ id: str,
22
+ created_time: str,
23
+ created_by: PartialUserDto,
24
+ last_edited_time: str,
25
+ last_edited_by: PartialUserDto,
26
+ in_trash: bool,
27
+ emoji_icon: str | None = None,
28
+ external_icon_url: str | None = None,
29
+ cover_image_url: str | None = None,
30
+ user_service: UserService | None = None,
31
+ ) -> None:
32
+ self._id = id
33
+ self._created_time = created_time
34
+ self._created_by = created_by
35
+ self._last_edited_time = last_edited_time
36
+ self._last_edited_by = last_edited_by
37
+ self._emoji_icon = emoji_icon
38
+ self._external_icon_url = external_icon_url
39
+ self._cover_image_url = cover_image_url
40
+ self._in_trash = in_trash
41
+ self._user_service = user_service or UserService()
42
+
43
+ @classmethod
44
+ @abstractmethod
45
+ async def from_id(cls, id: str) -> Self:
46
+ pass
47
+
48
+ @classmethod
49
+ @abstractmethod
50
+ async def from_title(cls, title: str) -> Self:
51
+ pass
52
+
53
+ @classmethod
54
+ async def from_url(cls, url: str) -> Self:
55
+ entity_id = extract_uuid(url)
56
+ if not entity_id:
57
+ raise ValueError(f"Could not extract entity ID from URL: {url}")
58
+ return await cls.from_id(entity_id)
59
+
60
+ @property
61
+ @abstractmethod
62
+ def _entity_metadata_update_client(self) -> EntityMetadataUpdateClient:
63
+ # functionality for updating properties like title, icon, cover, archive status depends on interface for template like implementation
64
+ # has to be implementated by inheritants to correctly use the methods below
65
+ ...
66
+
67
+ @property
68
+ def id(self) -> str:
69
+ return self._id
70
+
71
+ @property
72
+ def created_time(self) -> str:
73
+ return self._created_time
74
+
75
+ @property
76
+ def last_edited_time(self) -> str:
77
+ return self._last_edited_time
78
+
79
+ @property
80
+ def in_trash(self) -> bool:
81
+ return self._in_trash
82
+
83
+ @property
84
+ def emoji_icon(self) -> str | None:
85
+ return self._emoji_icon
86
+
87
+ @property
88
+ def external_icon_url(self) -> str | None:
89
+ return self._external_icon_url
90
+
91
+ @property
92
+ def cover_image_url(self) -> str | None:
93
+ return self._cover_image_url
94
+
95
+ @property
96
+ def created_by(self) -> PartialUserDto:
97
+ return self._created_by
98
+
99
+ @property
100
+ def last_edited_by(self) -> PartialUserDto:
101
+ return self._last_edited_by
102
+
103
+ async def get_created_by_user(self) -> BaseUser | None:
104
+ return await self._user_service.get_user_by_id(self._created_by.id)
105
+
106
+ async def get_last_edited_by_user(self) -> BaseUser | None:
107
+ return await self._user_service.get_user_by_id(self._last_edited_by.id)
108
+
109
+ async def set_emoji_icon(self, emoji: str) -> None:
110
+ entity_response = await self._entity_metadata_update_client.patch_emoji_icon(emoji)
111
+ self._emoji_icon = entity_response.icon.emoji if entity_response.icon else None
112
+ self._external_icon_url = None
113
+
114
+ async def set_external_icon(self, icon_url: str) -> None:
115
+ entity_response = await self._entity_metadata_update_client.patch_external_icon(icon_url)
116
+ self._emoji_icon = None
117
+ self._external_icon_url = (
118
+ entity_response.icon.external.url if entity_response.icon and entity_response.icon.external else None
119
+ )
120
+
121
+ async def remove_icon(self) -> None:
122
+ await self._entity_metadata_update_client.remove_icon()
123
+ self._emoji_icon = None
124
+ self._external_icon_url = None
125
+
126
+ async def set_cover_image_by_url(self, image_url: str) -> None:
127
+ entity_response = await self._entity_metadata_update_client.patch_external_cover(image_url)
128
+ self._cover_image_url = (
129
+ entity_response.cover.external.url if entity_response.cover and entity_response.cover.external else None
130
+ )
131
+
132
+ async def set_random_gradient_cover(self) -> None:
133
+ random_cover_url = self._get_random_gradient_cover()
134
+ await self.set_cover_image_by_url(random_cover_url)
135
+
136
+ async def remove_cover_image(self) -> None:
137
+ await self._entity_metadata_update_client.remove_cover()
138
+ self._cover_image_url = None
139
+
140
+ async def move_to_trash(self) -> None:
141
+ if self._in_trash:
142
+ self.logger.warning("Entity is already in trash.")
143
+ return
144
+
145
+ entity_response = await self._entity_metadata_update_client.move_to_trash()
146
+ self._in_trash = entity_response.in_trash
147
+
148
+ async def restore_from_trash(self) -> None:
149
+ if not self._in_trash:
150
+ self.logger.warning("Entity is not in trash.")
151
+ return
152
+
153
+ entity_response = await self._entity_metadata_update_client.restore_from_trash()
154
+ self._in_trash = entity_response.in_trash
155
+
156
+ def __repr__(self) -> str:
157
+ attrs = []
158
+ for key, value in self.__dict__.items():
159
+ if key.startswith("_") and not key.startswith("__"):
160
+ attr_name = key[1:]
161
+ attrs.append(f"{attr_name}={value!r}")
162
+
163
+ attrs_str = ", ".join(attrs)
164
+ return f"{self.__class__.__name__}({attrs_str})"
165
+
166
+ def _get_random_gradient_cover(self) -> str:
167
+ DEFAULT_NOTION_COVERS: Sequence[str] = [
168
+ f"https://www.notion.so/images/page-cover/gradients_{i}.png" for i in range(1, 10)
169
+ ]
170
+
171
+ return random.choice(DEFAULT_NOTION_COVERS)
@@ -0,0 +1,20 @@
1
+ from enum import StrEnum
2
+ from typing import Literal, Self
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from notionary.shared.models.file import ExternalFile
7
+
8
+
9
+ class CoverType(StrEnum):
10
+ EXTERNAL = "external"
11
+ FILE = "file"
12
+
13
+
14
+ class NotionCover(BaseModel):
15
+ type: Literal[CoverType.EXTERNAL, CoverType.FILE] = CoverType.EXTERNAL
16
+ external: ExternalFile | None = None
17
+
18
+ @classmethod
19
+ def from_url(cls, url: str) -> Self:
20
+ return cls(icon=ExternalFile(url))
@@ -0,0 +1,21 @@
1
+ from enum import StrEnum
2
+ from typing import Literal, Self
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class FileType(StrEnum):
8
+ EXTERNAL = "external"
9
+
10
+
11
+ class ExternalFile(BaseModel):
12
+ url: str
13
+
14
+
15
+ class ExternalRessource(BaseModel):
16
+ type: Literal[FileType.EXTERNAL] = FileType.EXTERNAL
17
+ external: ExternalFile
18
+
19
+ @classmethod
20
+ def from_url(cls, url: str) -> Self:
21
+ return cls(external=ExternalFile(url=url))
@@ -0,0 +1,28 @@
1
+ from enum import StrEnum
2
+ from typing import Literal, Self
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from notionary.shared.models.file import ExternalFile
7
+
8
+
9
+ class IconType(StrEnum):
10
+ EMOJI = "emoji"
11
+ EXTERNAL = "external"
12
+
13
+
14
+ class EmojiIcon(BaseModel):
15
+ type: Literal[IconType.EMOJI] = IconType.EMOJI
16
+ emoji: str
17
+
18
+
19
+ class ExternalIcon(BaseModel):
20
+ type: Literal[IconType.EXTERNAL] = IconType.EXTERNAL
21
+ external: ExternalFile
22
+
23
+ @classmethod
24
+ def from_url(cls, url: str) -> Self:
25
+ return cls(external=ExternalFile(url=url))
26
+
27
+
28
+ Icon = EmojiIcon | ExternalIcon
@@ -0,0 +1,41 @@
1
+ from enum import StrEnum
2
+ from typing import Literal
3
+
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class ParentType(StrEnum):
8
+ DATABASE_ID = "database_id"
9
+ DATA_SOURCE_ID = "data_source_id"
10
+ PAGE_ID = "page_id"
11
+ BLOCK_ID = "block_id"
12
+ WORKSPACE = "workspace"
13
+
14
+
15
+ class DataSourceParent(BaseModel):
16
+ type: Literal[ParentType.DATA_SOURCE_ID]
17
+ data_source_id: str
18
+ database_id: str
19
+
20
+
21
+ class PageParent(BaseModel):
22
+ type: Literal[ParentType.PAGE_ID]
23
+ page_id: str
24
+
25
+
26
+ class BlockParent(BaseModel):
27
+ type: Literal[ParentType.BLOCK_ID]
28
+ block_id: str
29
+
30
+
31
+ class WorkspaceParent(BaseModel):
32
+ type: Literal[ParentType.WORKSPACE]
33
+ workspace: bool
34
+
35
+
36
+ class DatabaseParent(BaseModel):
37
+ type: Literal[ParentType.DATABASE_ID]
38
+ database_id: str
39
+
40
+
41
+ Parent = DataSourceParent | PageParent | BlockParent | WorkspaceParent | DatabaseParent
@@ -0,0 +1,30 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class PropertyType(StrEnum):
5
+ TITLE = "title"
6
+ RICH_TEXT = "rich_text"
7
+ SELECT = "select"
8
+ MULTI_SELECT = "multi_select"
9
+ STATUS = "status"
10
+ NUMBER = "number"
11
+ DATE = "date"
12
+ CHECKBOX = "checkbox"
13
+ URL = "url"
14
+ EMAIL = "email"
15
+ PHONE_NUMBER = "phone_number"
16
+ PEOPLE = "people"
17
+ CREATED_BY = "created_by"
18
+ LAST_EDITED_BY = "last_edited_by"
19
+ CREATED_TIME = "created_time"
20
+ LAST_EDITED_TIME = "last_edited_time"
21
+ LAST_VISITED_TIME = "last_visited_time"
22
+ FORMULA = "formula"
23
+ ROLLUP = "rollup"
24
+ FILES = "files"
25
+ RELATION = "relation"
26
+ BUTTON = "button"
27
+ LOCATION = "location"
28
+ PLACE = "place"
29
+ VERIFICATION = "verification"
30
+ UNIQUE_ID = "unique_id"
@@ -1,11 +1,7 @@
1
- from .client import NotionUserClient
2
- from .notion_bot_user import NotionBotUser
3
- from .notion_user import NotionUser
4
- from .notion_user_manager import NotionUserManager
1
+ from .bot import BotUser
2
+ from .person import PersonUser
5
3
 
6
4
  __all__ = [
7
- "NotionUser",
8
- "NotionUserManager",
9
- "NotionUserClient",
10
- "NotionBotUser",
5
+ "BotUser",
6
+ "PersonUser",
11
7
  ]
notionary/user/base.py ADDED
@@ -0,0 +1,89 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Self
3
+
4
+ from notionary.user.client import UserHttpClient
5
+ from notionary.user.schemas import UserResponseDto, UserType
6
+ from notionary.utils.fuzzy import find_best_match
7
+
8
+
9
+ class BaseUser(ABC):
10
+ def __init__(
11
+ self,
12
+ id: str,
13
+ name: str | None = None,
14
+ avatar_url: str | None = None,
15
+ ) -> None:
16
+ self._id = id
17
+ self._name = name
18
+ self._avatar_url = avatar_url
19
+
20
+ @classmethod
21
+ async def from_id(
22
+ cls,
23
+ user_id: str,
24
+ http_client: UserHttpClient | None = None,
25
+ ) -> Self:
26
+ client = http_client or UserHttpClient()
27
+ user_dto = await client.get_user_by_id(user_id)
28
+
29
+ expected_type = cls._get_expected_user_type()
30
+ if user_dto.type != expected_type:
31
+ raise ValueError(f"User {user_id} is not a '{expected_type.value}', but '{user_dto.type.value}'")
32
+
33
+ return cls.from_dto(user_dto)
34
+
35
+ @classmethod
36
+ async def from_name(
37
+ cls,
38
+ name: str,
39
+ http_client: UserHttpClient | None = None,
40
+ ) -> Self:
41
+ client = http_client or UserHttpClient()
42
+ all_users = await cls._get_all_users_of_type(client)
43
+
44
+ if not all_users:
45
+ user_type = cls._get_expected_user_type().value
46
+ raise ValueError(f"No '{user_type}' users found in the workspace")
47
+
48
+ best_match = find_best_match(query=name, items=all_users, text_extractor=cls._get_name_extractor())
49
+ if not best_match:
50
+ user_type = cls._get_expected_user_type().value
51
+ raise ValueError(f"No '{user_type}' user found with name similar to '{name}'")
52
+
53
+ return best_match
54
+
55
+ @classmethod
56
+ async def _get_all_users_of_type(cls, http_client: UserHttpClient) -> list[Self]:
57
+ all_workspace_user_dtos = await http_client.get_all_workspace_users()
58
+ expected_type = cls._get_expected_user_type()
59
+ filtered_dtos = [dto for dto in all_workspace_user_dtos if dto.type == expected_type]
60
+ return [cls.from_dto(dto) for dto in filtered_dtos]
61
+
62
+ @classmethod
63
+ @abstractmethod
64
+ def _get_expected_user_type(cls) -> UserType:
65
+ pass
66
+
67
+ @classmethod
68
+ @abstractmethod
69
+ def from_dto(cls, user_dto: UserResponseDto) -> Self:
70
+ pass
71
+
72
+ @classmethod
73
+ def _get_name_extractor(cls):
74
+ return lambda user: user.name or ""
75
+
76
+ @property
77
+ def id(self) -> str:
78
+ return self._id
79
+
80
+ @property
81
+ def name(self) -> str | None:
82
+ return self._name
83
+
84
+ @property
85
+ def avatar_url(self) -> str | None:
86
+ return self._avatar_url
87
+
88
+ def __repr__(self) -> str:
89
+ return f"{self.__class__.__name__}(id={self._id!r}, name={self._name!r})"
notionary/user/bot.py ADDED
@@ -0,0 +1,70 @@
1
+ from typing import Self, cast
2
+
3
+ from notionary.user.base import BaseUser
4
+ from notionary.user.client import UserHttpClient
5
+ from notionary.user.schemas import BotUserDto, BotUserResponseDto, UserResponseDto, UserType, WorkspaceOwnerType
6
+
7
+
8
+ class BotUser(BaseUser):
9
+ def __init__(
10
+ self,
11
+ id: str,
12
+ workspace_file_upload_limit_in_bytes: int,
13
+ owner_type: WorkspaceOwnerType | None,
14
+ name: str | None = None,
15
+ avatar_url: str | None = None,
16
+ workspace_name: str | None = None,
17
+ ) -> None:
18
+ super().__init__(id=id, name=name, avatar_url=avatar_url)
19
+ self._workspace_name = workspace_name
20
+ self._workspace_file_upload_limit_in_bytes = workspace_file_upload_limit_in_bytes
21
+ self._owner_type = owner_type
22
+
23
+ @classmethod
24
+ def _get_expected_user_type(cls) -> UserType:
25
+ return UserType.BOT
26
+
27
+ @classmethod
28
+ async def from_current_integration(
29
+ cls,
30
+ http_client: UserHttpClient | None = None,
31
+ ) -> Self:
32
+ client = http_client or UserHttpClient()
33
+ user_dto = await client.get_current_integration_bot()
34
+ return cls.from_dto(user_dto)
35
+
36
+ @classmethod
37
+ def from_dto(cls, user_dto: UserResponseDto) -> Self:
38
+ bot_dto = cast(BotUserResponseDto, user_dto)
39
+ bot_data: BotUserDto = bot_dto.bot
40
+
41
+ owner_type = bot_data.owner.type if bot_data and bot_data.owner else None
42
+ workspace_name = bot_data.workspace_name if bot_data else None
43
+
44
+ limit = 0
45
+ if bot_data and bot_data.workspace_limits:
46
+ limit = bot_data.workspace_limits.max_file_upload_size_in_bytes
47
+
48
+ return cls(
49
+ id=bot_dto.id,
50
+ name=bot_dto.name,
51
+ avatar_url=bot_dto.avatar_url,
52
+ workspace_name=workspace_name,
53
+ workspace_file_upload_limit_in_bytes=limit,
54
+ owner_type=owner_type,
55
+ )
56
+
57
+ @property
58
+ def workspace_name(self) -> str | None:
59
+ return self._workspace_name
60
+
61
+ @property
62
+ def workspace_file_upload_limit_in_bytes(self) -> int:
63
+ return self._workspace_file_upload_limit_in_bytes
64
+
65
+ @property
66
+ def owner_type(self) -> WorkspaceOwnerType | None:
67
+ return self._owner_type
68
+
69
+ def __repr__(self) -> str:
70
+ return f"BotUser(id={self._id!r}, name={self._name!r}, avatar_url={self._avatar_url!r}, workspace_name={self._workspace_name!r}, workspace_file_upload_limit_in_bytes={self._workspace_file_upload_limit_in_bytes!r}, owner_type={self._owner_type!r})"
notionary/user/client.py CHANGED
@@ -1,128 +1,39 @@
1
- from typing import List, Optional
1
+ from pydantic import TypeAdapter
2
2
 
3
- from notionary.base_notion_client import BaseNotionClient
4
- from notionary.user.models import (
5
- NotionBotUserResponse,
6
- NotionUserResponse,
3
+ from notionary.http.client import NotionHttpClient
4
+ from notionary.user.schemas import (
5
+ BotUserResponseDto,
7
6
  NotionUsersListResponse,
7
+ UserResponseDto,
8
8
  )
9
+ from notionary.utils.pagination import paginate_notion_api
9
10
 
10
11
 
11
- class NotionUserClient(BaseNotionClient):
12
- """
13
- Client for Notion user-specific operations.
14
- Inherits base HTTP functionality from BaseNotionClient.
15
-
16
- Note: The Notion API only supports individual user queries and bot user info.
17
- List users endpoint is available but only returns workspace members (no guests).
18
- """
19
-
20
- async def get_user(self, user_id: str) -> Optional[NotionUserResponse]:
21
- """
22
- Retrieve a user by their ID.
23
- """
12
+ class UserHttpClient(NotionHttpClient):
13
+ async def get_user_by_id(self, user_id: str) -> UserResponseDto:
24
14
  response = await self.get(f"users/{user_id}")
25
- if response is None:
26
- self.logger.error("Failed to fetch user %s - API returned None", user_id)
27
- return None
28
15
 
29
- try:
30
- return NotionUserResponse.model_validate(response)
31
- except Exception as e:
32
- self.logger.error("Failed to validate user response for %s: %s", user_id, e)
33
- return None
34
-
35
- async def get_bot_user(self) -> Optional[NotionBotUserResponse]:
36
- """
37
- Retrieve your token's bot user information.
38
- """
39
- response = await self.get("users/me")
40
- if response is None:
41
- self.logger.error("Failed to fetch bot user - API returned None")
42
- return None
16
+ adapter = TypeAdapter(UserResponseDto)
17
+ return adapter.validate_python(response)
43
18
 
44
- try:
45
- return NotionBotUserResponse.model_validate(response)
46
- except Exception as e:
47
- self.logger.error("Failed to validate bot user response: %s", e)
48
- return None
19
+ async def get_all_workspace_users(self) -> list[UserResponseDto]:
20
+ all_entities = await paginate_notion_api(self._get_workspace_entities, page_size=100)
49
21
 
50
- async def list_users(
51
- self, page_size: int = 100, start_cursor: Optional[str] = None
52
- ) -> Optional[NotionUsersListResponse]:
53
- """
54
- List all users in the workspace (paginated).
22
+ self.logger.info("Fetched %d total workspace users", len(all_entities))
23
+ return all_entities
55
24
 
56
- Note: Guests are not included in the response.
57
- """
58
- params = {"page_size": min(page_size, 100)} # API max is 100
25
+ async def _get_workspace_entities(
26
+ self, page_size: int = 100, start_cursor: str | None = None
27
+ ) -> NotionUsersListResponse | None:
28
+ params = {"page_size": min(page_size, 100)}
59
29
  if start_cursor:
60
30
  params["start_cursor"] = start_cursor
61
31
 
62
32
  response = await self.get("users", params=params)
63
- if response is None:
64
- self.logger.error("Failed to fetch users list - API returned None")
65
- return None
66
-
67
- try:
68
- return NotionUsersListResponse.model_validate(response)
69
- except Exception as e:
70
- self.logger.error("Failed to validate users list response: %s", e)
71
- return None
72
-
73
- async def get_all_users(self) -> List[NotionUserResponse]:
74
- """
75
- Get all users in the workspace by handling pagination automatically.
76
- """
77
- all_users = []
78
- start_cursor = None
79
-
80
- while True:
81
- try:
82
- response = await self.list_users(
83
- page_size=100, start_cursor=start_cursor
84
- )
85
33
 
86
- if not response or not response.results:
87
- break
34
+ return NotionUsersListResponse.model_validate(response)
88
35
 
89
- all_users.extend(response.results)
90
-
91
- # Check if there are more pages
92
- if not response.has_more or not response.next_cursor:
93
- break
94
-
95
- start_cursor = response.next_cursor
96
-
97
- except Exception as e:
98
- self.logger.error("Error fetching all users: %s", str(e))
99
- break
100
-
101
- self.logger.info("Retrieved %d total users from workspace", len(all_users))
102
- return all_users
103
-
104
- async def get_workspace_name(self) -> Optional[str]:
105
- """
106
- Get the workspace name from the bot user.
107
- """
108
- try:
109
- bot_user = await self.get_bot_user()
110
- if bot_user and bot_user.bot and bot_user.bot.workspace_name:
111
- return bot_user.bot.workspace_name
112
- return None
113
- except Exception as e:
114
- self.logger.error("Error fetching workspace name: %s", str(e))
115
- return None
36
+ async def get_current_integration_bot(self) -> BotUserResponseDto:
37
+ response = await self.get("users/me")
116
38
 
117
- async def get_workspace_limits(self) -> Optional[dict]:
118
- """
119
- Get workspace limits from the bot user.
120
- """
121
- try:
122
- bot_user = await self.get_bot_user()
123
- if bot_user and bot_user.bot and bot_user.bot.workspace_limits:
124
- return bot_user.bot.workspace_limits.model_dump()
125
- return None
126
- except Exception as e:
127
- self.logger.error("Error fetching workspace limits: %s", str(e))
128
- return None
39
+ return BotUserResponseDto.model_validate(response)
@@ -0,0 +1,41 @@
1
+ from typing import Self, cast
2
+
3
+ from notionary.user.base import BaseUser
4
+ from notionary.user.schemas import PersonUserResponseDto, UserResponseDto, UserType
5
+
6
+
7
+ class PersonUser(BaseUser):
8
+ def __init__(
9
+ self,
10
+ id: str,
11
+ name: str,
12
+ avatar_url: str,
13
+ email: str,
14
+ ) -> None:
15
+ super().__init__(id=id, name=name, avatar_url=avatar_url)
16
+ self._email = email
17
+
18
+ @classmethod
19
+ def _get_expected_user_type(cls) -> UserType:
20
+ return UserType.PERSON
21
+
22
+ @classmethod
23
+ def from_dto(cls, user_dto: UserResponseDto) -> Self:
24
+ person_dto = cast(PersonUserResponseDto, user_dto)
25
+ return cls(
26
+ id=person_dto.id,
27
+ name=person_dto.name or "",
28
+ avatar_url=person_dto.avatar_url,
29
+ email=person_dto.person.email or "",
30
+ )
31
+
32
+ @property
33
+ def name(self) -> str:
34
+ return self._name or ""
35
+
36
+ @property
37
+ def email(self) -> str:
38
+ return self._email
39
+
40
+ def __repr__(self) -> str:
41
+ return f"PersonUser(id={self._id!r}, name={self._name!r}, email={self._email!r})"