julee 0.1.2__py3-none-any.whl → 0.1.4__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.
- julee/api/app.py +9 -8
- julee/api/dependencies.py +15 -15
- julee/api/requests.py +10 -9
- julee/api/responses.py +2 -1
- julee/api/routers/__init__.py +5 -5
- julee/api/routers/assembly_specifications.py +5 -4
- julee/api/routers/documents.py +1 -1
- julee/api/routers/knowledge_service_configs.py +4 -3
- julee/api/routers/knowledge_service_queries.py +7 -6
- julee/api/routers/system.py +4 -3
- julee/api/routers/workflows.py +4 -5
- julee/api/services/system_initialization.py +6 -6
- julee/api/tests/routers/test_assembly_specifications.py +4 -3
- julee/api/tests/routers/test_documents.py +11 -10
- julee/api/tests/routers/test_knowledge_service_configs.py +7 -6
- julee/api/tests/routers/test_knowledge_service_queries.py +4 -3
- julee/api/tests/routers/test_system.py +5 -4
- julee/api/tests/routers/test_workflows.py +5 -4
- julee/api/tests/test_app.py +5 -4
- julee/api/tests/test_dependencies.py +3 -2
- julee/api/tests/test_requests.py +2 -1
- julee/contrib/__init__.py +15 -0
- julee/contrib/polling/__init__.py +47 -0
- julee/contrib/polling/domain/__init__.py +17 -0
- julee/contrib/polling/domain/models/__init__.py +13 -0
- julee/contrib/polling/domain/models/polling_config.py +39 -0
- julee/contrib/polling/domain/services/__init__.py +11 -0
- julee/contrib/polling/domain/services/poller.py +39 -0
- julee/contrib/polling/infrastructure/__init__.py +15 -0
- julee/contrib/polling/infrastructure/services/__init__.py +12 -0
- julee/contrib/polling/infrastructure/services/polling/__init__.py +12 -0
- julee/contrib/polling/infrastructure/services/polling/http/__init__.py +12 -0
- julee/contrib/polling/infrastructure/services/polling/http/http_poller_service.py +80 -0
- julee/contrib/polling/infrastructure/temporal/__init__.py +20 -0
- julee/contrib/polling/infrastructure/temporal/activities.py +42 -0
- julee/contrib/polling/infrastructure/temporal/activity_names.py +20 -0
- julee/contrib/polling/infrastructure/temporal/proxies.py +45 -0
- julee/contrib/polling/tests/__init__.py +6 -0
- julee/contrib/polling/tests/unit/__init__.py +6 -0
- julee/contrib/polling/tests/unit/infrastructure/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/services/__init__.py +6 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/__init__.py +6 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/__init__.py +7 -0
- julee/contrib/polling/tests/unit/infrastructure/services/polling/http/test_http_poller_service.py +163 -0
- julee/docs/__init__.py +5 -0
- julee/docs/sphinx_hcd/__init__.py +76 -0
- julee/docs/sphinx_hcd/accelerators.py +1175 -0
- julee/docs/sphinx_hcd/apps.py +518 -0
- julee/docs/sphinx_hcd/config.py +148 -0
- julee/docs/sphinx_hcd/epics.py +453 -0
- julee/docs/sphinx_hcd/integrations.py +310 -0
- julee/docs/sphinx_hcd/journeys.py +797 -0
- julee/docs/sphinx_hcd/personas.py +457 -0
- julee/docs/sphinx_hcd/stories.py +960 -0
- julee/docs/sphinx_hcd/utils.py +185 -0
- julee/domain/models/__init__.py +5 -6
- julee/domain/models/assembly/assembly.py +7 -7
- julee/domain/models/assembly/tests/factories.py +2 -1
- julee/domain/models/assembly/tests/test_assembly.py +16 -13
- julee/domain/models/assembly_specification/assembly_specification.py +11 -10
- julee/domain/models/assembly_specification/knowledge_service_query.py +7 -6
- julee/domain/models/assembly_specification/tests/factories.py +2 -1
- julee/domain/models/assembly_specification/tests/test_assembly_specification.py +9 -6
- julee/domain/models/assembly_specification/tests/test_knowledge_service_query.py +3 -1
- julee/domain/models/custom_fields/content_stream.py +3 -2
- julee/domain/models/custom_fields/tests/test_custom_fields.py +2 -1
- julee/domain/models/document/document.py +23 -30
- julee/domain/models/document/tests/factories.py +3 -2
- julee/domain/models/document/tests/test_document.py +20 -37
- julee/domain/models/knowledge_service_config/knowledge_service_config.py +4 -4
- julee/domain/models/policy/__init__.py +4 -4
- julee/domain/models/policy/document_policy_validation.py +17 -17
- julee/domain/models/policy/policy.py +10 -10
- julee/domain/models/policy/tests/factories.py +2 -1
- julee/domain/models/policy/tests/test_document_policy_validation.py +3 -1
- julee/domain/models/policy/tests/test_policy.py +2 -1
- julee/domain/repositories/__init__.py +3 -3
- julee/domain/repositories/assembly.py +3 -1
- julee/domain/repositories/assembly_specification.py +2 -0
- julee/domain/repositories/base.py +5 -4
- julee/domain/repositories/document.py +3 -1
- julee/domain/repositories/document_policy_validation.py +3 -1
- julee/domain/repositories/knowledge_service_config.py +2 -0
- julee/domain/repositories/knowledge_service_query.py +1 -0
- julee/domain/repositories/policy.py +3 -1
- julee/domain/use_cases/decorators.py +3 -2
- julee/domain/use_cases/extract_assemble_data.py +14 -13
- julee/domain/use_cases/initialize_system_data.py +88 -34
- julee/domain/use_cases/tests/test_extract_assemble_data.py +10 -10
- julee/domain/use_cases/tests/test_initialize_system_data.py +2 -2
- julee/domain/use_cases/tests/test_validate_document.py +11 -11
- julee/domain/use_cases/validate_document.py +14 -14
- julee/fixtures/documents.yaml +4 -43
- julee/fixtures/knowledge_service_queries.yaml +9 -0
- julee/maintenance/__init__.py +1 -0
- julee/maintenance/release.py +243 -0
- julee/repositories/memory/assembly.py +6 -5
- julee/repositories/memory/assembly_specification.py +8 -9
- julee/repositories/memory/base.py +12 -11
- julee/repositories/memory/document.py +27 -20
- julee/repositories/memory/document_policy_validation.py +7 -6
- julee/repositories/memory/knowledge_service_config.py +8 -7
- julee/repositories/memory/knowledge_service_query.py +8 -7
- julee/repositories/memory/policy.py +6 -5
- julee/repositories/memory/tests/test_document.py +24 -22
- julee/repositories/memory/tests/test_document_policy_validation.py +2 -1
- julee/repositories/memory/tests/test_policy.py +2 -1
- julee/repositories/minio/assembly.py +4 -4
- julee/repositories/minio/assembly_specification.py +6 -8
- julee/repositories/minio/client.py +22 -25
- julee/repositories/minio/document.py +36 -33
- julee/repositories/minio/document_policy_validation.py +5 -5
- julee/repositories/minio/knowledge_service_config.py +6 -6
- julee/repositories/minio/knowledge_service_query.py +6 -9
- julee/repositories/minio/policy.py +4 -4
- julee/repositories/minio/tests/fake_client.py +11 -9
- julee/repositories/minio/tests/test_assembly.py +3 -1
- julee/repositories/minio/tests/test_assembly_specification.py +2 -1
- julee/repositories/minio/tests/test_client_protocol.py +5 -5
- julee/repositories/minio/tests/test_document.py +23 -22
- julee/repositories/minio/tests/test_document_policy_validation.py +3 -1
- julee/repositories/minio/tests/test_knowledge_service_config.py +4 -2
- julee/repositories/minio/tests/test_knowledge_service_query.py +3 -2
- julee/repositories/minio/tests/test_policy.py +3 -1
- julee/repositories/temporal/activities.py +5 -5
- julee/repositories/temporal/proxies.py +5 -5
- julee/services/knowledge_service/__init__.py +1 -2
- julee/services/knowledge_service/anthropic/knowledge_service.py +8 -7
- julee/services/knowledge_service/anthropic/tests/test_knowledge_service.py +11 -10
- julee/services/knowledge_service/factory.py +8 -8
- julee/services/knowledge_service/knowledge_service.py +12 -14
- julee/services/knowledge_service/memory/knowledge_service.py +13 -12
- julee/services/knowledge_service/memory/test_knowledge_service.py +10 -7
- julee/services/knowledge_service/test_factory.py +11 -10
- julee/services/temporal/activities.py +10 -10
- julee/services/temporal/proxies.py +2 -2
- julee/util/domain.py +6 -6
- julee/util/repos/minio/file_storage.py +8 -9
- julee/util/repos/temporal/client_proxies/file_storage.py +3 -4
- julee/util/repos/temporal/data_converter.py +6 -6
- julee/util/repos/temporal/minio_file_storage.py +1 -1
- julee/util/repos/temporal/proxies/file_storage.py +2 -3
- julee/util/repositories.py +4 -3
- julee/util/temporal/decorators.py +20 -18
- julee/util/tests/test_decorators.py +13 -15
- julee/util/validation/repository.py +3 -3
- julee/util/validation/type_guards.py +12 -11
- julee/worker.py +9 -8
- julee/workflows/__init__.py +2 -2
- julee/workflows/extract_assemble.py +2 -1
- julee/workflows/validate_document.py +3 -2
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/METADATA +3 -3
- julee-0.1.4.dist-info/RECORD +196 -0
- julee/fixtures/assembly_specifications.yaml +0 -70
- julee-0.1.2.dist-info/RECORD +0 -161
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/WHEEL +0 -0
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.2.dist-info → julee-0.1.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Release preparation and tagging script.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
release.py prepare X.Y.Z [--message-file FILE] # Create release branch and PR
|
|
7
|
+
release.py tag X.Y.Z # Tag after PR is merged
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import re
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def run(
|
|
19
|
+
cmd: str, check: bool = True, capture: bool = True
|
|
20
|
+
) -> subprocess.CompletedProcess:
|
|
21
|
+
"""Run a shell command."""
|
|
22
|
+
result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)
|
|
23
|
+
if check and result.returncode != 0:
|
|
24
|
+
print(f"ERROR: {cmd}", file=sys.stderr)
|
|
25
|
+
if result.stderr:
|
|
26
|
+
print(result.stderr, file=sys.stderr)
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
return result
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_repo_root() -> Path:
|
|
32
|
+
"""Get the repository root directory."""
|
|
33
|
+
result = run("git rev-parse --show-toplevel")
|
|
34
|
+
return Path(result.stdout.strip())
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_package_init(repo_root: Path) -> Path | None:
|
|
38
|
+
"""Find __init__.py with __version__ in src/ directory."""
|
|
39
|
+
src_dir = repo_root / "src"
|
|
40
|
+
if not src_dir.exists():
|
|
41
|
+
return None
|
|
42
|
+
packages = [
|
|
43
|
+
p for p in src_dir.iterdir() if p.is_dir() and not p.name.startswith("_")
|
|
44
|
+
]
|
|
45
|
+
if len(packages) != 1:
|
|
46
|
+
# Multiple packages (bounded contexts) - no single __init__.py to update
|
|
47
|
+
return None
|
|
48
|
+
init_file = packages[0] / "__init__.py"
|
|
49
|
+
if init_file.exists() and "__version__" in init_file.read_text():
|
|
50
|
+
return init_file
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def validate_version(version: str) -> None:
|
|
55
|
+
"""Validate version string format."""
|
|
56
|
+
if not re.match(r"^\d+\.\d+\.\d+$", version):
|
|
57
|
+
print(
|
|
58
|
+
f"ERROR: Invalid version format '{version}'. Expected X.Y.Z",
|
|
59
|
+
file=sys.stderr,
|
|
60
|
+
)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def validate_git_state(require_master: bool = True) -> None:
|
|
65
|
+
"""Validate git working tree is clean and on correct branch."""
|
|
66
|
+
# Check for uncommitted changes
|
|
67
|
+
result = run("git status --porcelain")
|
|
68
|
+
if result.stdout.strip():
|
|
69
|
+
print("ERROR: Working tree has uncommitted changes", file=sys.stderr)
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
if require_master:
|
|
73
|
+
# Check we're on master
|
|
74
|
+
result = run("git branch --show-current")
|
|
75
|
+
branch = result.stdout.strip()
|
|
76
|
+
if branch not in ("master", "main"):
|
|
77
|
+
print(
|
|
78
|
+
f"ERROR: Must be on master or main branch, currently on '{branch}'",
|
|
79
|
+
file=sys.stderr,
|
|
80
|
+
)
|
|
81
|
+
sys.exit(1)
|
|
82
|
+
|
|
83
|
+
# Check we're up to date with remote
|
|
84
|
+
run("git fetch origin")
|
|
85
|
+
result = run(
|
|
86
|
+
"git rev-list HEAD...origin/master --count 2>/dev/null || git rev-list HEAD...origin/main --count",
|
|
87
|
+
check=False,
|
|
88
|
+
)
|
|
89
|
+
if result.stdout.strip() != "0":
|
|
90
|
+
print("ERROR: Branch is not up to date with remote", file=sys.stderr)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def update_version_in_file(
|
|
95
|
+
file_path: Path, version: str, pattern: str, replacement: str
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Update version string in a file."""
|
|
98
|
+
content = file_path.read_text()
|
|
99
|
+
new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
|
|
100
|
+
if content == new_content:
|
|
101
|
+
print(f"WARNING: No version replacement made in {file_path}", file=sys.stderr)
|
|
102
|
+
file_path.write_text(new_content)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def prepare(version: str, message_file: Path | None = None) -> None:
|
|
106
|
+
"""Prepare a release: create branch, update versions, push, create PR."""
|
|
107
|
+
validate_version(version)
|
|
108
|
+
validate_git_state(require_master=True)
|
|
109
|
+
|
|
110
|
+
# Read release notes if provided
|
|
111
|
+
release_notes = None
|
|
112
|
+
if message_file:
|
|
113
|
+
if not message_file.exists():
|
|
114
|
+
print(f"ERROR: Message file not found: {message_file}", file=sys.stderr)
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
release_notes = message_file.read_text().strip()
|
|
117
|
+
|
|
118
|
+
repo_root = get_repo_root()
|
|
119
|
+
branch_name = f"release/v{version}"
|
|
120
|
+
|
|
121
|
+
# Create release branch
|
|
122
|
+
print(f"Creating branch {branch_name}...")
|
|
123
|
+
run(f"git checkout -b {branch_name}")
|
|
124
|
+
|
|
125
|
+
# Update pyproject.toml
|
|
126
|
+
pyproject = repo_root / "pyproject.toml"
|
|
127
|
+
print(f"Updating {pyproject}...")
|
|
128
|
+
update_version_in_file(
|
|
129
|
+
pyproject,
|
|
130
|
+
version,
|
|
131
|
+
r'^version\s*=\s*"[^"]*"',
|
|
132
|
+
f'version = "{version}"',
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Update __init__.py if it exists with __version__
|
|
136
|
+
init_file = get_package_init(repo_root)
|
|
137
|
+
if init_file:
|
|
138
|
+
print(f"Updating {init_file}...")
|
|
139
|
+
update_version_in_file(
|
|
140
|
+
init_file,
|
|
141
|
+
version,
|
|
142
|
+
r'^__version__\s*=\s*"[^"]*"',
|
|
143
|
+
f'__version__ = "{version}"',
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Commit with release notes or default message
|
|
147
|
+
print("Committing version bump...")
|
|
148
|
+
if release_notes:
|
|
149
|
+
commit_msg = f"release: v{version}\n\n{release_notes}"
|
|
150
|
+
else:
|
|
151
|
+
commit_msg = f"release: bump version to {version}"
|
|
152
|
+
|
|
153
|
+
# Use a temp file for the commit message to handle multiline properly
|
|
154
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
155
|
+
f.write(commit_msg)
|
|
156
|
+
commit_msg_file = f.name
|
|
157
|
+
try:
|
|
158
|
+
run(f'git add -A && git commit -F "{commit_msg_file}"')
|
|
159
|
+
finally:
|
|
160
|
+
Path(commit_msg_file).unlink()
|
|
161
|
+
|
|
162
|
+
# Push
|
|
163
|
+
print(f"Pushing {branch_name}...")
|
|
164
|
+
run(f"git push -u origin {branch_name}")
|
|
165
|
+
|
|
166
|
+
# Create PR with release notes as body
|
|
167
|
+
print("Creating pull request...")
|
|
168
|
+
pr_body = release_notes if release_notes else f"Bump version to {version}"
|
|
169
|
+
|
|
170
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
|
|
171
|
+
f.write(pr_body)
|
|
172
|
+
pr_body_file = f.name
|
|
173
|
+
try:
|
|
174
|
+
result = run(
|
|
175
|
+
f'gh pr create --title "Release v{version}" --body-file "{pr_body_file}"',
|
|
176
|
+
check=False,
|
|
177
|
+
)
|
|
178
|
+
finally:
|
|
179
|
+
Path(pr_body_file).unlink()
|
|
180
|
+
|
|
181
|
+
if result.returncode != 0:
|
|
182
|
+
print(f"\nTo create PR manually:\n gh pr create --title 'Release v{version}'")
|
|
183
|
+
|
|
184
|
+
print(
|
|
185
|
+
f"\nRelease branch ready. After PR is merged, run:\n ./maintenance/release.py tag {version}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def tag(version: str) -> None:
|
|
190
|
+
"""Tag a release after PR is merged."""
|
|
191
|
+
validate_version(version)
|
|
192
|
+
|
|
193
|
+
# Checkout master and pull
|
|
194
|
+
print("Checking out master...")
|
|
195
|
+
run("git checkout master || git checkout main")
|
|
196
|
+
run("git pull")
|
|
197
|
+
|
|
198
|
+
validate_git_state(require_master=True)
|
|
199
|
+
|
|
200
|
+
tag_name = f"v{version}"
|
|
201
|
+
|
|
202
|
+
# Check tag doesn't already exist
|
|
203
|
+
result = run(f"git tag -l {tag_name}")
|
|
204
|
+
if result.stdout.strip():
|
|
205
|
+
print(f"ERROR: Tag {tag_name} already exists", file=sys.stderr)
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
# Create and push tag
|
|
209
|
+
print(f"Creating tag {tag_name}...")
|
|
210
|
+
run(f"git tag {tag_name}")
|
|
211
|
+
print(f"Pushing tag {tag_name}...")
|
|
212
|
+
run(f"git push origin {tag_name}")
|
|
213
|
+
|
|
214
|
+
print(f"\nRelease {tag_name} tagged and pushed.")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main() -> None:
|
|
218
|
+
parser = argparse.ArgumentParser(description="Release preparation and tagging script")
|
|
219
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
220
|
+
|
|
221
|
+
# prepare subcommand
|
|
222
|
+
prepare_parser = subparsers.add_parser("prepare", help="Create release branch and PR")
|
|
223
|
+
prepare_parser.add_argument("version", help="Version number (X.Y.Z)")
|
|
224
|
+
prepare_parser.add_argument(
|
|
225
|
+
"--message-file", "-m",
|
|
226
|
+
type=Path,
|
|
227
|
+
help="File containing release notes for commit message and PR body",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# tag subcommand
|
|
231
|
+
tag_parser = subparsers.add_parser("tag", help="Tag after PR is merged")
|
|
232
|
+
tag_parser.add_argument("version", help="Version number (X.Y.Z)")
|
|
233
|
+
|
|
234
|
+
args = parser.parse_args()
|
|
235
|
+
|
|
236
|
+
if args.command == "prepare":
|
|
237
|
+
prepare(args.version, args.message_file)
|
|
238
|
+
elif args.command == "tag":
|
|
239
|
+
tag(args.version)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
main()
|
|
@@ -12,10 +12,11 @@ All operations are still async to maintain interface compatibility.
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
|
-
from typing import
|
|
15
|
+
from typing import Any
|
|
16
16
|
|
|
17
17
|
from julee.domain.models.assembly import Assembly
|
|
18
18
|
from julee.domain.repositories.assembly import AssemblyRepository
|
|
19
|
+
|
|
19
20
|
from .base import MemoryRepositoryMixin
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
@@ -34,11 +35,11 @@ class MemoryAssemblyRepository(AssemblyRepository, MemoryRepositoryMixin[Assembl
|
|
|
34
35
|
"""Initialize repository with empty in-memory storage."""
|
|
35
36
|
self.logger = logger
|
|
36
37
|
self.entity_name = "Assembly"
|
|
37
|
-
self.storage_dict:
|
|
38
|
+
self.storage_dict: dict[str, Assembly] = {}
|
|
38
39
|
|
|
39
40
|
logger.debug("Initializing MemoryAssemblyRepository")
|
|
40
41
|
|
|
41
|
-
async def get(self, assembly_id: str) ->
|
|
42
|
+
async def get(self, assembly_id: str) -> Assembly | None:
|
|
42
43
|
"""Retrieve an assembly by ID.
|
|
43
44
|
|
|
44
45
|
Args:
|
|
@@ -65,7 +66,7 @@ class MemoryAssemblyRepository(AssemblyRepository, MemoryRepositoryMixin[Assembl
|
|
|
65
66
|
"""
|
|
66
67
|
return self.generate_entity_id("assembly")
|
|
67
68
|
|
|
68
|
-
async def get_many(self, assembly_ids:
|
|
69
|
+
async def get_many(self, assembly_ids: list[str]) -> dict[str, Assembly | None]:
|
|
69
70
|
"""Retrieve multiple assemblies by ID.
|
|
70
71
|
|
|
71
72
|
Args:
|
|
@@ -77,7 +78,7 @@ class MemoryAssemblyRepository(AssemblyRepository, MemoryRepositoryMixin[Assembl
|
|
|
77
78
|
return self.get_many_entities(assembly_ids)
|
|
78
79
|
|
|
79
80
|
def _add_entity_specific_log_data(
|
|
80
|
-
self, entity: Assembly, log_data:
|
|
81
|
+
self, entity: Assembly, log_data: dict[str, Any]
|
|
81
82
|
) -> None:
|
|
82
83
|
"""Add assembly-specific data to log entries."""
|
|
83
84
|
super()._add_entity_specific_log_data(entity, log_data)
|
|
@@ -14,7 +14,7 @@ avoided. All operations are still async to maintain interface compatibility.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import Any
|
|
18
18
|
|
|
19
19
|
from julee.domain.models.assembly_specification import (
|
|
20
20
|
AssemblySpecification,
|
|
@@ -22,6 +22,7 @@ from julee.domain.models.assembly_specification import (
|
|
|
22
22
|
from julee.domain.repositories.assembly_specification import (
|
|
23
23
|
AssemblySpecificationRepository,
|
|
24
24
|
)
|
|
25
|
+
|
|
25
26
|
from .base import MemoryRepositoryMixin
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
@@ -48,13 +49,11 @@ class MemoryAssemblySpecificationRepository(
|
|
|
48
49
|
"""Initialize repository with empty in-memory storage."""
|
|
49
50
|
self.logger = logger
|
|
50
51
|
self.entity_name = "AssemblySpecification"
|
|
51
|
-
self.storage_dict:
|
|
52
|
+
self.storage_dict: dict[str, AssemblySpecification] = {}
|
|
52
53
|
|
|
53
54
|
logger.debug("Initializing MemoryAssemblySpecificationRepository")
|
|
54
55
|
|
|
55
|
-
async def get(
|
|
56
|
-
self, assembly_specification_id: str
|
|
57
|
-
) -> Optional[AssemblySpecification]:
|
|
56
|
+
async def get(self, assembly_specification_id: str) -> AssemblySpecification | None:
|
|
58
57
|
"""Retrieve an assembly specification by ID.
|
|
59
58
|
|
|
60
59
|
Args:
|
|
@@ -82,8 +81,8 @@ class MemoryAssemblySpecificationRepository(
|
|
|
82
81
|
return self.generate_entity_id("spec")
|
|
83
82
|
|
|
84
83
|
async def get_many(
|
|
85
|
-
self, assembly_specification_ids:
|
|
86
|
-
) ->
|
|
84
|
+
self, assembly_specification_ids: list[str]
|
|
85
|
+
) -> dict[str, AssemblySpecification | None]:
|
|
87
86
|
"""Retrieve multiple assembly specifications by ID.
|
|
88
87
|
|
|
89
88
|
Args:
|
|
@@ -96,7 +95,7 @@ class MemoryAssemblySpecificationRepository(
|
|
|
96
95
|
"""
|
|
97
96
|
return self.get_many_entities(assembly_specification_ids)
|
|
98
97
|
|
|
99
|
-
async def list_all(self) ->
|
|
98
|
+
async def list_all(self) -> list[AssemblySpecification]:
|
|
100
99
|
"""List all assembly specifications.
|
|
101
100
|
|
|
102
101
|
Returns:
|
|
@@ -118,7 +117,7 @@ class MemoryAssemblySpecificationRepository(
|
|
|
118
117
|
return specifications
|
|
119
118
|
|
|
120
119
|
def _add_entity_specific_log_data(
|
|
121
|
-
self, entity: AssemblySpecification, log_data:
|
|
120
|
+
self, entity: AssemblySpecification, log_data: dict[str, Any]
|
|
122
121
|
) -> None:
|
|
123
122
|
"""Add assembly specification-specific data to log entries."""
|
|
124
123
|
super()._add_entity_specific_log_data(entity, log_data)
|
|
@@ -20,7 +20,8 @@ Classes using this mixin must provide:
|
|
|
20
20
|
|
|
21
21
|
import uuid
|
|
22
22
|
from datetime import datetime, timezone
|
|
23
|
-
from typing import
|
|
23
|
+
from typing import Any, Generic, TypeVar
|
|
24
|
+
|
|
24
25
|
from pydantic import BaseModel
|
|
25
26
|
|
|
26
27
|
T = TypeVar("T", bound=BaseModel)
|
|
@@ -45,11 +46,11 @@ class MemoryRepositoryMixin(Generic[T]):
|
|
|
45
46
|
"""
|
|
46
47
|
|
|
47
48
|
# Type annotations for attributes that implementing classes must provide
|
|
48
|
-
storage_dict:
|
|
49
|
+
storage_dict: dict[str, T]
|
|
49
50
|
entity_name: str
|
|
50
51
|
logger: Any # logging.Logger, but avoiding import
|
|
51
52
|
|
|
52
|
-
def get_entity(self, entity_id: str) ->
|
|
53
|
+
def get_entity(self, entity_id: str) -> T | None:
|
|
53
54
|
"""Get an entity from memory storage with standardized logging.
|
|
54
55
|
|
|
55
56
|
Args:
|
|
@@ -84,7 +85,7 @@ class MemoryRepositoryMixin(Generic[T]):
|
|
|
84
85
|
|
|
85
86
|
return entity
|
|
86
87
|
|
|
87
|
-
def get_many_entities(self, entity_ids:
|
|
88
|
+
def get_many_entities(self, entity_ids: list[str]) -> dict[str, T | None]:
|
|
88
89
|
"""Get multiple entities from memory storage with standardized
|
|
89
90
|
logging.
|
|
90
91
|
|
|
@@ -103,7 +104,7 @@ class MemoryRepositoryMixin(Generic[T]):
|
|
|
103
104
|
},
|
|
104
105
|
)
|
|
105
106
|
|
|
106
|
-
result:
|
|
107
|
+
result: dict[str, T | None] = {}
|
|
107
108
|
found_count = 0
|
|
108
109
|
|
|
109
110
|
for entity_id in entity_ids:
|
|
@@ -160,7 +161,7 @@ class MemoryRepositoryMixin(Generic[T]):
|
|
|
160
161
|
extra=success_extra,
|
|
161
162
|
)
|
|
162
163
|
|
|
163
|
-
def generate_entity_id(self, prefix:
|
|
164
|
+
def generate_entity_id(self, prefix: str | None = None) -> str:
|
|
164
165
|
"""Generate a unique entity ID with consistent format.
|
|
165
166
|
|
|
166
167
|
Args:
|
|
@@ -196,14 +197,14 @@ class MemoryRepositoryMixin(Generic[T]):
|
|
|
196
197
|
hasattr(entity, "created_at")
|
|
197
198
|
and getattr(entity, "created_at", None) is None
|
|
198
199
|
):
|
|
199
|
-
|
|
200
|
+
entity.created_at = now
|
|
200
201
|
|
|
201
202
|
# Always update updated_at
|
|
202
203
|
if hasattr(entity, "updated_at"):
|
|
203
|
-
|
|
204
|
+
entity.updated_at = now
|
|
204
205
|
|
|
205
206
|
def _add_entity_specific_log_data(
|
|
206
|
-
self, entity: T, log_data:
|
|
207
|
+
self, entity: T, log_data: dict[str, Any]
|
|
207
208
|
) -> None:
|
|
208
209
|
"""Add entity-specific data to log entries for richer logging.
|
|
209
210
|
|
|
@@ -216,12 +217,12 @@ class MemoryRepositoryMixin(Generic[T]):
|
|
|
216
217
|
"""
|
|
217
218
|
# Default implementation adds basic model info
|
|
218
219
|
if hasattr(entity, "status"):
|
|
219
|
-
status =
|
|
220
|
+
status = entity.status
|
|
220
221
|
log_data["status"] = (
|
|
221
222
|
status.value if hasattr(status, "value") else str(status)
|
|
222
223
|
)
|
|
223
224
|
|
|
224
225
|
if hasattr(entity, "updated_at"):
|
|
225
|
-
updated_at =
|
|
226
|
+
updated_at = entity.updated_at
|
|
226
227
|
if updated_at:
|
|
227
228
|
log_data["updated_at"] = updated_at.isoformat()
|
|
@@ -14,13 +14,14 @@ All operations are still async to maintain interface compatibility.
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import io
|
|
16
16
|
import logging
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import Any
|
|
18
18
|
|
|
19
|
-
from julee.domain.models.document import Document
|
|
20
19
|
from julee.domain.models.custom_fields.content_stream import (
|
|
21
20
|
ContentStream,
|
|
22
21
|
)
|
|
22
|
+
from julee.domain.models.document import Document
|
|
23
23
|
from julee.domain.repositories.document import DocumentRepository
|
|
24
|
+
|
|
24
25
|
from .base import MemoryRepositoryMixin
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
@@ -41,11 +42,11 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
41
42
|
"""Initialize repository with empty in-memory storage."""
|
|
42
43
|
self.logger = logger
|
|
43
44
|
self.entity_name = "Document"
|
|
44
|
-
self.storage_dict:
|
|
45
|
+
self.storage_dict: dict[str, Document] = {}
|
|
45
46
|
|
|
46
47
|
logger.debug("Initializing MemoryDocumentRepository")
|
|
47
48
|
|
|
48
|
-
async def get(self, document_id: str) ->
|
|
49
|
+
async def get(self, document_id: str) -> Document | None:
|
|
49
50
|
"""Retrieve a document with metadata and content.
|
|
50
51
|
|
|
51
52
|
Args:
|
|
@@ -59,46 +60,52 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
59
60
|
async def save(self, document: Document) -> None:
|
|
60
61
|
"""Save a document with its content and metadata.
|
|
61
62
|
|
|
62
|
-
If the document has
|
|
63
|
-
|
|
63
|
+
If the document has content_bytes, it will be normalized to bytes
|
|
64
|
+
(encoding str as UTF-8), converted to a ContentStream and the
|
|
65
|
+
content hash will be calculated automatically.
|
|
64
66
|
|
|
65
67
|
Args:
|
|
66
68
|
document: Document object to save
|
|
67
69
|
|
|
68
70
|
Raises:
|
|
69
|
-
ValueError: If document has no content or
|
|
71
|
+
ValueError: If document has no content or content_bytes
|
|
72
|
+
TypeError: If content_bytes is not bytes or str
|
|
70
73
|
"""
|
|
71
74
|
# Handle content_string conversion (only if no content provided)
|
|
72
|
-
if document.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
if document.content_bytes is not None:
|
|
76
|
+
if isinstance(document.content_bytes, str):
|
|
77
|
+
raw_bytes = document.content_bytes.encode("utf-8")
|
|
78
|
+
elif isinstance(document.content_bytes, bytes):
|
|
79
|
+
raw_bytes = document.content_bytes
|
|
80
|
+
else:
|
|
81
|
+
raise TypeError("content_bytes must be of type 'bytes' or 'str'.")
|
|
82
|
+
|
|
83
|
+
content_stream = ContentStream(io.BytesIO(raw_bytes))
|
|
77
84
|
|
|
78
85
|
# Calculate content hash
|
|
79
|
-
content_hash = hashlib.sha256(
|
|
86
|
+
content_hash = hashlib.sha256(raw_bytes).hexdigest()
|
|
80
87
|
|
|
81
88
|
# Create new document with ContentStream and calculated hash
|
|
82
89
|
document = document.model_copy(
|
|
83
90
|
update={
|
|
84
91
|
"content": content_stream,
|
|
85
92
|
"content_multihash": content_hash,
|
|
86
|
-
"size_bytes": len(
|
|
93
|
+
"size_bytes": len(raw_bytes),
|
|
87
94
|
}
|
|
88
95
|
)
|
|
89
96
|
|
|
90
97
|
self.logger.debug(
|
|
91
|
-
"Converted
|
|
98
|
+
"Converted content_bytes to ContentStream for document save",
|
|
92
99
|
extra={
|
|
93
100
|
"document_id": document.document_id,
|
|
94
101
|
"content_hash": content_hash,
|
|
95
|
-
"content_length": len(
|
|
102
|
+
"content_length": len(raw_bytes),
|
|
96
103
|
},
|
|
97
104
|
)
|
|
98
105
|
|
|
99
106
|
# Create a copy without content_string (content saved
|
|
100
107
|
# in separate content-addressable storage)
|
|
101
|
-
document_for_storage = document.model_copy(update={"
|
|
108
|
+
document_for_storage = document.model_copy(update={"content_bytes": None})
|
|
102
109
|
self.save_entity(document_for_storage, "document_id")
|
|
103
110
|
|
|
104
111
|
async def generate_id(self) -> str:
|
|
@@ -109,7 +116,7 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
109
116
|
"""
|
|
110
117
|
return self.generate_entity_id("doc")
|
|
111
118
|
|
|
112
|
-
async def get_many(self, document_ids:
|
|
119
|
+
async def get_many(self, document_ids: list[str]) -> dict[str, Document | None]:
|
|
113
120
|
"""Retrieve multiple documents by ID.
|
|
114
121
|
|
|
115
122
|
Args:
|
|
@@ -120,7 +127,7 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
120
127
|
"""
|
|
121
128
|
return self.get_many_entities(document_ids)
|
|
122
129
|
|
|
123
|
-
async def list_all(self) ->
|
|
130
|
+
async def list_all(self) -> list[Document]:
|
|
124
131
|
"""List all documents.
|
|
125
132
|
|
|
126
133
|
Returns:
|
|
@@ -142,7 +149,7 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
142
149
|
return documents
|
|
143
150
|
|
|
144
151
|
def _add_entity_specific_log_data(
|
|
145
|
-
self, entity: Document, log_data:
|
|
152
|
+
self, entity: Document, log_data: dict[str, Any]
|
|
146
153
|
) -> None:
|
|
147
154
|
"""Add document-specific data to log entries."""
|
|
148
155
|
super()._add_entity_specific_log_data(entity, log_data)
|
|
@@ -13,12 +13,13 @@ All operations are still async to maintain interface compatibility.
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
|
-
from typing import
|
|
16
|
+
from typing import Any
|
|
17
17
|
|
|
18
18
|
from julee.domain.models.policy import DocumentPolicyValidation
|
|
19
19
|
from julee.domain.repositories.document_policy_validation import (
|
|
20
20
|
DocumentPolicyValidationRepository,
|
|
21
21
|
)
|
|
22
|
+
|
|
22
23
|
from .base import MemoryRepositoryMixin
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
@@ -41,11 +42,11 @@ class MemoryDocumentPolicyValidationRepository(
|
|
|
41
42
|
"""Initialize repository with empty in-memory storage."""
|
|
42
43
|
self.logger = logger
|
|
43
44
|
self.entity_name = "DocumentPolicyValidation"
|
|
44
|
-
self.storage_dict:
|
|
45
|
+
self.storage_dict: dict[str, DocumentPolicyValidation] = {}
|
|
45
46
|
|
|
46
47
|
logger.debug("Initializing MemoryDocumentPolicyValidationRepository")
|
|
47
48
|
|
|
48
|
-
async def get(self, validation_id: str) ->
|
|
49
|
+
async def get(self, validation_id: str) -> DocumentPolicyValidation | None:
|
|
49
50
|
"""Retrieve a document policy validation by ID.
|
|
50
51
|
|
|
51
52
|
Args:
|
|
@@ -73,8 +74,8 @@ class MemoryDocumentPolicyValidationRepository(
|
|
|
73
74
|
return self.generate_entity_id("validation")
|
|
74
75
|
|
|
75
76
|
async def get_many(
|
|
76
|
-
self, validation_ids:
|
|
77
|
-
) ->
|
|
77
|
+
self, validation_ids: list[str]
|
|
78
|
+
) -> dict[str, DocumentPolicyValidation | None]:
|
|
78
79
|
"""Retrieve multiple document policy validations by ID.
|
|
79
80
|
|
|
80
81
|
Args:
|
|
@@ -87,7 +88,7 @@ class MemoryDocumentPolicyValidationRepository(
|
|
|
87
88
|
return self.get_many_entities(validation_ids)
|
|
88
89
|
|
|
89
90
|
def _add_entity_specific_log_data(
|
|
90
|
-
self, entity: DocumentPolicyValidation, log_data:
|
|
91
|
+
self, entity: DocumentPolicyValidation, log_data: dict[str, Any]
|
|
91
92
|
) -> None:
|
|
92
93
|
"""Add validation-specific data to log entries."""
|
|
93
94
|
super()._add_entity_specific_log_data(entity, log_data)
|
|
@@ -14,7 +14,7 @@ interface compatibility.
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
-
from typing import
|
|
17
|
+
from typing import Any
|
|
18
18
|
|
|
19
19
|
from julee.domain.models.knowledge_service_config import (
|
|
20
20
|
KnowledgeServiceConfig,
|
|
@@ -22,6 +22,7 @@ from julee.domain.models.knowledge_service_config import (
|
|
|
22
22
|
from julee.domain.repositories.knowledge_service_config import (
|
|
23
23
|
KnowledgeServiceConfigRepository,
|
|
24
24
|
)
|
|
25
|
+
|
|
25
26
|
from .base import MemoryRepositoryMixin
|
|
26
27
|
|
|
27
28
|
logger = logging.getLogger(__name__)
|
|
@@ -48,11 +49,11 @@ class MemoryKnowledgeServiceConfigRepository(
|
|
|
48
49
|
"""Initialize repository with empty in-memory storage."""
|
|
49
50
|
self.logger = logger
|
|
50
51
|
self.entity_name = "KnowledgeServiceConfig"
|
|
51
|
-
self.storage_dict:
|
|
52
|
+
self.storage_dict: dict[str, KnowledgeServiceConfig] = {}
|
|
52
53
|
|
|
53
54
|
logger.debug("Initializing MemoryKnowledgeServiceConfigRepository")
|
|
54
55
|
|
|
55
|
-
async def get(self, knowledge_service_id: str) ->
|
|
56
|
+
async def get(self, knowledge_service_id: str) -> KnowledgeServiceConfig | None:
|
|
56
57
|
"""Retrieve a knowledge service configuration by ID.
|
|
57
58
|
|
|
58
59
|
Args:
|
|
@@ -80,8 +81,8 @@ class MemoryKnowledgeServiceConfigRepository(
|
|
|
80
81
|
return self.generate_entity_id("ks")
|
|
81
82
|
|
|
82
83
|
async def get_many(
|
|
83
|
-
self, knowledge_service_ids:
|
|
84
|
-
) ->
|
|
84
|
+
self, knowledge_service_ids: list[str]
|
|
85
|
+
) -> dict[str, KnowledgeServiceConfig | None]:
|
|
85
86
|
"""Retrieve multiple knowledge service configs by ID.
|
|
86
87
|
|
|
87
88
|
Args:
|
|
@@ -94,7 +95,7 @@ class MemoryKnowledgeServiceConfigRepository(
|
|
|
94
95
|
"""
|
|
95
96
|
return self.get_many_entities(knowledge_service_ids)
|
|
96
97
|
|
|
97
|
-
async def list_all(self) ->
|
|
98
|
+
async def list_all(self) -> list[KnowledgeServiceConfig]:
|
|
98
99
|
"""List all knowledge service configurations.
|
|
99
100
|
|
|
100
101
|
Returns:
|
|
@@ -116,7 +117,7 @@ class MemoryKnowledgeServiceConfigRepository(
|
|
|
116
117
|
return configs
|
|
117
118
|
|
|
118
119
|
def _add_entity_specific_log_data(
|
|
119
|
-
self, entity: KnowledgeServiceConfig, log_data:
|
|
120
|
+
self, entity: KnowledgeServiceConfig, log_data: dict[str, Any]
|
|
120
121
|
) -> None:
|
|
121
122
|
"""Add knowledge service config-specific data to log entries."""
|
|
122
123
|
super()._add_entity_specific_log_data(entity, log_data)
|