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.
- issues_fs/issues/Issue__Path__Config.py +25 -0
- issues_fs/issues/__init__.py +3 -0
- issues_fs/issues/graph_services/Comments__Service.py +235 -0
- issues_fs/issues/graph_services/Graph__Repository.py +307 -0
- issues_fs/issues/graph_services/Graph__Repository__Factory.py +106 -0
- issues_fs/issues/graph_services/Link__Service.py +227 -0
- issues_fs/issues/graph_services/Node__Service.py +392 -0
- issues_fs/issues/graph_services/Type__Service.py +209 -0
- issues_fs/issues/graph_services/__init__.py +3 -0
- issues_fs/issues/phase_1/Issue__Children__Service.py +340 -0
- issues_fs/issues/phase_1/Root__Issue__Service.py +137 -0
- issues_fs/issues/phase_1/Root__Selection__Service.py +310 -0
- issues_fs/issues/phase_1/__init__.py +3 -0
- issues_fs/issues/status/Git__Status__Service.py +115 -0
- issues_fs/issues/status/Index__Status__Service.py +131 -0
- issues_fs/issues/status/Server__Status__Service.py +113 -0
- issues_fs/issues/status/Storage__Status__Service.py +75 -0
- issues_fs/issues/status/Types__Status__Service.py +75 -0
- issues_fs/issues/status/__init__.py +3 -0
- issues_fs/issues/storage/Path__Handler__Graph_Node.py +185 -0
- issues_fs/issues/storage/Path__Handler__Issues.py +57 -0
- issues_fs/issues/storage/__init__.py +3 -0
- issues_fs/schemas/__init__.py +0 -0
- issues_fs/schemas/enums/Enum__Comment__Author.py +10 -0
- issues_fs/schemas/enums/Enum__Graph__Storage__Backend.py +8 -0
- issues_fs/schemas/enums/Enum__Issue__Status.py +13 -0
- issues_fs/schemas/enums/__init__.py +3 -0
- issues_fs/schemas/graph/Safe_Str__Graph_Types.py +63 -0
- issues_fs/schemas/graph/Schema__Global__Index.py +16 -0
- issues_fs/schemas/graph/Schema__Graph__Link.py +8 -0
- issues_fs/schemas/graph/Schema__Graph__Node.py +10 -0
- issues_fs/schemas/graph/Schema__Graph__Response.py +21 -0
- issues_fs/schemas/graph/Schema__Link__Create__Request.py +12 -0
- issues_fs/schemas/graph/Schema__Link__Create__Response.py +15 -0
- issues_fs/schemas/graph/Schema__Link__Delete__Response.py +16 -0
- issues_fs/schemas/graph/Schema__Link__List__Response.py +15 -0
- issues_fs/schemas/graph/Schema__Link__Type.py +20 -0
- issues_fs/schemas/graph/Schema__Node.py +43 -0
- issues_fs/schemas/graph/Schema__Node__Create__Request.py +19 -0
- issues_fs/schemas/graph/Schema__Node__Create__Response.py +14 -0
- issues_fs/schemas/graph/Schema__Node__Delete__Response.py +15 -0
- issues_fs/schemas/graph/Schema__Node__Link.py +17 -0
- issues_fs/schemas/graph/Schema__Node__List__Response.py +16 -0
- issues_fs/schemas/graph/Schema__Node__Summary.py +15 -0
- issues_fs/schemas/graph/Schema__Node__Type.py +28 -0
- issues_fs/schemas/graph/Schema__Node__Update__Request.py +18 -0
- issues_fs/schemas/graph/Schema__Node__Update__Response.py +14 -0
- issues_fs/schemas/graph/Schema__Property__Definition.py +27 -0
- issues_fs/schemas/graph/Schema__Type__Index.py +16 -0
- issues_fs/schemas/graph/Schema__Type__Summary.py +13 -0
- issues_fs/schemas/graph/__init__.py +0 -0
- issues_fs/schemas/identifiers/Comment_Id.py +5 -0
- issues_fs/schemas/identifiers/Issue_Id.py +13 -0
- issues_fs/schemas/identifiers/__init__.py +0 -0
- issues_fs/schemas/issues/Schema__Comment.py +61 -0
- issues_fs/schemas/issues/__init__.py +0 -0
- issues_fs/schemas/issues/phase_1/Schema__Issue__Children.py +85 -0
- issues_fs/schemas/issues/phase_1/Schema__Root.py +60 -0
- issues_fs/schemas/issues/phase_1/__init__.py +0 -0
- issues_fs/schemas/safe_str/Safe_Str__Hex_Color.py +10 -0
- issues_fs/schemas/safe_str/Safe_Str__Issue_Id.py +14 -0
- issues_fs/schemas/safe_str/Safe_Str__Issue__Node__Description.py +15 -0
- issues_fs/schemas/safe_str/Safe_Str__Label_Name.py +9 -0
- issues_fs/schemas/safe_str/__init__.py +0 -0
- issues_fs/schemas/status/Schema__API__Info.py +15 -0
- issues_fs/schemas/status/Schema__Git__Status.py +21 -0
- issues_fs/schemas/status/Schema__Index__Status.py +22 -0
- issues_fs/schemas/status/Schema__Server__Status.py +28 -0
- issues_fs/schemas/status/Schema__Storage__Status.py +23 -0
- issues_fs/schemas/status/Schema__Types__Status.py +30 -0
- issues_fs/schemas/status/__init__.py +0 -0
- issues_fs/version +1 -1
- {issues_fs-0.3.0.dist-info → issues_fs-0.4.0.dist-info}/METADATA +2 -1
- issues_fs-0.4.0.dist-info/RECORD +79 -0
- issues_fs-0.3.0.dist-info/RECORD +0 -8
- {issues_fs-0.3.0.dist-info → issues_fs-0.4.0.dist-info}/LICENSE +0 -0
- {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,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}"
|
|
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,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,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
|