pytaskwarrior 2.0.2__tar.gz → 2.0.4__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.
Files changed (34) hide show
  1. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/PKG-INFO +10 -1
  2. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/PYPI_README.md +9 -0
  3. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/README.md +8 -0
  4. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/pyproject.toml +1 -1
  5. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/pytaskwarrior.egg-info/PKG-INFO +10 -1
  6. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/adapters/taskwarrior_adapter.py +54 -0
  7. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/main.py +60 -0
  8. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/services/uda_service.py +35 -2
  9. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/LICENSE +0 -0
  10. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/setup.cfg +0 -0
  11. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/__init__.py +0 -0
  12. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/pytaskwarrior.egg-info/SOURCES.txt +0 -0
  13. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/pytaskwarrior.egg-info/dependency_links.txt +0 -0
  14. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/pytaskwarrior.egg-info/requires.txt +0 -0
  15. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/pytaskwarrior.egg-info/top_level.txt +0 -0
  16. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/__init__.py +0 -0
  17. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/adapters/__init__.py +0 -0
  18. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/config/config_store.py +0 -0
  19. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/config/uda_parser.py +0 -0
  20. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/dto/__init__.py +0 -0
  21. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/dto/annotation_dto.py +0 -0
  22. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/dto/context_dto.py +0 -0
  23. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/dto/task_dto.py +0 -0
  24. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/dto/uda_dto.py +0 -0
  25. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/enums.py +0 -0
  26. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/exceptions.py +0 -0
  27. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/py.typed +0 -0
  28. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/registry/__init__.py +0 -0
  29. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/registry/uda_registry.py +0 -0
  30. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/services/__init__.py +0 -0
  31. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/services/context_service.py +0 -0
  32. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/utils/__init__.py +0 -0
  33. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/utils/conversions.py +0 -0
  34. {pytaskwarrior-2.0.2 → pytaskwarrior-2.0.4}/src/taskwarrior/utils/dto_converter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytaskwarrior
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Taskwarrior wrapper python module
5
5
  Author-email: sznicolas <sznicolas@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -42,6 +42,7 @@ Production-ready with 164 tests (96% coverage), strict type checking, and profes
42
42
  - Type-safe with Pydantic models
43
43
  - Context management
44
44
  - UDA (User Defined Attributes) support
45
+ - Tag discovery and `@` context tags
45
46
  - Recurring tasks and annotations
46
47
  - Consistent exception hierarchy (`TaskNotFound`, `TaskValidationError`, `TaskOperationError`, `TaskConfigurationError`, …)
47
48
 
@@ -80,6 +81,14 @@ for t in tw.get_tasks():
80
81
  tw.done_task(added.uuid)
81
82
  ```
82
83
 
84
+ ## Tags
85
+
86
+ ```python
87
+ tags = tw.get_tags() # virtual tags excluded
88
+ all_tags = tw.get_tags(include_virtual_tags=True)
89
+ context_tags = tw.get_context_tags() # tags starting with "@"
90
+ ```
91
+
83
92
  ## Documentation
84
93
 
85
94
  Full documentation: [GitHub Repository](https://github.com/sznicolas/pytaskwarrior/)
@@ -17,6 +17,7 @@ Production-ready with 164 tests (96% coverage), strict type checking, and profes
17
17
  - Type-safe with Pydantic models
18
18
  - Context management
19
19
  - UDA (User Defined Attributes) support
20
+ - Tag discovery and `@` context tags
20
21
  - Recurring tasks and annotations
21
22
  - Consistent exception hierarchy (`TaskNotFound`, `TaskValidationError`, `TaskOperationError`, `TaskConfigurationError`, …)
22
23
 
@@ -55,6 +56,14 @@ for t in tw.get_tasks():
55
56
  tw.done_task(added.uuid)
56
57
  ```
57
58
 
59
+ ## Tags
60
+
61
+ ```python
62
+ tags = tw.get_tags() # virtual tags excluded
63
+ all_tags = tw.get_tags(include_virtual_tags=True)
64
+ context_tags = tw.get_context_tags() # tags starting with "@"
65
+ ```
66
+
58
67
  ## Documentation
59
68
 
