better-notion 0.9.9__py3-none-any.whl → 1.0.1__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.
@@ -0,0 +1,216 @@
1
+ """State machine for managing task status transitions.
2
+
3
+ This module provides the state machine logic for task workflow transitions,
4
+ ensuring that tasks only move through valid states.
5
+ """
6
+
7
+ from enum import Enum
8
+ from typing import Dict, List, Optional, Tuple
9
+
10
+
11
+ class TaskStatus(Enum):
12
+ """Task status values following the workflow.
13
+
14
+ The typical workflow is:
15
+ Backlog → Claimed → In Progress → In Review → Completed
16
+
17
+ Tasks can be Cancelled from most states.
18
+ """
19
+
20
+ BACKLOG = "Backlog"
21
+ CLAIMED = "Claimed"
22
+ IN_PROGRESS = "In Progress"
23
+ IN_REVIEW = "In Review"
24
+ COMPLETED = "Completed"
25
+ CANCELLED = "Cancelled"
26
+
27
+
28
+ class TaskStateMachine:
29
+ """Manages valid task status transitions.
30
+
31
+ This state machine enforces the workflow rules for task status changes.
32
+ It prevents invalid transitions and provides helpers for querying
33
+ possible next states.
34
+
35
+ Example:
36
+ >>> # Check if a transition is valid
37
+ >>> is_valid = TaskStateMachine.can_transition(
38
+ ... TaskStatus.BACKLOG,
39
+ ... TaskStatus.CLAIMED
40
+ ... )
41
+ >>> print(is_valid) # True
42
+
43
+ >>> # Get next possible states
44
+ >>> next_states = TaskStateMachine.get_next_statuses("Backlog")
45
+ >>> print(next_states) # ["Claimed", "Cancelled"]
46
+
47
+ >>> # Validate transition
48
+ >>> is_valid, error = TaskStateMachine.validate_transition(
49
+ ... "Backlog",
50
+ ... "Completed"
51
+ ... )
52
+ >>> print(error) # "Invalid transition: Backlog → Completed"
53
+ """
54
+
55
+ # Define valid state transitions
56
+ TRANSITIONS: Dict[TaskStatus, List[TaskStatus]] = {
57
+ TaskStatus.BACKLOG: [
58
+ TaskStatus.CLAIMED,
59
+ TaskStatus.CANCELLED,
60
+ ],
61
+ TaskStatus.CLAIMED: [
62
+ TaskStatus.IN_PROGRESS,
63
+ TaskStatus.BACKLOG, # Unclaim and return to backlog
64
+ TaskStatus.CANCELLED,
65
+ ],
66
+ TaskStatus.IN_PROGRESS: [
67
+ TaskStatus.IN_REVIEW,
68
+ TaskStatus.COMPLETED, # Skip review if not needed
69
+ TaskStatus.CANCELLED,
70
+ ],
71
+ TaskStatus.IN_REVIEW: [
72
+ TaskStatus.COMPLETED,
73
+ TaskStatus.IN_PROGRESS, # Request changes during review
74
+ TaskStatus.CANCELLED,
75
+ ],
76
+ TaskStatus.COMPLETED: [
77
+ # Terminal state - no transitions out
78
+ ],
79
+ TaskStatus.CANCELLED: [
80
+ # Terminal state - no transitions out
81
+ ],
82
+ }
83
+
84
+ @classmethod
85
+ def can_transition(
86
+ cls,
87
+ from_status: TaskStatus,
88
+ to_status: TaskStatus,
89
+ ) -> bool:
90
+ """Check if a transition between two states is valid.
91
+
92
+ Args:
93
+ from_status: Current state
94
+ to_status: Desired next state
95
+
96
+ Returns:
97
+ True if the transition is valid, False otherwise
98
+
99
+ Example:
100
+ >>> TaskStateMachine.can_transition(
101
+ ... TaskStatus.BACKLOG,
102
+ ... TaskStatus.CLAIMED
103
+ ... )
104
+ True
105
+ """
106
+ allowed = cls.TRANSITIONS.get(from_status, [])
107
+ return to_status in allowed
108
+
109
+ @classmethod
110
+ def validate_transition(
111
+ cls,
112
+ from_status: str,
113
+ to_status: str,
114
+ ) -> Tuple[bool, Optional[str]]:
115
+ """Validate a transition and return (is_valid, error_message).
116
+
117
+ This is a convenience method that handles string status values
118
+ and returns a detailed error message if the transition is invalid.
119
+
120
+ Args:
121
+ from_status: Current status as string
122
+ to_status: Desired next status as string
123
+
124
+ Returns:
125
+ Tuple of (is_valid, error_message):
126
+ - is_valid: True if transition is valid
127
+ - error_message: None if valid, error description if invalid
128
+
129
+ Example:
130
+ >>> is_valid, error = TaskStateMachine.validate_transition(
131
+ ... "Backlog",
132
+ ... "Claimed"
133
+ ... )
134
+ >>> print(is_valid) # True
135
+ >>> print(error) # None
136
+
137
+ >>> is_valid, error = TaskStateMachine.validate_transition(
138
+ ... "Completed",
139
+ ... "In Progress"
140
+ ... )
141
+ >>> print(is_valid) # False
142
+ >>> print(error) # "Invalid transition: Completed → In Progress"
143
+ """
144
+ try:
145
+ from_enum = TaskStatus(from_status)
146
+ to_enum = TaskStatus(to_status)
147
+ except ValueError as e:
148
+ return False, f"Invalid status values: {from_status} → {to_status}"
149
+
150
+ if not cls.can_transition(from_enum, to_enum):
151
+ return False, f"Invalid transition: {from_status} → {to_status}"
152
+
153
+ return True, None
154
+
155
+ @classmethod
156
+ def get_next_statuses(cls, current_status: str) -> List[str]:
157
+ """Get list of valid next statuses from current status.
158
+
159
+ Args:
160
+ current_status: Current status as string
161
+
162
+ Returns:
163
+ List of valid next status values as strings
164
+
165
+ Example:
166
+ >>> TaskStateMachine.get_next_statuses("Backlog")
167
+ ["Claimed", "Cancelled"]
168
+
169
+ >>> TaskStateMachine.get_next_statuses("Completed")
170
+ []
171
+ """
172
+ try:
173
+ current_enum = TaskStatus(current_status)
174
+ next_enums = cls.TRANSITIONS.get(current_enum, [])
175
+ return [e.value for e in next_enums]
176
+ except ValueError:
177
+ return []
178
+
179
+ @classmethod
180
+ def is_terminal_state(cls, status: str) -> bool:
181
+ """Check if a status is a terminal state (no transitions out).
182
+
183
+ Terminal states are Completed and Cancelled.
184
+
185
+ Args:
186
+ status: Status to check
187
+
188
+ Returns:
189
+ True if status is terminal, False otherwise
190
+
191
+ Example:
192
+ >>> TaskStateMachine.is_terminal_state("Completed")
193
+ True
194
+
195
+ >>> TaskStateMachine.is_terminal_state("In Progress")
196
+ False
197
+ """
198
+ try:
199
+ status_enum = TaskStatus(status)
200
+ allowed = cls.TRANSITIONS.get(status_enum, [])
201
+ return len(allowed) == 0
202
+ except ValueError:
203
+ return False
204
+
205
+ @classmethod
206
+ def get_initial_state(cls) -> str:
207
+ """Get the initial state for new tasks.
208
+
209
+ Returns:
210
+ The initial task status (Backlog)
211
+
212
+ Example:
213
+ >>> TaskStateMachine.get_initial_state()
214
+ "Backlog"
215
+ """
216
+ return TaskStatus.BACKLOG.value
@@ -0,0 +1,371 @@
1
+ """Workspace initializer for the agents workflow system.
2
+
3
+ This module provides functionality to initialize a new workspace with all
4
+ required databases for the workflow management system.
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any, Dict, Optional
10
+
11
+ from better_notion._cli.config import Config
12
+ from better_notion._sdk.client import NotionClient
13
+ from better_notion._sdk.models.page import Page
14
+ from better_notion.utils.agents.schemas import (
15
+ IncidentSchema,
16
+ IdeaSchema,
17
+ OrganizationSchema,
18
+ ProjectSchema,
19
+ TagSchema,
20
+ TaskSchema,
21
+ VersionSchema,
22
+ WorkIssueSchema,
23
+ )
24
+
25
+
26
+ class WorkspaceInitializer:
27
+ """Initialize a new workspace with workflow databases.
28
+
29
+ This class handles the creation of all required databases and their
30
+ relationships for the agents workflow system.
31
+
32
+ Example:
33
+ >>> initializer = WorkspaceInitializer(client)
34
+ >>> database_ids = await initializer.initialize_workspace(
35
+ ... parent_page_id="page123",
36
+ ... workspace_name="My Workspace"
37
+ ... )
38
+ """
39
+
40
+ def __init__(self, client: NotionClient) -> None:
41
+ """Initialize the workspace initializer.
42
+
43
+ Args:
44
+ client: Authenticated NotionClient instance
45
+ """
46
+ self._client = client
47
+ self._database_ids: Dict[str, str] = {}
48
+
49
+ async def initialize_workspace(
50
+ self,
51
+ parent_page_id: str,
52
+ workspace_name: str = "Agents Workspace",
53
+ ) -> Dict[str, str]:
54
+ """Initialize a complete workspace with all databases.
55
+
56
+ Creates all 8 databases in the correct order, establishing
57
+ relationships between them.
58
+
59
+ Args:
60
+ parent_page_id: ID of the parent page where databases will be created
61
+ workspace_name: Name for the workspace (used for database titles)
62
+
63
+ Returns:
64
+ Dict mapping database names to their IDs
65
+
66
+ Raises:
67
+ Exception: If database creation fails
68
+ """
69
+ # Get parent page
70
+ parent = await Page.get(parent_page_id, client=self._client)
71
+
72
+ # Create databases in order (independent first, then dependent)
73
+ self._database_ids = {}
74
+
75
+ # 1. Organizations (independent)
76
+ await self._create_organizations_db(parent)
77
+
78
+ # 2. Tags (independent)
79
+ await self._create_tags_db(parent)
80
+
81
+ # 3. Projects (depends on Organizations)
82
+ await self._create_projects_db(parent)
83
+
84
+ # 4. Versions (depends on Projects)
85
+ await self._create_versions_db(parent)
86
+
87
+ # 5. Tasks (depends on Versions)
88
+ await self._create_tasks_db(parent)
89
+
90
+ # 6. Ideas (depends on Projects)
91
+ await self._create_ideas_db(parent)
92
+
93
+ # 7. Work Issues (depends on Projects, Tasks, Ideas)
94
+ await self._create_work_issues_db(parent)
95
+
96
+ # 8. Incidents (depends on Projects, Versions, Tasks)
97
+ await self._create_incidents_db(parent)
98
+
99
+ return self._database_ids
100
+
101
+ async def _create_organizations_db(self, parent: Page) -> None:
102
+ """Create Organizations database."""
103
+ schema = OrganizationSchema.get_schema()
104
+
105
+ db = await self._client.databases.create(
106
+ parent=parent,
107
+ title="Organizations",
108
+ schema=schema,
109
+ )
110
+
111
+ self._database_ids["organizations"] = db.id
112
+
113
+ async def _create_tags_db(self, parent: Page) -> None:
114
+ """Create Tags database."""
115
+ schema = TagSchema.get_schema()
116
+
117
+ db = await self._client.databases.create(
118
+ parent=parent,
119
+ title="Tags",
120
+ schema=schema,
121
+ )
122
+
123
+ self._database_ids["tags"] = db.id
124
+
125
+ async def _create_projects_db(self, parent: Page) -> None:
126
+ """Create Projects database with relation to Organizations."""
127
+ schema = ProjectSchema.get_schema()
128
+
129
+ # Update relation with Organizations database ID
130
+ if "organizations" in self._database_ids:
131
+ schema["Organization"]["relation"]["database_id"] = self._database_ids[
132
+ "organizations"
133
+ ]
134
+
135
+ db = await self._client.databases.create(
136
+ parent=parent,
137
+ title="Projects",
138
+ schema=schema,
139
+ )
140
+
141
+ self._database_ids["projects"] = db.id
142
+
143
+ # Update Organizations database with reverse relation
144
+ await self._add_reverse_relation(
145
+ self._database_ids["organizations"],
146
+ db.id,
147
+ "Projects",
148
+ )
149
+
150
+ async def _create_versions_db(self, parent: Page) -> None:
151
+ """Create Versions database with relation to Projects."""
152
+ schema = VersionSchema.get_schema()
153
+
154
+ # Update relation with Projects database ID
155
+ if "projects" in self._database_ids:
156
+ schema["Project"]["relation"]["database_id"] = self._database_ids["projects"]
157
+
158
+ db = await self._client.databases.create(
159
+ parent=parent,
160
+ title="Versions",
161
+ schema=schema,
162
+ )
163
+
164
+ self._database_ids["versions"] = db.id
165
+
166
+ # Update Projects database with reverse relation
167
+ await self._add_reverse_relation(
168
+ self._database_ids["projects"],
169
+ db.id,
170
+ "Versions",
171
+ )
172
+
173
+ async def _create_tasks_db(self, parent: Page) -> None:
174
+ """Create Tasks database with relations to Versions (self-referencing for dependencies)."""
175
+ schema = TaskSchema.get_schema()
176
+
177
+ # Update relations with Versions database ID
178
+ if "versions" in self._database_ids:
179
+ schema["Version"]["relation"]["database_id"] = self._database_ids["versions"]
180
+ schema["Target Version"]["relation"]["database_id"] = self._database_ids[
181
+ "versions"
182
+ ]
183
+
184
+ # Tasks reference themselves for dependencies
185
+ # Will be updated after database creation
186
+ db = await self._client.databases.create(
187
+ parent=parent,
188
+ title="Tasks",
189
+ schema=schema,
190
+ )
191
+
192
+ self._database_ids["tasks"] = db.id
193
+
194
+ # Update self-referential relations
195
+ await self._update_self_relations(db.id)
196
+
197
+ # Update Versions database with reverse relation
198
+ await self._add_reverse_relation(
199
+ self._database_ids["versions"],
200
+ db.id,
201
+ "Tasks",
202
+ )
203
+
204
+ async def _create_ideas_db(self, parent: Page) -> None:
205
+ """Create Ideas database with relations to Projects and Tasks."""
206
+ schema = IdeaSchema.get_schema()
207
+
208
+ # Update relations
209
+ if "projects" in self._database_ids:
210
+ schema["Project"]["relation"]["database_id"] = self._database_ids["projects"]
211
+
212
+ # Related Task will be updated after Tasks DB creation
213
+ db = await self._client.databases.create(
214
+ parent=parent,
215
+ title="Ideas",
216
+ schema=schema,
217
+ )
218
+
219
+ self._database_ids["ideas"] = db.id
220
+
221
+ # Update Projects database with reverse relation
222
+ await self._add_reverse_relation(
223
+ self._database_ids["projects"],
224
+ db.id,
225
+ "Ideas",
226
+ )
227
+
228
+ async def _create_work_issues_db(self, parent: Page) -> None:
229
+ """Create Work Issues database with relations."""
230
+ schema = WorkIssueSchema.get_schema()
231
+
232
+ # Update relations
233
+ if "projects" in self._database_ids:
234
+ schema["Project"]["relation"]["database_id"] = self._database_ids["projects"]
235
+ if "tasks" in self._database_ids:
236
+ schema["Task"]["relation"]["database_id"] = self._database_ids["tasks"]
237
+ if "ideas" in self._database_ids:
238
+ schema["Related Idea"]["relation"]["database_id"] = self._database_ids["ideas"]
239
+
240
+ db = await self._client.databases.create(
241
+ parent=parent,
242
+ title="Work Issues",
243
+ schema=schema,
244
+ )
245
+
246
+ self._database_ids["work_issues"] = db.id
247
+
248
+ async def _create_incidents_db(self, parent: Page) -> None:
249
+ """Create Incidents database with relations."""
250
+ schema = IncidentSchema.get_schema()
251
+
252
+ # Update relations
253
+ if "projects" in self._database_ids:
254
+ schema["Project"]["relation"]["database_id"] = self._database_ids["projects"]
255
+ if "versions" in self._database_ids:
256
+ schema["Affected Version"]["relation"][
257
+ "database_id"
258
+ ] = self._database_ids["versions"]
259
+ if "tasks" in self._database_ids:
260
+ schema["Fix Task"]["relation"]["database_id"] = self._database_ids["tasks"]
261
+
262
+ db = await self._client.databases.create(
263
+ parent=parent,
264
+ title="Incidents",
265
+ schema=schema,
266
+ )
267
+
268
+ self._database_ids["incidents"] = db.id
269
+
270
+ async def _add_reverse_relation(
271
+ self,
272
+ database_id: str,
273
+ related_db_id: str,
274
+ property_name: str,
275
+ ) -> None:
276
+ """Add reverse relation to a database.
277
+
278
+ Note: This is a placeholder. The actual Notion API creates
279
+ dual_property relations automatically. This method exists
280
+ for documentation purposes.
281
+ """
282
+ # Notion API handles dual_property relations automatically
283
+ # This is a no-op but documents the intent
284
+ pass
285
+
286
+ async def _update_self_relations(self, database_id: str) -> None:
287
+ """Update self-referential relations in Tasks database.
288
+
289
+ Note: The database was created with placeholder relations.
290
+ This method updates them to point to themselves.
291
+ """
292
+ # Get the database
293
+ db = await self._client.databases.get(database_id)
294
+
295
+ # Update Dependencies and Dependent Tasks to point to self
296
+ # Note: This would require updating the database schema
297
+ # via the Notion API, which may not support all updates
298
+ pass
299
+
300
+ def save_database_ids(self, path: Optional[Path] = None) -> None:
301
+ """Save database IDs to a config file.
302
+
303
+ Args:
304
+ path: Path to save config file (default: ~/.notion/workspace.json)
305
+ """
306
+ if path is None:
307
+ path = Path.home() / ".notion" / "workspace.json"
308
+
309
+ path.parent.mkdir(parents=True, exist_ok=True)
310
+
311
+ with open(path, "w", encoding="utf-8") as f:
312
+ json.dump(self._database_ids, f, indent=2)
313
+
314
+ @classmethod
315
+ def load_database_ids(cls, path: Optional[Path] = None) -> Dict[str, str]:
316
+ """Load database IDs from config file.
317
+
318
+ Args:
319
+ path: Path to config file (default: ~/.notion/workspace.json)
320
+
321
+ Returns:
322
+ Dict mapping database names to IDs
323
+
324
+ Raises:
325
+ FileNotFoundError: If config file doesn't exist
326
+ """
327
+ if path is None:
328
+ path = Path.home() / ".notion" / "workspace.json"
329
+
330
+ with open(path, encoding="utf-8") as f:
331
+ return json.load(f)
332
+
333
+
334
+ async def initialize_workspace_command(
335
+ parent_page_id: str,
336
+ workspace_name: str = "Agents Workspace",
337
+ ) -> Dict[str, str]:
338
+ """Convenience function to initialize a workspace.
339
+
340
+ Args:
341
+ parent_page_id: ID of parent page
342
+ workspace_name: Name for the workspace
343
+
344
+ Returns:
345
+ Dict mapping database names to IDs
346
+
347
+ Example:
348
+ >>> from better_notion._cli.config import Config
349
+ >>> from better_notion._sdk.client import NotionClient
350
+ >>>
351
+ >>> config = Config.load()
352
+ >>> client = NotionClient(auth=config.token)
353
+ >>>
354
+ >>> db_ids = await initialize_workspace_command(
355
+ ... parent_page_id="page123",
356
+ ... workspace_name="My Workspace"
357
+ ... )
358
+ """
359
+ from better_notion._cli.config import Config
360
+ from better_notion._sdk.client import NotionClient
361
+
362
+ config = Config.load()
363
+ client = NotionClient(auth=config.token)
364
+
365
+ initializer = WorkspaceInitializer(client)
366
+ database_ids = await initializer.initialize_workspace(parent_page_id, workspace_name)
367
+
368
+ # Save to config file
369
+ initializer.save_database_ids()
370
+
371
+ return database_ids
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: better-notion
3
- Version: 0.9.9
4
- Summary: A high-level Python SDK for the Notion API with developer experience in mind.
3
+ Version: 1.0.1
4
+ Summary: A high-level Python SDK for the Notion API with developer experience in mind. Now with AI agents workflow management system!
5
5
  Project-URL: Homepage, https://github.com/nesalia-inc/better-notion
6
6
  Project-URL: Documentation, https://github.com/nesalia-inc/better-notion#readme
7
7
  Project-URL: Repository, https://github.com/nesalia-inc/better-notion
@@ -105,13 +105,22 @@ better_notion/plugins/__init__.py,sha256=ZbaLy0BS4_zWfEyCW0Xv9EhK427cEhrKIap_8DR
105
105
  better_notion/plugins/base.py,sha256=G2asUEvQXWIiyWvNtB1hgg9JXZhDayi7Jvfq15FeQWY,5255
106
106
  better_notion/plugins/loader.py,sha256=ewCG_Thv7Khh8IsHLB17U1Hi0cWpq0YG3w37hrpbeRk,7710
107
107
  better_notion/plugins/state.py,sha256=jH_tZWvC35hqLO4qwl2Kwq9ziWVavwCEUcCqy3s5wMY,3780
108
- better_notion/plugins/official/__init__.py,sha256=4c0kaPLOqUQ7vIISgCm4Y9TG306Vew5A8dfDrLsAGwc,327
108
+ better_notion/plugins/official/__init__.py,sha256=rPg5vdk1cEANVstMPzxcWmImtsOpdSR40JSml7h1uUk,426
109
+ better_notion/plugins/official/agents.py,sha256=8Cwx5fkNMaYc1bavS48jyvaXCTMYWgCaIMZH0y9YSbM,12696
109
110
  better_notion/plugins/official/productivity.py,sha256=_-whP4pYA4HufE1aUFbIdhrjU-O9njI7xUO_Id2M1J8,8726
110
111
  better_notion/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
112
  better_notion/utils/helpers.py,sha256=HgFuUQlG_HzBOB0z2GA9RxPLoXgwRc0DIxa9Fg6C-Jk,2337
112
113
  better_notion/utils/retry.py,sha256=9WJDNitiIfVTL18hIlipvOKn41ukePrOwtAwx-LevpQ,7479
113
- better_notion-0.9.9.dist-info/METADATA,sha256=Dr3EAgGQk505eplEDCEObHyv1bf81bkjjWNI9d6cPOQ,11096
114
- better_notion-0.9.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
115
- better_notion-0.9.9.dist-info/entry_points.txt,sha256=D0bUcP7Z00Zyjxw7r2p29T95UrwioDO0aGDoHe9I6fo,55
116
- better_notion-0.9.9.dist-info/licenses/LICENSE,sha256=BAdN3JpgMY_y_fWqZSCFSvSbC2mTHP-BKDAzF5FXQAI,1069
117
- better_notion-0.9.9.dist-info/RECORD,,
114
+ better_notion/utils/agents/__init__.py,sha256=Zu32q0abbimrJY5dczjWNEastE-IrtGPQjpxD4JS4IU,1715
115
+ better_notion/utils/agents/auth.py,sha256=_SBcqBjXmX8CJMCPpRWM-UuaDg7-OOtMWbhnYEiIBTs,6568
116
+ better_notion/utils/agents/dependency_resolver.py,sha256=PfHHDIQztGih4LwylMb0_MyhDFbOYPjvUxcxY52mSEs,12033
117
+ better_notion/utils/agents/project_context.py,sha256=aJlzy5H2rL4sAfW2jHL_3K2VkBJ4ihUhCRVolkpuO78,7477
118
+ better_notion/utils/agents/rbac.py,sha256=8ZA8Y7wbOiVZDbpjpH7iC35SZrZ0jl4fcJ3xWCm3SsE,11820
119
+ better_notion/utils/agents/schemas.py,sha256=e_lpGGO12FXtfqFyI91edj9xc5RUtuA6pU7Sk6ip7xg,21784
120
+ better_notion/utils/agents/state_machine.py,sha256=xUBEeDTbU1Xq-rsRo2sbr6AUYpYrV9DTHOtZT2cWES8,6699
121
+ better_notion/utils/agents/workspace.py,sha256=T8mP_DNIWhI1-k6UydJINsEJ6kQxQ6_Pa44ul58k088,12370
122
+ better_notion-1.0.1.dist-info/METADATA,sha256=twwihoncbT6FKJ_VmFtC_5g2zXSY12q-YAWFm4hLaqQ,11143
123
+ better_notion-1.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
124
+ better_notion-1.0.1.dist-info/entry_points.txt,sha256=D0bUcP7Z00Zyjxw7r2p29T95UrwioDO0aGDoHe9I6fo,55
125
+ better_notion-1.0.1.dist-info/licenses/LICENSE,sha256=BAdN3JpgMY_y_fWqZSCFSvSbC2mTHP-BKDAzF5FXQAI,1069
126
+ better_notion-1.0.1.dist-info/RECORD,,