issues-fs 0.3.0__py3-none-any.whl → 0.4.0__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 (77) hide show
  1. issues_fs/issues/Issue__Path__Config.py +25 -0
  2. issues_fs/issues/__init__.py +3 -0
  3. issues_fs/issues/graph_services/Comments__Service.py +235 -0
  4. issues_fs/issues/graph_services/Graph__Repository.py +307 -0
  5. issues_fs/issues/graph_services/Graph__Repository__Factory.py +106 -0
  6. issues_fs/issues/graph_services/Link__Service.py +227 -0
  7. issues_fs/issues/graph_services/Node__Service.py +392 -0
  8. issues_fs/issues/graph_services/Type__Service.py +209 -0
  9. issues_fs/issues/graph_services/__init__.py +3 -0
  10. issues_fs/issues/phase_1/Issue__Children__Service.py +340 -0
  11. issues_fs/issues/phase_1/Root__Issue__Service.py +137 -0
  12. issues_fs/issues/phase_1/Root__Selection__Service.py +310 -0
  13. issues_fs/issues/phase_1/__init__.py +3 -0
  14. issues_fs/issues/status/Git__Status__Service.py +115 -0
  15. issues_fs/issues/status/Index__Status__Service.py +131 -0
  16. issues_fs/issues/status/Server__Status__Service.py +113 -0
  17. issues_fs/issues/status/Storage__Status__Service.py +75 -0
  18. issues_fs/issues/status/Types__Status__Service.py +75 -0
  19. issues_fs/issues/status/__init__.py +3 -0
  20. issues_fs/issues/storage/Path__Handler__Graph_Node.py +185 -0
  21. issues_fs/issues/storage/Path__Handler__Issues.py +57 -0
  22. issues_fs/issues/storage/__init__.py +3 -0
  23. issues_fs/schemas/__init__.py +0 -0
  24. issues_fs/schemas/enums/Enum__Comment__Author.py +10 -0
  25. issues_fs/schemas/enums/Enum__Graph__Storage__Backend.py +8 -0
  26. issues_fs/schemas/enums/Enum__Issue__Status.py +13 -0
  27. issues_fs/schemas/enums/__init__.py +3 -0
  28. issues_fs/schemas/graph/Safe_Str__Graph_Types.py +63 -0
  29. issues_fs/schemas/graph/Schema__Global__Index.py +16 -0
  30. issues_fs/schemas/graph/Schema__Graph__Link.py +8 -0
  31. issues_fs/schemas/graph/Schema__Graph__Node.py +10 -0
  32. issues_fs/schemas/graph/Schema__Graph__Response.py +21 -0
  33. issues_fs/schemas/graph/Schema__Link__Create__Request.py +12 -0
  34. issues_fs/schemas/graph/Schema__Link__Create__Response.py +15 -0
  35. issues_fs/schemas/graph/Schema__Link__Delete__Response.py +16 -0
  36. issues_fs/schemas/graph/Schema__Link__List__Response.py +15 -0
  37. issues_fs/schemas/graph/Schema__Link__Type.py +20 -0
  38. issues_fs/schemas/graph/Schema__Node.py +43 -0
  39. issues_fs/schemas/graph/Schema__Node__Create__Request.py +19 -0
  40. issues_fs/schemas/graph/Schema__Node__Create__Response.py +14 -0
  41. issues_fs/schemas/graph/Schema__Node__Delete__Response.py +15 -0
  42. issues_fs/schemas/graph/Schema__Node__Link.py +17 -0
  43. issues_fs/schemas/graph/Schema__Node__List__Response.py +16 -0
  44. issues_fs/schemas/graph/Schema__Node__Summary.py +15 -0
  45. issues_fs/schemas/graph/Schema__Node__Type.py +28 -0
  46. issues_fs/schemas/graph/Schema__Node__Update__Request.py +18 -0
  47. issues_fs/schemas/graph/Schema__Node__Update__Response.py +14 -0
  48. issues_fs/schemas/graph/Schema__Property__Definition.py +27 -0
  49. issues_fs/schemas/graph/Schema__Type__Index.py +16 -0
  50. issues_fs/schemas/graph/Schema__Type__Summary.py +13 -0
  51. issues_fs/schemas/graph/__init__.py +0 -0
  52. issues_fs/schemas/identifiers/Comment_Id.py +5 -0
  53. issues_fs/schemas/identifiers/Issue_Id.py +13 -0
  54. issues_fs/schemas/identifiers/__init__.py +0 -0
  55. issues_fs/schemas/issues/Schema__Comment.py +61 -0
  56. issues_fs/schemas/issues/__init__.py +0 -0
  57. issues_fs/schemas/issues/phase_1/Schema__Issue__Children.py +85 -0
  58. issues_fs/schemas/issues/phase_1/Schema__Root.py +60 -0
  59. issues_fs/schemas/issues/phase_1/__init__.py +0 -0
  60. issues_fs/schemas/safe_str/Safe_Str__Hex_Color.py +10 -0
  61. issues_fs/schemas/safe_str/Safe_Str__Issue_Id.py +14 -0
  62. issues_fs/schemas/safe_str/Safe_Str__Issue__Node__Description.py +15 -0
  63. issues_fs/schemas/safe_str/Safe_Str__Label_Name.py +9 -0
  64. issues_fs/schemas/safe_str/__init__.py +0 -0
  65. issues_fs/schemas/status/Schema__API__Info.py +15 -0
  66. issues_fs/schemas/status/Schema__Git__Status.py +21 -0
  67. issues_fs/schemas/status/Schema__Index__Status.py +22 -0
  68. issues_fs/schemas/status/Schema__Server__Status.py +28 -0
  69. issues_fs/schemas/status/Schema__Storage__Status.py +23 -0
  70. issues_fs/schemas/status/Schema__Types__Status.py +30 -0
  71. issues_fs/schemas/status/__init__.py +0 -0
  72. issues_fs/version +1 -1
  73. {issues_fs-0.3.0.dist-info → issues_fs-0.4.0.dist-info}/METADATA +2 -1
  74. issues_fs-0.4.0.dist-info/RECORD +79 -0
  75. issues_fs-0.3.0.dist-info/RECORD +0 -8
  76. {issues_fs-0.3.0.dist-info → issues_fs-0.4.0.dist-info}/LICENSE +0 -0
  77. {issues_fs-0.3.0.dist-info → issues_fs-0.4.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,113 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Server__Status__Service - Main server status orchestrator
3
+ # Aggregates all status components into a comprehensive response
4
+ # ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import sys
7
+ from datetime import datetime
8
+ from osbot_utils.type_safe.Type_Safe import Type_Safe
9
+ from osbot_utils.type_safe.primitives.domains.common.safe_str.Safe_Str__Text import Safe_Str__Text
10
+ from issues_fs.schemas.status.Schema__API__Info import Schema__API__Info
11
+ from issues_fs.schemas.status.Schema__Server__Status import Schema__Server__Status
12
+ from issues_fs.schemas.status.Schema__Server__Status import Schema__Server__Status__Response
13
+ from issues_fs.issues.status.Git__Status__Service import Git__Status__Service
14
+ from issues_fs.issues.status.Index__Status__Service import Index__Status__Service
15
+ from issues_fs.issues.status.Storage__Status__Service import Storage__Status__Service
16
+ from issues_fs.issues.status.Types__Status__Service import Types__Status__Service
17
+ from issues_fs.utils.Version import version__issues_fs
18
+
19
+ # ═══════════════════════════════════════════════════════════════════════════════
20
+ # Configuration Constants
21
+ # ═══════════════════════════════════════════════════════════════════════════════
22
+
23
+ API_VERSION = '0.2.4'
24
+ API_NAME = 'Graph Issue Tracking API'
25
+ BUILD_DATE = 'NA' # see if we can get this from the .git folder
26
+ ENVIRONMENT = 'development'
27
+
28
+
29
+ class Server__Status__Service(Type_Safe): # Main status service
30
+ storage_service : Storage__Status__Service = None # Storage status
31
+ git_service : Git__Status__Service = None # Git status
32
+ types_service : Types__Status__Service = None # Types status
33
+ index_service : Index__Status__Service = None # Index status
34
+
35
+ # ═══════════════════════════════════════════════════════════════════════════════
36
+ # Main Status Method
37
+ # ═══════════════════════════════════════════════════════════════════════════════
38
+
39
+ def get_full_status(self) -> Schema__Server__Status__Response: # Get comprehensive status
40
+
41
+ timestamp = self._get_timestamp()
42
+ api_info = self._get_api_info()
43
+ storage_status = self._get_storage_status()
44
+ git_status = self._get_git_status()
45
+ types_status = self._get_types_status()
46
+ index_status = self._get_index_status()
47
+
48
+ status = Schema__Server__Status(timestamp = Safe_Str__Text(timestamp) ,
49
+ api = api_info ,
50
+ storage = storage_status ,
51
+ types = types_status ,
52
+ index = index_status ,
53
+ git = git_status )
54
+
55
+ return Schema__Server__Status__Response(success = True ,
56
+ status = status )
57
+
58
+ # except Exception as e:
59
+ # return Schema__Server__Status__Response(success = False ,
60
+ # message = Safe_Str__Text(f'Error: {str(e)}'))
61
+
62
+ # ═══════════════════════════════════════════════════════════════════════════════
63
+ # Individual Status Getters
64
+ # ═══════════════════════════════════════════════════════════════════════════════
65
+
66
+ def get_api_info(self) -> Schema__API__Info: # Public API info getter
67
+ return self._get_api_info()
68
+
69
+ def get_storage_status(self): # Public storage status
70
+ return self._get_storage_status()
71
+
72
+ def get_git_status(self): # Public git status
73
+ return self._get_git_status()
74
+
75
+ def get_types_status(self): # Public types status
76
+ return self._get_types_status()
77
+
78
+ def get_index_status(self): # Public index status
79
+ return self._get_index_status()
80
+
81
+ # ═══════════════════════════════════════════════════════════════════════════════
82
+ # Private Status Methods
83
+ # ═══════════════════════════════════════════════════════════════════════════════
84
+
85
+ def _get_timestamp(self) -> str: # Get current timestamp
86
+ return datetime.utcnow().isoformat() + 'Z'
87
+
88
+ def _get_api_info(self) -> Schema__API__Info: # Build API info
89
+ return Schema__API__Info(version = version__issues_fs ,
90
+ build_date = Safe_Str__Text(BUILD_DATE) ,
91
+ environment = Safe_Str__Text(ENVIRONMENT) ,
92
+ python_version = Safe_Str__Text(sys.version.split()[0]) ,
93
+ api_name = Safe_Str__Text(API_NAME) )
94
+
95
+ def _get_storage_status(self): # Get storage status
96
+ if self.storage_service:
97
+ return self.storage_service.get_status()
98
+ return None
99
+
100
+ def _get_git_status(self): # Get git status
101
+ if self.git_service:
102
+ return self.git_service.get_status()
103
+ return None
104
+
105
+ def _get_types_status(self): # Get types status
106
+ if self.types_service:
107
+ return self.types_service.get_status()
108
+ return None
109
+
110
+ def _get_index_status(self): # Get index status
111
+ if self.index_service:
112
+ return self.index_service.get_status()
113
+ return None
@@ -0,0 +1,75 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Storage__Status__Service - Storage backend introspection
3
+ # Inspects Memory-FS configuration and provides detailed status
4
+ # ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ from osbot_utils.type_safe.Type_Safe import Type_Safe
7
+ from osbot_utils.type_safe.primitives.domains.common.safe_str.Safe_Str__Text import Safe_Str__Text
8
+ from memory_fs.storage_fs.Storage_FS import Storage_FS
9
+ from issues_fs.schemas.status.Schema__Storage__Status import Schema__Storage__Status
10
+
11
+
12
+ class Storage__Status__Service(Type_Safe): # Storage status service
13
+ storage_fs : Storage_FS = None # Storage_FS instance
14
+
15
+ # ═══════════════════════════════════════════════════════════════════════════════
16
+ # Main Status Method
17
+ # ═══════════════════════════════════════════════════════════════════════════════
18
+
19
+ def get_status(self) -> Schema__Storage__Status: # Get storage status
20
+ if self.storage_fs is None:
21
+ return Schema__Storage__Status(backend_type = Safe_Str__Text('not_configured'),
22
+ is_connected = False )
23
+
24
+ backend_type = type(self.storage_fs).__name__
25
+
26
+ return Schema__Storage__Status(backend_type = backend_type ,
27
+ root_path = self._get_root_path (),
28
+ is_connected = self._check_connection(),
29
+ is_writable = self._check_writable (),
30
+ file_count = self._count_files ())
31
+
32
+ def _get_root_path(self) -> str: # Get configured root path
33
+
34
+ path_attrs = ['root_path', 'db_path', 'zip_path', 'bucket', 'base_path']
35
+ for attr in path_attrs:
36
+ if hasattr(self.storage_fs, attr):
37
+ value = getattr(self.storage_fs, attr, None)
38
+ if value:
39
+ return str(value)
40
+ return ''
41
+
42
+ # ═══════════════════════════════════════════════════════════════════════════════
43
+ # Connectivity Methods
44
+ # ═══════════════════════════════════════════════════════════════════════════════
45
+
46
+ def _check_connection(self) -> bool: # Test read connectivity
47
+ self.storage_fs.file__exists('__connectivity_test__')
48
+ return True
49
+
50
+ def _check_writable(self) -> bool: # Test write connectivity
51
+ with self.storage_fs as _:
52
+ test_path = '__write_test_temp__'
53
+ test_data = b'test'
54
+
55
+ if _.file__save(test_path, test_data):
56
+ if _.file__delete(test_path):
57
+ return True
58
+ return False
59
+
60
+ # ═══════════════════════════════════════════════════════════════════════════════
61
+ # Statistics Methods
62
+ # ═══════════════════════════════════════════════════════════════════════════════
63
+
64
+ def _count_files(self) -> int: # Count stored files
65
+ return len(self.storage_fs.files__paths())
66
+
67
+ def _count_folders(self) -> int: # Count folders
68
+ for method_name in ['folders__all', 'folders', 'list_folders']: # todo: find a better way to do this
69
+ if hasattr(self.storage_fs, method_name):
70
+ method = getattr(self.storage_fs, method_name)
71
+ if callable(method):
72
+ result = method()
73
+ if result:
74
+ return len(result)
75
+ return 0
@@ -0,0 +1,75 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Types__Status__Service - Node and link type configuration status
3
+ # Reports on configured node types and link types
4
+ # ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ from typing import List, Optional
7
+ from osbot_utils.type_safe.Type_Safe import Type_Safe
8
+ from osbot_utils.type_safe.primitives.core.Safe_UInt import Safe_UInt
9
+ from osbot_utils.type_safe.primitives.domains.common.safe_str.Safe_Str__Text import Safe_Str__Text
10
+ from osbot_utils.type_safe.type_safe_core.decorators.type_safe import type_safe
11
+
12
+ from issues_fs.schemas.status.Schema__Types__Status import Schema__Types__Status, Schema__Node__Type__Summary, Schema__Link__Type__Summary
13
+ from issues_fs.issues.graph_services.Type__Service import Type__Service
14
+
15
+
16
+ class Types__Status__Service(Type_Safe): # Types status service
17
+ type_service : Type__Service = None # Type__Service instance
18
+
19
+ # ═══════════════════════════════════════════════════════════════════════════════
20
+ # Main Status Method
21
+ # ═══════════════════════════════════════════════════════════════════════════════
22
+
23
+ def get_status(self) -> Schema__Types__Status: # Get types status
24
+ if self.type_service is None:
25
+ return Schema__Types__Status(is_initialized = False )
26
+
27
+ node_types = self._get_node_type_summaries()
28
+ link_types = self._get_link_type_summaries()
29
+ is_init = len(node_types) > 0 or len(link_types) > 0
30
+
31
+ return Schema__Types__Status(is_initialized = is_init ,
32
+ node_type_count = Safe_UInt(len(node_types)) ,
33
+ link_type_count = Safe_UInt(len(link_types)) ,
34
+ node_types = node_types ,
35
+ link_types = link_types )
36
+
37
+ # ═══════════════════════════════════════════════════════════════════════════════
38
+ # Node Types
39
+ # ═══════════════════════════════════════════════════════════════════════════════
40
+
41
+ @type_safe
42
+ def _get_node_type_summaries(self) -> List[Schema__Node__Type__Summary]: # Get node type summaries
43
+ summaries = []
44
+
45
+ node_types = self.type_service.list_node_types()
46
+ if node_types:
47
+ for note_type in node_types:
48
+ status_count = len(note_type.statuses)
49
+ summary = Schema__Node__Type__Summary(name = note_type.name ,
50
+ display_name = note_type.display_name,
51
+ color = note_type.color ,
52
+ status_count = status_count )
53
+ summaries.append(summary)
54
+
55
+
56
+ return summaries
57
+
58
+ # ═══════════════════════════════════════════════════════════════════════════════
59
+ # Link Types
60
+ # ═══════════════════════════════════════════════════════════════════════════════
61
+
62
+ @type_safe
63
+ def _get_link_type_summaries(self) -> List[Schema__Link__Type__Summary]: # Get link type summaries
64
+ summaries = []
65
+
66
+
67
+ link_types = self.type_service.list_link_types()
68
+ if link_types:
69
+ for link_type in link_types:
70
+
71
+ summary = Schema__Link__Type__Summary(verb = link_type.verb ,
72
+ inverse_verb = link_type.inverse_verb ,
73
+ is_symmetric = link_type.verb == link_type.inverse_verb ) # todo: review the use of this, and if we need this, why we are mapping is_symmetric in Schema__Link__Type__Summary and not in Schema__Link__Type
74
+ summaries.append(summary)
75
+ return summaries
@@ -0,0 +1,3 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Issue tracking services package
3
+ # ═══════════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,185 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Path__Handler__Graph_Node - Path generation for graph-based issue tracking
3
+ # Phase 1: Added dual file support (issue.json preferred, node.json fallback)
4
+ #
5
+ # Storage structure:
6
+ # data/{node_type}/{Label}/issue.json <- NEW: Preferred file
7
+ # data/{node_type}/{Label}/node.json <- LEGACY: Read-only fallback
8
+ # data/{node_type}/{Label}/attachments/{filename}
9
+ # data/{node_type}/_index.json
10
+ # config/node-types.json
11
+ # config/link-types.json
12
+ # _index.json
13
+ # issue.json <- NEW: Root issue (optional)
14
+ # ═══════════════════════════════════════════════════════════════════════════════
15
+
16
+ from osbot_utils.type_safe.Type_Safe import Type_Safe
17
+ from osbot_utils.type_safe.type_safe_core.decorators.type_safe import type_safe
18
+ from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Path import Safe_Str__File__Path
19
+ from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Name import Safe_Str__File__Name
20
+ from issues_fs.schemas.graph.Safe_Str__Graph_Types import Safe_Str__Node_Type, Safe_Str__Node_Label
21
+
22
+
23
+ # ═══════════════════════════════════════════════════════════════════════════════
24
+ # File Name Constants
25
+ # ═══════════════════════════════════════════════════════════════════════════════
26
+
27
+ FILE_NAME__ISSUE_JSON = 'issue.json' # NEW: Preferred issue data file
28
+ FILE_NAME__NODE_JSON = 'node.json' # LEGACY: Fallback for backward compat
29
+
30
+ # todo: quite a number of raw primitives used below (which need to type safe primitives)
31
+
32
+ class Path__Handler__Graph_Node(Type_Safe): # Path handler for graph nodes
33
+ base_path : Safe_Str__File__Path = '.issues' # Root directory for issues (empty or '.' for local disk)
34
+
35
+ def path_prefix(self) -> str: # Get prefix for paths (empty or base_path/)
36
+ if self.base_path and str(self.base_path) != '.':
37
+ return f"{self.base_path}/"
38
+ return ""
39
+
40
+ def has_base_path(self) -> bool: # Check if base_path is set (not empty or '.')
41
+ return bool(self.base_path) and str(self.base_path) != '.'
42
+
43
+ # ═══════════════════════════════════════════════════════════════════════════════
44
+ # Issue File Paths (Phase 1: Dual File Support)
45
+ # ═══════════════════════════════════════════════════════════════════════════════
46
+
47
+ @type_safe
48
+ def path_for_issue_json(self , # Path to issue.json (preferred)
49
+ node_type : Safe_Str__Node_Type ,
50
+ label : Safe_Str__Node_Label
51
+ ) -> str:
52
+ if self.has_base_path():
53
+ return f"{self.base_path}/data/{node_type}/{label}/{FILE_NAME__ISSUE_JSON}"
54
+ return f"data/{node_type}/{label}/{FILE_NAME__ISSUE_JSON}"
55
+
56
+ @type_safe
57
+ def path_for_node_json(self , # Path to node.json (legacy)
58
+ node_type : Safe_Str__Node_Type ,
59
+ label : Safe_Str__Node_Label
60
+ ) -> str:
61
+ if self.has_base_path():
62
+ return f"{self.base_path}/data/{node_type}/{label}/{FILE_NAME__NODE_JSON}"
63
+ return f"data/{node_type}/{label}/{FILE_NAME__NODE_JSON}"
64
+
65
+ @type_safe
66
+ def path_for_node(self , # DEPRECATED: Use path_for_issue_json
67
+ node_type : Safe_Str__Node_Type , # Kept for backward compatibility
68
+ label : Safe_Str__Node_Label
69
+ ) -> str:
70
+ return self.path_for_node_json(node_type, label) # Returns legacy path
71
+
72
+ @type_safe
73
+ def path_for_node_folder(self , # Path to node folder
74
+ node_type : Safe_Str__Node_Type ,
75
+ label : Safe_Str__Node_Label
76
+ ) -> str:
77
+ if self.has_base_path():
78
+ return f"{self.base_path}/data/{node_type}/{label}"
79
+ return f"data/{node_type}/{label}"
80
+
81
+ # ═══════════════════════════════════════════════════════════════════════════════
82
+ # Root Issue Path (Phase 1: Root issue support)
83
+ # ═══════════════════════════════════════════════════════════════════════════════
84
+
85
+ def path_for_root_issue(self) -> str: # Path to root issue.json
86
+ if self.has_base_path():
87
+ return f"{self.base_path}/{FILE_NAME__ISSUE_JSON}"
88
+ return FILE_NAME__ISSUE_JSON
89
+
90
+ # ═══════════════════════════════════════════════════════════════════════════════
91
+ # Child Issues Folder (Phase 1: Hierarchical structure)
92
+ # ═══════════════════════════════════════════════════════════════════════════════
93
+
94
+ @type_safe
95
+ def path_for_issues_folder(self , # Path to issues/ subfolder
96
+ node_type : Safe_Str__Node_Type ,
97
+ label : Safe_Str__Node_Label
98
+ ) -> str:
99
+ if self.has_base_path():
100
+ return f"{self.base_path}/data/{node_type}/{label}/issues"
101
+ return f"data/{node_type}/{label}/issues"
102
+
103
+ # ═══════════════════════════════════════════════════════════════════════════════
104
+ # Attachment Paths
105
+ # ═══════════════════════════════════════════════════════════════════════════════
106
+
107
+ @type_safe
108
+ def path_for_attachment(self , # Path to attachment file
109
+ node_type : Safe_Str__Node_Type ,
110
+ label : Safe_Str__Node_Label ,
111
+ filename : Safe_Str__File__Name
112
+ ) -> str:
113
+ if self.has_base_path():
114
+ return f"{self.base_path}/data/{node_type}/{label}/attachments/{filename}"
115
+ return f"data/{node_type}/{label}/attachments/{filename}"
116
+
117
+ @type_safe
118
+ def path_for_attachments_folder(self , # Path to attachments folder
119
+ node_type : Safe_Str__Node_Type ,
120
+ label : Safe_Str__Node_Label
121
+ ) -> str:
122
+ if self.has_base_path():
123
+ return f"{self.base_path}/data/{node_type}/{label}/attachments"
124
+ return f"data/{node_type}/{label}/attachments"
125
+
126
+ # ═══════════════════════════════════════════════════════════════════════════════
127
+ # Index Paths
128
+ # ═══════════════════════════════════════════════════════════════════════════════
129
+
130
+ @type_safe
131
+ def path_for_type_index(self , # Path to per-type index
132
+ node_type : Safe_Str__Node_Type
133
+ ) -> str:
134
+ if self.has_base_path():
135
+ return f"{self.base_path}/data/{node_type}/_index.json"
136
+ return f"data/{node_type}/_index.json"
137
+
138
+ def path_for_global_index(self) -> str: # Path to global index
139
+ if self.has_base_path():
140
+ return f"{self.base_path}/_index.json"
141
+ return "_index.json"
142
+
143
+ @type_safe
144
+ def path_for_type_folder(self , # Path to type folder
145
+ node_type : Safe_Str__Node_Type
146
+ ) -> str:
147
+ if self.has_base_path():
148
+ return f"{self.base_path}/data/{node_type}"
149
+ return f"data/{node_type}"
150
+
151
+ # ═══════════════════════════════════════════════════════════════════════════════
152
+ # Config Paths
153
+ # ═══════════════════════════════════════════════════════════════════════════════
154
+
155
+ def path_for_node_types(self) -> str: # Path to node-types.json
156
+ if self.has_base_path():
157
+ return f"{self.base_path}/config/node-types.json"
158
+ return "config/node-types.json"
159
+
160
+ def path_for_link_types(self) -> str: # Path to link-types.json
161
+ if self.has_base_path():
162
+ return f"{self.base_path}/config/link-types.json"
163
+ return "config/link-types.json"
164
+
165
+ def path_for_settings(self) -> str: # Path to settings.json
166
+ if self.has_base_path():
167
+ return f"{self.base_path}/config/settings.json"
168
+ return "config/settings.json"
169
+
170
+ def path_for_config_folder(self) -> str: # Path to config folder
171
+ if self.has_base_path():
172
+ return f"{self.base_path}/config"
173
+ return "config"
174
+
175
+ # ═══════════════════════════════════════════════════════════════════════════════
176
+ # Label Generation
177
+ # ═══════════════════════════════════════════════════════════════════════════════
178
+
179
+ @type_safe
180
+ def label_from_type_and_index(self , # Generate label from type + index
181
+ node_type : Safe_Str__Node_Type , # e.g., "bug"
182
+ node_index : int # e.g., 27
183
+ ) -> Safe_Str__Node_Label:
184
+ display_type = node_type.capitalize() # "bug" → "Bug"
185
+ return Safe_Str__Node_Label(f"{display_type}-{node_index}") # "Bug-27"
@@ -0,0 +1,57 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Path__Handler__Issues - Custom path handler for issue tracking file structure
3
+ # Generates paths like: {Issue-NNN}.json, issues.json, labels.json
4
+ # ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+
7
+ from osbot_utils.utils.Files import path_combine_safe
8
+ from osbot_utils.type_safe.Type_Safe import Type_Safe
9
+ from osbot_utils.type_safe.type_safe_core.decorators.type_safe import type_safe
10
+ from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Name import Safe_Str__File__Name
11
+ from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Path import Safe_Str__File__Path
12
+ from issues_fs.schemas.safe_str.Safe_Str__Issue_Id import Safe_Str__Issue_Id
13
+
14
+
15
+ class Path__Handler__Issues(Type_Safe): # Path handler for issues
16
+ base_path : Safe_Str__File__Path # Optional prefix (empty for root)
17
+
18
+ # ═══════════════════════════════════════════════════════════════════════════════
19
+ # Issue Paths
20
+ # ═══════════════════════════════════════════════════════════════════════════════
21
+
22
+ @type_safe
23
+ def path_for_issue(self,
24
+ issue_id: Safe_Str__Issue_Id
25
+ ) -> Safe_Str__File__Path: # Path to issue JSON file
26
+ if self.base_path:
27
+ return path_combine_safe(self.base_path, f"{issue_id}.json")
28
+ return f"{issue_id}.json"
29
+
30
+ @type_safe
31
+ def path_for_issues_index(self) -> Safe_Str__File__Path: # Path to issues.json index
32
+ if self.base_path:
33
+ return f"{self.base_path}/issues.json"
34
+ return "issues.json"
35
+
36
+ # ═══════════════════════════════════════════════════════════════════════════════
37
+ # Label Paths
38
+ # ═══════════════════════════════════════════════════════════════════════════════
39
+
40
+ @type_safe
41
+ def path_for_labels(self) -> Safe_Str__File__Path: # Path to labels.json
42
+ if self.base_path:
43
+ return path_combine_safe(self.base_path, "labels.json")
44
+ return "labels.json"
45
+
46
+ # ═══════════════════════════════════════════════════════════════════════════════
47
+ # Attachment Paths (for future use)
48
+ # ═══════════════════════════════════════════════════════════════════════════════
49
+
50
+ @type_safe
51
+ def path_for_attachment(self , # Path to attachment file
52
+ issue_id : Safe_Str__Issue_Id ,
53
+ filename : Safe_Str__File__Name
54
+ ) -> Safe_Str__File__Path:
55
+ if self.base_path:
56
+ return path_combine_safe(self.base_path,f"attachments/{issue_id}/{filename}")
57
+ return f"attachments/{issue_id}/{filename}"
@@ -0,0 +1,3 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Storage package - Memory-FS based storage abstraction for issue tracking
3
+ # ═══════════════════════════════════════════════════════════════════════════════
File without changes
@@ -0,0 +1,10 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Enum__Comment__Author - Comment author enumeration
3
+ # ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class Enum__Comment__Author(str, Enum): # Comment author values
9
+ HUMAN = "human"
10
+ CLAUDE_CODE = "claude-code"
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Enum__Graph__Storage__Backend(str, Enum): # Available storage backends
5
+ MEMORY = "memory" # In-memory (tests)
6
+ LOCAL_DISK = "local_disk" # Local file system
7
+ SQLITE = "sqlite" # SQLite database
8
+ ZIP = "zip" # ZIP archive
@@ -0,0 +1,13 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Enum__Issue__Status - Issue status enumeration
3
+ # ═══════════════════════════════════════════════════════════════════════════════
4
+
5
+ from enum import Enum
6
+
7
+
8
+ class Enum__Issue__Status(str, Enum): # Issue status values
9
+ BACKLOG = "backlog"
10
+ TODO = "todo"
11
+ IN_PROGRESS = "in-progress"
12
+ REVIEW = "review"
13
+ DONE = "done"
@@ -0,0 +1,3 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Issue tracking schemas package
3
+ # ═══════════════════════════════════════════════════════════════════════════════
@@ -0,0 +1,63 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # Safe String Types for Graph-Based Issue Tracking
3
+ # Type-safe primitives for node types, labels, statuses, and verbs
4
+ # ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import re
7
+ from osbot_utils.type_safe.primitives.core.Safe_Str import Safe_Str
8
+ from osbot_utils.type_safe.primitives.core.enums.Enum__Safe_Str__Regex_Mode import Enum__Safe_Str__Regex_Mode
9
+
10
+
11
+ # ═══════════════════════════════════════════════════════════════════════════════
12
+ # Safe_Str__Node_Type - Node type identifier (bug, task, feature, person, etc.)
13
+ # ═══════════════════════════════════════════════════════════════════════════════
14
+
15
+ class Safe_Str__Node_Type(Safe_Str): # Node type like "bug", "task"
16
+ max_length = 50
17
+ regex = re.compile(r'^[a-z][a-z0-9-]*$') # lowercase, hyphens allowed
18
+ regex_mode = Enum__Safe_Str__Regex_Mode.MATCH
19
+ strict_validation = True
20
+
21
+
22
+ # ═══════════════════════════════════════════════════════════════════════════════
23
+ # Safe_Str__Node_Label - Human-readable node label (Bug-27, Task-15)
24
+ # ═══════════════════════════════════════════════════════════════════════════════
25
+
26
+ class Safe_Str__Node_Label(Safe_Str): # Node label like "Bug-27"
27
+ max_length = 100
28
+ regex = re.compile(r'^[A-Z][a-zA-Z]*-\d{1,5}$') # PascalCase-N (e.g., UserStory-1)
29
+ regex_mode = Enum__Safe_Str__Regex_Mode.MATCH
30
+ strict_validation = True
31
+
32
+
33
+ # ═══════════════════════════════════════════════════════════════════════════════
34
+ # Safe_Str__Status - Status identifier (backlog, in-progress, done, etc.)
35
+ # ═══════════════════════════════════════════════════════════════════════════════
36
+
37
+ class Safe_Str__Status(Safe_Str): # Status like "in-progress"
38
+ max_length = 50
39
+ regex = re.compile(r'^[a-z][a-z0-9-]*$') # lowercase, hyphens allowed
40
+ regex_mode = Enum__Safe_Str__Regex_Mode.MATCH
41
+ strict_validation = True
42
+
43
+
44
+ # ═══════════════════════════════════════════════════════════════════════════════
45
+ # Safe_Str__Link_Verb - Relationship verb (blocks, has-task, assigned-to)
46
+ # ═══════════════════════════════════════════════════════════════════════════════
47
+
48
+ class Safe_Str__Link_Verb(Safe_Str): # Link verb like "blocks"
49
+ max_length = 50
50
+ regex = re.compile(r'^[a-z][a-z0-9-]*$') # lowercase, hyphens allowed
51
+ regex_mode = Enum__Safe_Str__Regex_Mode.MATCH
52
+ strict_validation = True
53
+
54
+
55
+ # ═══════════════════════════════════════════════════════════════════════════════
56
+ # Safe_Str__Node_Type_Display - Display name for node type (Bug, Task, Feature)
57
+ # ═══════════════════════════════════════════════════════════════════════════════
58
+
59
+ class Safe_Str__Node_Type_Display(Safe_Str): # Display name like "Bug"
60
+ max_length = 100
61
+ regex = re.compile(r'^[A-Z][a-zA-Z0-9 ]*$') # Title case, spaces allowed
62
+ regex_mode = Enum__Safe_Str__Regex_Mode.MATCH
63
+ strict_validation = True