60
69
  Full documentation: [GitHub Repository](https://github.com/sznicolas/pytaskwarrior/)
@@ -17,6 +17,7 @@ A modern Python wrapper for [TaskWarrior](https://taskwarrior.org/) v3.4, the co
17
17
  - **Type-safe** - Pydantic models with full type hints
18
18
  - **Context management** - Define, apply, and switch contexts
19
19
  - **UDA support** - User Defined Attributes
20
+ - **Tag discovery** - List real tags, virtual tags, and `@` context tags
20
21
  - **Recurring tasks** - Full recurrence support
21
22
  - **Annotations** - Add notes to tasks
22
23
  - **Date calculations** - Use TaskWarrior's date expressions
@@ -128,6 +129,13 @@ tw = TaskWarrior(
128
129
  | `stop_task(uuid)` | Stop working on task |
129
130
  | `annotate_task(uuid, annotation)` | Add annotation to task |
130
131
 
132
+ #### Tags Operations
133
+
134
+ | Method | Description |
135
+ |--------|-------------|
136
+ | `get_tags(include_virtual_tags=False)` | List tags, excluding virtual tags by default |
137
+ | `get_context_tags()` | List tags that start with `@` |
138
+
131
139
  #### Context Operations
132
140
 
133
141
  | Method | Description |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pytaskwarrior"
3
- version = "2.0.2"
3
+ version = "2.0.4"
4
4
  description = "Taskwarrior wrapper python module"
5
5
  readme = "PYPI_README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytaskwarrior
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Taskwarrior wrapper python module
5
5
  Author-email: sznicolas <sznicolas@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -42,6 +42,7 @@ Production-ready with 164 tests (96% coverage), strict type checking, and profes
42
42
  - Type-safe with Pydantic models
43
43
  - Context management
44
44
  - UDA (User Defined Attributes) support
45
+ - Tag discovery and `@` context tags
45
46
  - Recurring tasks and annotations
46
47
  - Consistent exception hierarchy (`TaskNotFound`, `TaskValidationError`, `TaskOperationError`, `TaskConfigurationError`, …)
47
48
 
@@ -80,6 +81,14 @@ for t in tw.get_tasks():
80
81
  tw.done_task(added.uuid)
81
82
  ```
82
83
 
84
+ ## Tags
85
+
86
+ ```python
87
+ tags = tw.get_tags() # virtual tags excluded
88
+ all_tags = tw.get_tags(include_virtual_tags=True)
89
+ context_tags = tw.get_context_tags() # tags starting with "@"
90
+ ```
91
+
83
92
  ## Documentation
84
93
 
85
94
  Full documentation: [GitHub Repository](https://github.com/sznicolas/pytaskwarrior/)
@@ -26,6 +26,40 @@ from ..exceptions import (
26
26
 
27
27
  logger = logging.getLogger(__name__)
28
28
 
29
+ TASKWARRIOR_VIRTUAL_TAGS: tuple[str, ...] = (
30
+ "BLOCKED",
31
+ "UNBLOCKED",
32
+ "BLOCKING",
33
+ "DUE",
34
+ "DUETODAY",
35
+ "TODAY",
36
+ "OVERDUE",
37
+ "WEEK",
38
+ "MONTH",
39
+ "QUARTER",
40
+ "YEAR",
41
+ "ACTIVE",
42
+ "SCHEDULED",
43
+ "PARENT",
44
+ "CHILD",
45
+ "UNTIL",
46
+ "WAITING",
47
+ "ANNOTATED",
48
+ "READY",
49
+ "YESTERDAY",
50
+ "TOMORROW",
51
+ "TAGGED",
52
+ "PENDING",
53
+ "COMPLETED",
54
+ "DELETED",
55
+ "UDA",
56
+ "ORPHAN",
57
+ "PRIORITY",
58
+ "PROJECT",
59
+ "LATEST",
60
+ )
61
+ TASKWARRIOR_VIRTUAL_TAG_SET = frozenset(TASKWARRIOR_VIRTUAL_TAGS)
62
+
29
63
 
30
64
  class TaskWarriorAdapter:
31
65
  """Low-level adapter for TaskWarrior CLI commands.
@@ -515,3 +549,23 @@ class TaskWarriorAdapter:
515
549
 
516
550
  projects = [line.strip() for line in result.stdout.split("\n") if line.strip()]
517
551
  return projects
552
+
553
+ def get_tags(self, include_virtual_tags: bool = False) -> list[str]:
554
+ """Get all tags defined in TaskWarrior.
555
+
556
+ Args:
557
+ include_virtual_tags: If ``True``, include TaskWarrior virtual tags
558
+ such as ``TODAY`` and ``READY``.
559
+
560
+ Returns:
561
+ List of tag names.
562
+ """
563
+ result = self.run_task_command(["_tags"])
564
+
565
+ if result.returncode != 0:
566
+ raise TaskWarriorError(f"Failed to get tags: {result.stderr}")
567
+
568
+ tags = [line.strip() for line in result.stdout.splitlines() if line.strip()]
569
+ if include_virtual_tags:
570
+ return list(dict.fromkeys(tags + list(TASKWARRIOR_VIRTUAL_TAGS)))
571
+ return [tag for tag in tags if tag not in TASKWARRIOR_VIRTUAL_TAG_SET]
@@ -554,6 +554,47 @@ class TaskWarrior:
554
554
  """
555
555
  return self.uda_service.registry.get_uda(name)
556
556
 
557
+ def define_uda(self, uda: UdaConfig) -> None:
558
+ """Define a new UDA via the TaskWarrior facade.
559
+
560
+ Delegates to UdaService.define_uda which performs the necessary
561
+ TaskWarrior config writes and registers the UDA in the local registry.
562
+
563
+ Args:
564
+ uda: The UdaConfig describing the UDA to create.
565
+
566
+ Raises:
567
+ TaskOperationError: If creating the UDA via the underlying adapter fails.
568
+ """
569
+ self.uda_service.define_uda(uda)
570
+
571
+ def update_uda(self, uda: UdaConfig) -> None:
572
+ """Update an existing UDA via the TaskWarrior facade.
573
+
574
+ Delegates to UdaService.update_uda.
575
+
576
+ Args:
577
+ uda: The UdaConfig with updated fields.
578
+
579
+ Raises:
580
+ TaskOperationError: If applying the update fails.
581
+ """
582
+ self.uda_service.update_uda(uda)
583
+
584
+ def delete_uda(self, uda: UdaConfig) -> None:
585
+ """Delete a UDA via the TaskWarrior facade.
586
+
587
+ Delegates to UdaService.delete_uda which removes TaskWarrior config
588
+ keys and the UDA from the local registry.
589
+
590
+ Args:
591
+ uda: The UdaConfig identifying the UDA to remove.
592
+
593
+ Raises:
594
+ TaskOperationError: If deletion fails for reasons other than missing keys.
595
+ """
596
+ self.uda_service.delete_uda(uda)
597
+
557
598
  def get_projects(self) -> list[str]:
558
599
  """Get all projects defined in TaskWarrior.
559
600
 
@@ -566,3 +607,22 @@ class TaskWarrior:
566
607
  ['dmc.fil.aretordre', 'dmc.fil.adérouler', 'perso', 'perso.orl', 'pro']
567
608
  """
568
609
  return self.adapter.get_projects()
610
+
611
+ def get_tags(self, include_virtual_tags: bool = False) -> list[str]:
612
+ """Get all tags defined in TaskWarrior.
613
+
614
+ Args:
615
+ include_virtual_tags: If ``True``, include TaskWarrior virtual tags
616
+ such as ``TODAY`` and ``READY``.
617
+
618
+ Returns:
619
+ List of tag names.
620
+ """
621
+ return self.adapter.get_tags(include_virtual_tags=include_virtual_tags)
622
+
623
+ def get_context_tags(self) -> list[str]:
624
+ """Return tags that follow the ``@`` context convention.
625
+
626
+ This is a convenience filter for user-defined tags such as ``@work``.
627
+ """
628
+ return [tag for tag in self.get_tags() if tag.startswith("@")]
@@ -59,6 +59,16 @@ class UdaService:
59
59
 
60
60
  The service executes the required `task config` commands via the adapter
61
61
  and only updates the registry if all commands succeed.
62
+
63
+ Args:
64
+ uda: The UdaConfig describing the UDA to create.
65
+
66
+ Raises:
67
+ TaskOperationError: If any underlying TaskWarrior config command fails.
68
+
69
+ Example:
70
+ >>> uda = UdaConfig(name="sev", uda_type=UdaType.STRING, label="Severity")
71
+ >>> service.define_uda(uda)
62
72
  """
63
73
  # Build commands to define the UDA
64
74
  field_names = uda.__class__.model_fields.keys() - {"name"}
@@ -86,6 +96,12 @@ class UdaService:
86
96
  """Update an existing UDA in TaskWarrior and in the registry.
87
97
 
88
98
  Executes commands via adapter and updates the registry on success.
99
+
100
+ Args:
101
+ uda: The UdaConfig with updated settings to apply.
102
+
103
+ Raises:
104
+ TaskOperationError: If applying the updated configuration fails.
89
105
  """
90
106
  # For now, same as define_uda
91
107
  self.define_uda(uda)
@@ -94,9 +110,26 @@ class UdaService:
94
110
  """Delete a UDA from TaskWarrior and remove it from the registry.
95
111
 
96
112
  Executes `task config <key>` without a value to remove each UDA key.
113
+
114
+ Args:
115
+ uda: The UdaConfig identifying the UDA to remove.
116
+
117
+ Raises:
118
+ TaskOperationError: If an unexpected TaskWarrior error occurs while
119
+ attempting to remove configuration keys (missing keys are tolerated).
97
120
  """
98
- field_names = uda.__class__.model_fields.keys()
99
- for key in field_names:
121
+ # Mirror define_uda: skip 'name' and map internal 'uda_type' -> TaskWarrior 'type'
122
+ field_names = set(uda.__class__.model_fields.keys()) - {"name"}
123
+ keys_to_delete: list[str] = []
124
+
125
+ if "uda_type" in field_names:
126
+ keys_to_delete.append("type")
127
+ field_names.remove("uda_type")
128
+
129
+ # delete remaining fields deterministically
130
+ keys_to_delete.extend(sorted(field_names))
131
+
132
+ for key in keys_to_delete:
100
133
  cmd = ["config", f"uda.{uda.name}.{key}"]
101
134
  result = self.adapter.run_task_command(cmd)
102
135
  if getattr(result, "returncode", 0) != 0:
File without changes
File without changes