flowtask-sdk 0.2.0__tar.gz → 0.5.0__tar.gz
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.
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/PKG-INFO +1 -1
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask/__init__.py +5 -2
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask/client.py +79 -1
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask/models.py +126 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask_sdk.egg-info/PKG-INFO +1 -1
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/pyproject.toml +1 -1
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/README.md +0 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask/exceptions.py +0 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask_sdk.egg-info/SOURCES.txt +0 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask_sdk.egg-info/dependency_links.txt +0 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask_sdk.egg-info/requires.txt +0 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/flowtask_sdk.egg-info/top_level.txt +0 -0
- {flowtask_sdk-0.2.0 → flowtask_sdk-0.5.0}/setup.cfg +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from .client import FlowTask
|
|
2
|
-
from .models import Workspace, Project, Task, Dependency, Tag, Section, MyDayItem, SmartViewCounts
|
|
2
|
+
from .models import Workspace, Project, Task, Dependency, Tag, Section, MyDayItem, SmartViewCounts, WorkspaceMember, Note, NoteFolder
|
|
3
3
|
from .exceptions import FlowTaskError, AuthenticationError, ValidationError, NotFoundError, CycleDetectedError
|
|
4
4
|
|
|
5
|
-
__version__ = "0.
|
|
5
|
+
__version__ = "0.5.0"
|
|
6
6
|
__all__ = [
|
|
7
7
|
"FlowTask",
|
|
8
8
|
"Workspace",
|
|
@@ -13,6 +13,9 @@ __all__ = [
|
|
|
13
13
|
"Section",
|
|
14
14
|
"MyDayItem",
|
|
15
15
|
"SmartViewCounts",
|
|
16
|
+
"WorkspaceMember",
|
|
17
|
+
"Note",
|
|
18
|
+
"NoteFolder",
|
|
16
19
|
"FlowTaskError",
|
|
17
20
|
"AuthenticationError",
|
|
18
21
|
"ValidationError",
|
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
import requests as _requests
|
|
7
7
|
|
|
8
|
-
from .models import Workspace, Project, Task, Dependency, Tag, Section, MyDayItem, SmartViewCounts
|
|
8
|
+
from .models import Workspace, Project, Task, Dependency, Tag, Section, MyDayItem, SmartViewCounts, WorkspaceMember, Note, NoteFolder
|
|
9
9
|
from .exceptions import (
|
|
10
10
|
FlowTaskError,
|
|
11
11
|
AuthenticationError,
|
|
@@ -345,6 +345,33 @@ class FlowTask:
|
|
|
345
345
|
"""Reorder sections by providing IDs in desired order."""
|
|
346
346
|
self._request("PATCH", "/sections/reorder", json={"sectionIds": section_ids})
|
|
347
347
|
|
|
348
|
+
# ─── Sharing ───────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
def invite_to_workspace(self, workspace_id: str, email: str, role: str = "editor") -> WorkspaceMember:
|
|
351
|
+
"""Invite a user to a workspace by email. Returns the member or creates a pending invitation."""
|
|
352
|
+
data = self._request("POST", f"/workspaces/{workspace_id}/members", json={"email": email, "role": role})
|
|
353
|
+
if data.get("type") == "member" and data.get("member"):
|
|
354
|
+
return WorkspaceMember.from_dict(data["member"])
|
|
355
|
+
return WorkspaceMember(id="pending", user_id="", role=role, user_email=email)
|
|
356
|
+
|
|
357
|
+
def list_workspace_members(self, workspace_id: str) -> list[WorkspaceMember]:
|
|
358
|
+
"""List all members of a workspace."""
|
|
359
|
+
data = self._request("GET", f"/workspaces/{workspace_id}/members")
|
|
360
|
+
return [WorkspaceMember.from_dict(m) for m in data.get("members", [])]
|
|
361
|
+
|
|
362
|
+
def update_workspace_member(self, workspace_id: str, member_id: str, role: str) -> WorkspaceMember:
|
|
363
|
+
"""Change a member's role (owner only)."""
|
|
364
|
+
data = self._request("PUT", f"/workspaces/{workspace_id}/members/{member_id}", json={"role": role})
|
|
365
|
+
return WorkspaceMember.from_dict(data)
|
|
366
|
+
|
|
367
|
+
def remove_workspace_member(self, workspace_id: str, member_id: str) -> None:
|
|
368
|
+
"""Remove a member from a workspace (owner only)."""
|
|
369
|
+
self._request("DELETE", f"/workspaces/{workspace_id}/members/{member_id}")
|
|
370
|
+
|
|
371
|
+
def leave_workspace(self, workspace_id: str) -> None:
|
|
372
|
+
"""Leave a workspace (non-owners only)."""
|
|
373
|
+
self._request("DELETE", f"/workspaces/{workspace_id}/members/me")
|
|
374
|
+
|
|
348
375
|
# ─── My Day ────────────────────────────────────────
|
|
349
376
|
|
|
350
377
|
def get_my_day(self, date: Optional[str] = None) -> list[MyDayItem]:
|
|
@@ -406,6 +433,57 @@ class FlowTask:
|
|
|
406
433
|
data = self._request("GET", "/actual-tasks")
|
|
407
434
|
return [Task.from_dict(t, client=self) for t in data]
|
|
408
435
|
|
|
436
|
+
# ─── Notes ─────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
def list_notes(self, workspace_id: str) -> dict:
|
|
439
|
+
"""List all notes and folders in a workspace."""
|
|
440
|
+
data = self._request("GET", f"/workspaces/{workspace_id}/notes")
|
|
441
|
+
folders = [NoteFolder.from_dict(f, client=self) for f in data.get("folders", [])]
|
|
442
|
+
unfoldered = [Note.from_dict(n, client=self) for n in data.get("unfolderedNotes", [])]
|
|
443
|
+
return {"folders": folders, "unfolderedNotes": unfoldered}
|
|
444
|
+
|
|
445
|
+
def create_note(
|
|
446
|
+
self,
|
|
447
|
+
workspace_id: str,
|
|
448
|
+
title: str,
|
|
449
|
+
content: str = "",
|
|
450
|
+
folder_id: Optional[str] = None,
|
|
451
|
+
) -> Note:
|
|
452
|
+
"""Create a note in a workspace. Content is markdown."""
|
|
453
|
+
body: dict = {"title": title, "content": content}
|
|
454
|
+
if folder_id:
|
|
455
|
+
body["folderId"] = folder_id
|
|
456
|
+
data = self._request("POST", f"/workspaces/{workspace_id}/notes", json=body)
|
|
457
|
+
return Note.from_dict(data, client=self)
|
|
458
|
+
|
|
459
|
+
def update_note(self, note_id: str, **kwargs) -> Note:
|
|
460
|
+
"""Update a note. Accepts: title, content, folder_id."""
|
|
461
|
+
key_map = {"folder_id": "folderId"}
|
|
462
|
+
body = {}
|
|
463
|
+
for key, value in kwargs.items():
|
|
464
|
+
api_key = key_map.get(key, key)
|
|
465
|
+
body[api_key] = value
|
|
466
|
+
data = self._request("PUT", f"/notes/{note_id}", json=body)
|
|
467
|
+
return Note.from_dict(data, client=self)
|
|
468
|
+
|
|
469
|
+
def delete_note(self, note_id: str) -> None:
|
|
470
|
+
"""Delete a note."""
|
|
471
|
+
self._request("DELETE", f"/notes/{note_id}")
|
|
472
|
+
|
|
473
|
+
def create_note_folder(self, workspace_id: str, name: str) -> NoteFolder:
|
|
474
|
+
"""Create a note folder in a workspace."""
|
|
475
|
+
data = self._request("POST", f"/workspaces/{workspace_id}/folders", json={"name": name})
|
|
476
|
+
return NoteFolder.from_dict(data, client=self)
|
|
477
|
+
|
|
478
|
+
def update_note_folder(self, folder_id: str, name: str) -> NoteFolder:
|
|
479
|
+
"""Rename a note folder."""
|
|
480
|
+
data = self._request("PUT", f"/folders/{folder_id}", json={"name": name})
|
|
481
|
+
return NoteFolder.from_dict(data, client=self)
|
|
482
|
+
|
|
483
|
+
def delete_note_folder(self, folder_id: str) -> None:
|
|
484
|
+
"""Delete a note folder. Notes become unfoldered."""
|
|
485
|
+
self._request("DELETE", f"/folders/{folder_id}")
|
|
486
|
+
|
|
409
487
|
# ─── Search ────────────────────────────────────────
|
|
410
488
|
|
|
411
489
|
def search(self, query: str) -> list[Task]:
|
|
@@ -145,6 +145,26 @@ class Section:
|
|
|
145
145
|
)
|
|
146
146
|
|
|
147
147
|
|
|
148
|
+
@dataclass
|
|
149
|
+
class WorkspaceMember:
|
|
150
|
+
id: str
|
|
151
|
+
user_id: str
|
|
152
|
+
role: str
|
|
153
|
+
user_email: str = ""
|
|
154
|
+
user_name: Optional[str] = None
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def from_dict(cls, data: dict) -> WorkspaceMember:
|
|
158
|
+
user = data.get("user", {})
|
|
159
|
+
return cls(
|
|
160
|
+
id=data["id"],
|
|
161
|
+
user_id=data.get("userId", ""),
|
|
162
|
+
role=data.get("role", "editor"),
|
|
163
|
+
user_email=user.get("email", ""),
|
|
164
|
+
user_name=user.get("name"),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
148
168
|
@dataclass
|
|
149
169
|
class Workspace:
|
|
150
170
|
id: str
|
|
@@ -153,6 +173,8 @@ class Workspace:
|
|
|
153
173
|
sort_order: int = 0
|
|
154
174
|
project_count: int = 0
|
|
155
175
|
task_count: int = 0
|
|
176
|
+
is_shared: bool = False
|
|
177
|
+
current_user_role: str = "owner"
|
|
156
178
|
created_at: str = ""
|
|
157
179
|
updated_at: str = ""
|
|
158
180
|
_client: Optional[FlowTask] = field(default=None, repr=False)
|
|
@@ -167,6 +189,8 @@ class Workspace:
|
|
|
167
189
|
sort_order=data.get("sortOrder", 0),
|
|
168
190
|
project_count=count.get("projects", 0) if isinstance(count, dict) else 0,
|
|
169
191
|
task_count=count.get("tasks", 0) if isinstance(count, dict) else 0,
|
|
192
|
+
is_shared=data.get("isShared", False),
|
|
193
|
+
current_user_role=data.get("currentUserRole", "owner"),
|
|
170
194
|
created_at=data.get("createdAt", ""),
|
|
171
195
|
updated_at=data.get("updatedAt", ""),
|
|
172
196
|
_client=client,
|
|
@@ -220,6 +244,48 @@ class Workspace:
|
|
|
220
244
|
raise RuntimeError("Workspace not bound to a client")
|
|
221
245
|
self._client.delete_workspace(self.id)
|
|
222
246
|
|
|
247
|
+
def share(self, email: str, role: str = "editor") -> WorkspaceMember:
|
|
248
|
+
"""Invite a user to this workspace by email."""
|
|
249
|
+
if not self._client:
|
|
250
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
251
|
+
return self._client.invite_to_workspace(self.id, email, role)
|
|
252
|
+
|
|
253
|
+
def list_members(self) -> list[WorkspaceMember]:
|
|
254
|
+
"""List all members of this workspace."""
|
|
255
|
+
if not self._client:
|
|
256
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
257
|
+
return self._client.list_workspace_members(self.id)
|
|
258
|
+
|
|
259
|
+
def remove_member(self, member_id: str) -> None:
|
|
260
|
+
"""Remove a member from this workspace."""
|
|
261
|
+
if not self._client:
|
|
262
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
263
|
+
self._client.remove_workspace_member(self.id, member_id)
|
|
264
|
+
|
|
265
|
+
def leave(self) -> None:
|
|
266
|
+
"""Leave this workspace (non-owners only)."""
|
|
267
|
+
if not self._client:
|
|
268
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
269
|
+
self._client.leave_workspace(self.id)
|
|
270
|
+
|
|
271
|
+
def list_notes(self) -> dict:
|
|
272
|
+
"""List all notes and folders in this workspace."""
|
|
273
|
+
if not self._client:
|
|
274
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
275
|
+
return self._client.list_notes(self.id)
|
|
276
|
+
|
|
277
|
+
def create_note(self, title: str, content: str = "", folder_id: Optional[str] = None) -> Note:
|
|
278
|
+
"""Create a note in this workspace."""
|
|
279
|
+
if not self._client:
|
|
280
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
281
|
+
return self._client.create_note(self.id, title, content, folder_id)
|
|
282
|
+
|
|
283
|
+
def create_note_folder(self, name: str) -> NoteFolder:
|
|
284
|
+
"""Create a note folder in this workspace."""
|
|
285
|
+
if not self._client:
|
|
286
|
+
raise RuntimeError("Workspace not bound to a client")
|
|
287
|
+
return self._client.create_note_folder(self.id, name)
|
|
288
|
+
|
|
223
289
|
|
|
224
290
|
@dataclass
|
|
225
291
|
class Project:
|
|
@@ -335,3 +401,63 @@ class SmartViewCounts:
|
|
|
335
401
|
delegated=data.get("delegated", 0),
|
|
336
402
|
completed=data.get("completed", 0),
|
|
337
403
|
)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@dataclass
|
|
407
|
+
class NoteFolder:
|
|
408
|
+
id: str
|
|
409
|
+
name: str
|
|
410
|
+
sort_order: int = 0
|
|
411
|
+
workspace_id: str = ""
|
|
412
|
+
notes: list[Note] = field(default_factory=list)
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def from_dict(cls, data: dict, client: Optional[FlowTask] = None) -> NoteFolder:
|
|
416
|
+
return cls(
|
|
417
|
+
id=data["id"],
|
|
418
|
+
name=data["name"],
|
|
419
|
+
sort_order=data.get("sortOrder", 0),
|
|
420
|
+
workspace_id=data.get("workspaceId", ""),
|
|
421
|
+
notes=[Note.from_dict(n, client=client) for n in data.get("notes", [])],
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@dataclass
|
|
426
|
+
class Note:
|
|
427
|
+
id: str
|
|
428
|
+
title: str
|
|
429
|
+
content: str = ""
|
|
430
|
+
sort_order: int = 0
|
|
431
|
+
folder_id: Optional[str] = None
|
|
432
|
+
author_id: str = ""
|
|
433
|
+
workspace_id: str = ""
|
|
434
|
+
created_at: str = ""
|
|
435
|
+
updated_at: str = ""
|
|
436
|
+
_client: Optional[FlowTask] = field(default=None, repr=False)
|
|
437
|
+
|
|
438
|
+
@classmethod
|
|
439
|
+
def from_dict(cls, data: dict, client: Optional[FlowTask] = None) -> Note:
|
|
440
|
+
return cls(
|
|
441
|
+
id=data["id"],
|
|
442
|
+
title=data["title"],
|
|
443
|
+
content=data.get("content", ""),
|
|
444
|
+
sort_order=data.get("sortOrder", 0),
|
|
445
|
+
folder_id=data.get("folderId"),
|
|
446
|
+
author_id=data.get("authorId", ""),
|
|
447
|
+
workspace_id=data.get("workspaceId", ""),
|
|
448
|
+
created_at=data.get("createdAt", ""),
|
|
449
|
+
updated_at=data.get("updatedAt", ""),
|
|
450
|
+
_client=client,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
def update(self, **kwargs) -> Note:
|
|
454
|
+
"""Update this note. Accepts: title, content, folder_id."""
|
|
455
|
+
if not self._client:
|
|
456
|
+
raise RuntimeError("Note not bound to a client")
|
|
457
|
+
return self._client.update_note(self.id, **kwargs)
|
|
458
|
+
|
|
459
|
+
def delete(self) -> None:
|
|
460
|
+
"""Delete this note."""
|
|
461
|
+
if not self._client:
|
|
462
|
+
raise RuntimeError("Note not bound to a client")
|
|
463
|
+
self._client.delete_note(self.id)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|