gyomu-workflow 0.4.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.
@@ -0,0 +1,29 @@
1
+ venv/
2
+ .idea/
3
+
4
+ /build/
5
+ /dist/
6
+ /src/gyomu.egg-info/
7
+ /.coverage
8
+ /htmlcov/
9
+ __pycache__/
10
+ *.py[cod]
11
+
12
+ .env
13
+ .env.*
14
+ !.env.example
15
+
16
+ .mypy_cache/
17
+ .ruff_cache/
18
+ htmlcov/
19
+ *.egg-info/
20
+ log/
21
+
22
+
23
+ # gyomu ai related
24
+ **/.gyomu/cache
25
+ **/.gyomu/snapshot
26
+ **/.gyomu/checkpoint
27
+
28
+ # snapshot
29
+ __snapshots__
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.5
2
+ Name: gyomu-workflow
3
+ Version: 0.4.0
4
+ Summary: Gyomu CLI
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: gyomu-docstring
7
+ Requires-Dist: gyomu-infra
8
+ Requires-Dist: gyomu-python-analysis
9
+ Requires-Dist: gyomu-schema
10
+ Requires-Dist: returns
11
+ Description-Content-Type: text/markdown
12
+
13
+ # gyomu-cli
14
+
15
+ workflow components for Gyomu Python.
@@ -0,0 +1,3 @@
1
+ # gyomu-cli
2
+
3
+ workflow components for Gyomu Python.
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "gyomu-workflow"
3
+ dynamic = ["version"]
4
+ description = "Gyomu CLI"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "gyomu-schema",
9
+ "gyomu-infra",
10
+ "returns",
11
+ "gyomu-python-analysis",
12
+ "gyomu-docstring"
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["hatchling"]
17
+ build-backend = "hatchling.build"
18
+
19
+
20
+ [tool.uv.sources]
21
+ gyomu-schema = { workspace = true }
22
+ gyomu-infra = { workspace = true }
23
+ gyomu-python-analysis = { workspace = true }
24
+ gyomu-docstring = { workspace = true }
25
+
26
+ [tool.hatch.version]
27
+ path = "../../version/__about__.py"
28
+
File without changes
@@ -0,0 +1,51 @@
1
+ from collections.abc import Mapping
2
+
3
+ from gyomu_schema.error.base import BaseError
4
+ from gyomu_schema.schemas.python.types import ProjectRelativePath
5
+
6
+
7
+ class SnapshotRequestValidationError(BaseError):
8
+ """SnapshotRequest validation error."""
9
+
10
+ def __init__(
11
+ self,
12
+ message: str,
13
+ *,
14
+ code: str,
15
+ field: str | None = None,
16
+ expected: object | None = None,
17
+ actual: object | None = None,
18
+ context: str | None = None,
19
+ details: Mapping[str, object] | None = None,
20
+ ) -> None:
21
+ super().__init__(
22
+ message,
23
+ context=context,
24
+ details=details,
25
+ )
26
+ self.code = code
27
+ self.field = field
28
+ self.expected = expected
29
+ self.actual = actual
30
+
31
+
32
+ class PyProjectStructureValidationError(BaseError):
33
+ """Raised when a pyproject structure validation error occurs.
34
+
35
+ Raised when the pyproject structure validation fails.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ message: str,
41
+ *,
42
+ path: ProjectRelativePath,
43
+ context: str | None = None,
44
+ details: Mapping[str, object] | None = None,
45
+ ) -> None:
46
+ super().__init__(
47
+ message,
48
+ context=context,
49
+ details=details,
50
+ )
51
+ self.path = path
@@ -0,0 +1,153 @@
1
+ from dataclasses import dataclass
2
+
3
+ from gyomu_python_analysis.project.context import ProjectContext
4
+ from gyomu_python_analysis.snapshot.models import ProjectSnapshot
5
+ from gyomu_schema.schemas.python.types import ProjectRelativePath
6
+ from gyomu_schema.schemas.types import FullPath
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class FileFilter(BaseModel):
11
+ """File filter pattern configuration.
12
+
13
+ Represents a file filter pattern configuration.
14
+ """
15
+
16
+ pattern: str
17
+ """File path matching pattern.
18
+
19
+ The file path matching pattern.
20
+ """
21
+
22
+
23
+ class SnapshotTargetOption(BaseModel):
24
+ """Snapshot target selection options.
25
+
26
+ Represents options for selecting snapshot targets.
27
+ """
28
+
29
+ all: bool = Field(default=False)
30
+ """Whether to target all files.
31
+
32
+ Whether to target all files.
33
+ """
34
+ file_filter: FileFilter | None = Field(default=None)
35
+ """Optional file filter.
36
+
37
+ Optional file filter criteria.
38
+ """
39
+
40
+
41
+ class DocstringExecutionOption(BaseModel):
42
+ """Docstring execution options.
43
+
44
+ Represents execution options for docstring generation.
45
+ """
46
+
47
+ enabled: bool = Field(default=True)
48
+ """Whether docstring generation is enabled.
49
+
50
+ Whether docstring generation is enabled.
51
+ """
52
+ log_keyword: str | None = Field(default=None)
53
+ """Optional log keyword.
54
+
55
+ Optional keyword to trigger logging.
56
+ """
57
+
58
+
59
+ class SnapshotActionOption(BaseModel):
60
+ """Snapshot action configuration options.
61
+
62
+ Represents options for snapshot actions such as docstring generation, project
63
+ context, and unit tests.
64
+ """
65
+
66
+ docstring: DocstringExecutionOption
67
+ """Docstring execution options.
68
+
69
+ Docstring execution options.
70
+ """
71
+ project_context: bool = Field(default=False)
72
+ """Whether to include project context.
73
+
74
+ Whether to include project context.
75
+ """
76
+ unit_test: bool = Field(default=False)
77
+ """Whether to generate unit tests.
78
+
79
+ Whether to generate unit tests.
80
+ """
81
+
82
+
83
+ class SnapshotExecutionOption(BaseModel):
84
+ """Snapshot execution options.
85
+
86
+ Represents execution options for creating a snapshot, including commit settings,
87
+ targets, and actions.
88
+ """
89
+
90
+ commit: bool
91
+ """Whether to commit.
92
+
93
+ Whether to commit the snapshot.
94
+ """
95
+ target: SnapshotTargetOption
96
+ """Target options.
97
+
98
+ Target selection options.
99
+ """
100
+ action: SnapshotActionOption
101
+ """Action options.
102
+
103
+ Action options.
104
+ """
105
+
106
+
107
+ @dataclass(frozen=True)
108
+ class SnapshotRequest:
109
+ """Snapshot request parameters.
110
+
111
+ Represents a request to create a snapshot with repository path, project context, and
112
+ execution options.
113
+ """
114
+
115
+ repository_root_path: FullPath
116
+ """Repository root path.
117
+
118
+ Root path of the repository.
119
+ """
120
+ project_context: ProjectContext
121
+ """Project context.
122
+
123
+ Project context information.
124
+ """
125
+ option: SnapshotExecutionOption
126
+ """Execution options.
127
+
128
+ Snapshot execution options.
129
+ """
130
+
131
+
132
+ class SnapshotTarget(BaseModel):
133
+ """Snapshot target state.
134
+
135
+ Represents the target state of a snapshot including files, deleted files, and the
136
+ project snapshot.
137
+ """
138
+
139
+ files: frozenset[ProjectRelativePath]
140
+ """Target files.
141
+
142
+ Set of target files.
143
+ """
144
+ deleted_files: frozenset[ProjectRelativePath]
145
+ """Deleted files.
146
+
147
+ Set of deleted files.
148
+ """
149
+ snapshot: ProjectSnapshot
150
+ """Project snapshot.
151
+
152
+ The project snapshot.
153
+ """
@@ -0,0 +1,30 @@
1
+ from gyomu_workflow.snapshot.models import SnapshotTargetOption
2
+
3
+
4
+ def normalize_filter(option: SnapshotTargetOption) -> None:
5
+ """Normalizes the file filter pattern within a snapshot target option.
6
+
7
+ Args:
8
+ option (SnapshotTargetOption): The snapshot target option to normalize.
9
+ """
10
+ if option.file_filter is None:
11
+ return
12
+ option.file_filter.pattern = _normalize_snapshot_filter(option.file_filter.pattern)
13
+
14
+
15
+ def _normalize_snapshot_filter(
16
+ filter: str,
17
+ ) -> str:
18
+ """Normalizes a snapshot filter pattern string.
19
+
20
+ Args:
21
+ filter (str): The filter pattern string to normalize.
22
+
23
+ Returns:
24
+ str: The normalized filter pattern string.
25
+ """
26
+
27
+ if filter.endswith("/*"):
28
+ return f"**/{filter[:-2]}/**"
29
+
30
+ return filter
@@ -0,0 +1,181 @@
1
+ from gyomu_infra.logger import logger
2
+ from gyomu_python_analysis.analysis.delete_cache import delete_module_cache
3
+ from gyomu_python_analysis.snapshot.analyze import analyze_project_changes
4
+ from gyomu_python_analysis.snapshot.commit import commit_project_changes
5
+ from gyomu_python_analysis.snapshot.models import ProjectSnapshot
6
+ from gyomu_schema.error.gyomu import GyomuError
7
+ from gyomu_schema.option.update import UpdateDebugInfoOption, UpdateOption
8
+ from gyomu_schema.schemas.python.types import ProjectRelativePath
9
+ from gyomu_schema.utility.context import caller_context
10
+ from returns.result import Failure, Result, Success
11
+
12
+ from gyomu_workflow.snapshot.models import SnapshotRequest, SnapshotTarget
13
+ from gyomu_workflow.snapshot.normalize import normalize_filter
14
+ from gyomu_workflow.snapshot.run_docstring import run_docstring_action
15
+ from gyomu_workflow.snapshot.target import resolve_snapshot_target
16
+
17
+
18
+ async def run_snapshot(request: SnapshotRequest) -> Result[None, GyomuError]:
19
+ """Run the snapshot workflow based on the provided request.
20
+
21
+ Args:
22
+ request (SnapshotRequest): The snapshot request configuration and context.
23
+
24
+ Returns:
25
+ Result[None, GyomuError]: A Result indicating success with None or failure with
26
+ GyomuError.
27
+ """
28
+ normalize_filter(request.option.target)
29
+ logger.debug(f"file_filter: {repr(request.option.target.file_filter)}")
30
+ target_result = resolve_snapshot_target(
31
+ repository_root_path=request.repository_root_path,
32
+ project_context=request.project_context,
33
+ option=request.option.target,
34
+ )
35
+ if isinstance(target_result, Failure):
36
+ return target_result
37
+
38
+ target = target_result.unwrap()
39
+
40
+ logger.debug(repr(target.files))
41
+ if len(target.files) == 0:
42
+ logger.debug_object(target.snapshot.files)
43
+ current_snapshot = target.snapshot
44
+
45
+ action_result = await run_actions(request=request, target=target)
46
+
47
+ if isinstance(action_result, Failure):
48
+ return action_result
49
+ current_snapshot = action_result.unwrap()
50
+
51
+ if request.option.commit:
52
+ commit_result = commit_project_changes(
53
+ repository_root_path=request.repository_root_path,
54
+ project_context=request.project_context,
55
+ expected_snapshot=current_snapshot,
56
+ )
57
+ if isinstance(commit_result, Failure):
58
+ context = caller_context()
59
+ return commit_result.alt(
60
+ lambda error: GyomuError(
61
+ message="fail to commit project changes",
62
+ domain="snapshot",
63
+ operation="run_snapshot",
64
+ reason="external_failure",
65
+ context=context,
66
+ ).chain(error)
67
+ )
68
+ return Success(None)
69
+
70
+
71
+ async def run_actions(
72
+ request: SnapshotRequest, target: SnapshotTarget
73
+ ) -> Result[ProjectSnapshot, GyomuError]:
74
+ """Execute requested actions on the snapshot target files.
75
+
76
+ Args:
77
+ request (SnapshotRequest): The snapshot request configuration.
78
+ target (SnapshotTarget): The target snapshot data and file lists.
79
+
80
+ Returns:
81
+ Result[ProjectSnapshot, GyomuError]: A Result containing the updated
82
+ ProjectSnapshot or a GyomuError.
83
+ """
84
+ current_snapshot = target.snapshot
85
+ context = caller_context()
86
+ option = build_docstring_update_option(request.option.action.docstring.log_keyword)
87
+ # Do Action
88
+
89
+ for deleted in target.deleted_files:
90
+ if is_source_file(deleted, request.project_context.source_root):
91
+ delete_result = delete_module_cache(
92
+ request.project_context, file_path=deleted
93
+ )
94
+ if isinstance(delete_result, Failure):
95
+ return delete_result.alt(
96
+ lambda error: GyomuError(
97
+ "fail to delete unnecessary module cache",
98
+ domain="snapshot",
99
+ operation="run_actions",
100
+ reason="external_failure",
101
+ context=context,
102
+ ).chain(error)
103
+ )
104
+ project_context = request.project_context
105
+
106
+ for file in target.files:
107
+ if is_source_file(file, request.project_context.source_root):
108
+ if request.option.action.docstring.enabled:
109
+ docstring_action_result = await run_docstring_action(
110
+ project_context=project_context,
111
+ source_project_relative_path=file,
112
+ option=option,
113
+ )
114
+ if isinstance(docstring_action_result, Failure):
115
+ return docstring_action_result
116
+ if request.option.action.unit_test:
117
+ pass
118
+
119
+ # Post Action
120
+ if (
121
+ request.option.action.docstring.enabled
122
+ or request.option.action.project_context
123
+ or request.option.action.unit_test
124
+ ):
125
+ analysis_result = analyze_project_changes(
126
+ repository_root_path=request.repository_root_path,
127
+ project_context=request.project_context,
128
+ )
129
+ if isinstance(analysis_result, Failure):
130
+ return analysis_result.alt(
131
+ lambda error: GyomuError(
132
+ message="fail to analyze project change",
133
+ domain="snapshot",
134
+ operation="run_actions",
135
+ reason="external_failure",
136
+ context=context,
137
+ ).chain(error)
138
+ )
139
+ current_snapshot = analysis_result.unwrap().current_snapshot
140
+
141
+ return Success(current_snapshot)
142
+
143
+
144
+ def build_docstring_update_option(
145
+ log_keyword: str | None,
146
+ ) -> UpdateOption:
147
+ """Construct the docstring update option configuration.
148
+
149
+ Args:
150
+ log_keyword (str | None): Optional keyword for logging.
151
+
152
+ Returns:
153
+ UpdateOption: The constructed UpdateOption configuration.
154
+ """
155
+ return UpdateOption(
156
+ debug_info=UpdateDebugInfoOption(
157
+ dump_to_file=True,
158
+ updated_symbol_docstring=True,
159
+ file_update_plan=True,
160
+ rendered_symbol_docstring=True,
161
+ docstring_update_plan=True,
162
+ docstring_update_context=True,
163
+ keyword=log_keyword,
164
+ ),
165
+ no_check_cache=True,
166
+ )
167
+
168
+
169
+ def is_source_file(
170
+ file_path: ProjectRelativePath, source_root: ProjectRelativePath
171
+ ) -> bool:
172
+ """Check whether a file path is a source file.
173
+
174
+ Args:
175
+ file_path (ProjectRelativePath): Path of the file to check.
176
+ source_root (ProjectRelativePath): Root directory path of the source files.
177
+
178
+ Returns:
179
+ bool: True if the file is a source file, False otherwise.
180
+ """
181
+ return file_path.is_relative_to(source_root)
@@ -0,0 +1,101 @@
1
+ from pathlib import Path
2
+
3
+ from gyomu_docstring.update.process import process_docstring_update
4
+ from gyomu_infra.logger import logger
5
+ from gyomu_python_analysis.analysis.load_file_context import load_file_analysis_context
6
+ from gyomu_python_analysis.project.context import ProjectContext
7
+ from gyomu_schema.error.gyomu import GyomuError
8
+ from gyomu_schema.option.update import UpdateOption
9
+ from gyomu_schema.schemas.python.types import ProjectRelativePath
10
+ from gyomu_schema.utility.context import caller_context
11
+ from returns.result import Failure, Result, Success
12
+
13
+
14
+ def is_source_docstring_target(
15
+ project_context: ProjectContext,
16
+ source_project_relative_path: ProjectRelativePath,
17
+ ) -> bool:
18
+ """Determine if a source path is a target for docstring processing.
19
+
20
+ Determines whether a source file is a target for docstring generation based on
21
+ project exclusion configurations.
22
+
23
+ Args:
24
+ project_context (ProjectContext): The project context containing configuration
25
+ settings.
26
+ source_project_relative_path (ProjectRelativePath): The relative path of the
27
+ source file within the project.
28
+
29
+ Returns:
30
+ bool: True if the source file is a docstring target, otherwise False.
31
+ """
32
+ exclude_path_list = project_context.config.get_attribute(
33
+ "tool.gyomu.exclude", list[str]
34
+ )
35
+ if exclude_path_list is None:
36
+ return True
37
+ return not any(
38
+ source_project_relative_path.is_relative_to(Path(exclude_path))
39
+ for exclude_path in exclude_path_list
40
+ )
41
+
42
+
43
+ async def run_docstring_action(
44
+ project_context: ProjectContext,
45
+ source_project_relative_path: ProjectRelativePath,
46
+ option: UpdateOption,
47
+ ) -> Result[None, GyomuError]:
48
+ """Run the docstring update action for a specified source file.
49
+
50
+ Executes the docstring update action for a given source file within the project.
51
+
52
+ Args:
53
+ project_context (ProjectContext): The project context containing configuration
54
+ and analysis settings.
55
+ source_project_relative_path (ProjectRelativePath): The relative path of the
56
+ source file to process.
57
+ option (UpdateOption): The update options controlling analysis and modification
58
+ behavior.
59
+
60
+ Returns:
61
+ Result[None, GyomuError]: A Result indicating success with None or a GyomuError
62
+ on failure.
63
+ """
64
+ if not is_source_docstring_target(project_context, source_project_relative_path):
65
+ logger.info(f"Not Scope of Docstring:{source_project_relative_path}")
66
+ return Success(None)
67
+
68
+ file_path = source_project_relative_path
69
+ context = caller_context()
70
+
71
+ file_analysis_result = load_file_analysis_context(
72
+ context=project_context, file_path=file_path, option=option
73
+ )
74
+ if isinstance(file_analysis_result, Failure):
75
+ return file_analysis_result.alt(
76
+ lambda error: GyomuError(
77
+ "fail to analyze source file",
78
+ domain="snapshot",
79
+ operation="run_docstring_action",
80
+ reason="external_failure",
81
+ context=context,
82
+ details={"file_path": file_path},
83
+ ).chain(error)
84
+ )
85
+
86
+ file_context = file_analysis_result.unwrap()
87
+ docstring_update_result = await process_docstring_update(
88
+ context=project_context, file_context=file_context, option=option
89
+ )
90
+ if isinstance(docstring_update_result, Failure):
91
+ return docstring_update_result.alt(
92
+ lambda error: GyomuError(
93
+ "fail to update docstring",
94
+ domain="snapshot",
95
+ operation="run_docstring_action",
96
+ reason="external_failure",
97
+ context=context,
98
+ details={"file_path": file_path},
99
+ ).chain(error)
100
+ )
101
+ return Success(None)