julee 0.1.2__py3-none-any.whl → 0.1.3__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 +5 -4
- 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 +82 -0
- julee/docs/sphinx_hcd/accelerators.py +1078 -0
- julee/docs/sphinx_hcd/apps.py +499 -0
- julee/docs/sphinx_hcd/config.py +148 -0
- julee/docs/sphinx_hcd/epics.py +448 -0
- julee/docs/sphinx_hcd/integrations.py +306 -0
- julee/docs/sphinx_hcd/journeys.py +783 -0
- julee/docs/sphinx_hcd/personas.py +435 -0
- julee/docs/sphinx_hcd/stories.py +932 -0
- julee/docs/sphinx_hcd/utils.py +180 -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 +12 -10
- julee/domain/models/document/tests/factories.py +3 -2
- julee/domain/models/document/tests/test_document.py +6 -3
- 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 +13 -12
- julee/domain/use_cases/initialize_system_data.py +13 -13
- 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/maintenance/__init__.py +1 -0
- julee/maintenance/release.py +188 -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 +8 -7
- 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 +6 -4
- 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 +11 -11
- 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 +7 -6
- 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.3.dist-info}/METADATA +2 -1
- julee-0.1.3.dist-info/RECORD +197 -0
- julee-0.1.2.dist-info/RECORD +0 -161
- {julee-0.1.2.dist-info → julee-0.1.3.dist-info}/WHEEL +0 -0
- {julee-0.1.2.dist-info → julee-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {julee-0.1.2.dist-info → julee-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -8,35 +8,35 @@ following the Clean Architecture principles.
|
|
|
8
8
|
|
|
9
9
|
import io
|
|
10
10
|
import json
|
|
11
|
-
import pytest
|
|
12
|
-
|
|
13
|
-
from unittest.mock import AsyncMock
|
|
14
11
|
from datetime import datetime, timezone
|
|
12
|
+
from unittest.mock import AsyncMock
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
15
|
|
|
16
|
-
from julee.domain.use_cases import ExtractAssembleDataUseCase
|
|
17
16
|
from julee.domain.models import (
|
|
18
17
|
Assembly,
|
|
18
|
+
AssemblySpecification,
|
|
19
|
+
AssemblySpecificationStatus,
|
|
19
20
|
AssemblyStatus,
|
|
21
|
+
ContentStream,
|
|
20
22
|
Document,
|
|
21
23
|
DocumentStatus,
|
|
22
|
-
ContentStream,
|
|
23
|
-
AssemblySpecification,
|
|
24
|
-
AssemblySpecificationStatus,
|
|
25
|
-
KnowledgeServiceQuery,
|
|
26
24
|
KnowledgeServiceConfig,
|
|
25
|
+
KnowledgeServiceQuery,
|
|
27
26
|
)
|
|
28
27
|
from julee.domain.models.knowledge_service_config import ServiceApi
|
|
28
|
+
from julee.domain.use_cases import ExtractAssembleDataUseCase
|
|
29
29
|
from julee.repositories.memory import (
|
|
30
|
-
MemoryDocumentRepository,
|
|
31
30
|
MemoryAssemblyRepository,
|
|
32
31
|
MemoryAssemblySpecificationRepository,
|
|
32
|
+
MemoryDocumentRepository,
|
|
33
33
|
MemoryKnowledgeServiceConfigRepository,
|
|
34
34
|
MemoryKnowledgeServiceQueryRepository,
|
|
35
35
|
)
|
|
36
|
+
from julee.services.knowledge_service import QueryResult
|
|
36
37
|
from julee.services.knowledge_service.memory import (
|
|
37
38
|
MemoryKnowledgeService,
|
|
38
39
|
)
|
|
39
|
-
from julee.services.knowledge_service import QueryResult
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class TestExtractAssembleDataUseCase:
|
|
@@ -86,7 +86,7 @@ def fixture_configs() -> list[dict]:
|
|
|
86
86
|
|
|
87
87
|
assert fixture_path.exists(), f"Fixture file not found: {fixture_path}"
|
|
88
88
|
|
|
89
|
-
with open(fixture_path,
|
|
89
|
+
with open(fixture_path, encoding="utf-8") as f:
|
|
90
90
|
fixture_data = yaml.safe_load(f)
|
|
91
91
|
|
|
92
92
|
assert "knowledge_services" in fixture_data
|
|
@@ -320,7 +320,7 @@ class TestYamlFixtureIntegration:
|
|
|
320
320
|
assert fixture_path.exists(), f"Fixture file not found: {fixture_path}"
|
|
321
321
|
|
|
322
322
|
# Verify file can be parsed
|
|
323
|
-
with open(fixture_path,
|
|
323
|
+
with open(fixture_path, encoding="utf-8") as f:
|
|
324
324
|
fixture_data = yaml.safe_load(f)
|
|
325
325
|
|
|
326
326
|
# Verify structure
|
|
@@ -7,38 +7,38 @@ following the Clean Architecture principles.
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import io
|
|
10
|
-
import pytest
|
|
11
|
-
from unittest.mock import AsyncMock
|
|
12
10
|
from datetime import datetime, timezone
|
|
13
|
-
from
|
|
11
|
+
from unittest.mock import AsyncMock
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
import pytest
|
|
14
|
+
from pydantic import ValidationError
|
|
16
15
|
|
|
17
16
|
from julee.domain.models import (
|
|
17
|
+
ContentStream,
|
|
18
18
|
Document,
|
|
19
19
|
DocumentStatus,
|
|
20
|
-
ContentStream,
|
|
21
20
|
KnowledgeServiceConfig,
|
|
22
21
|
KnowledgeServiceQuery,
|
|
23
22
|
)
|
|
23
|
+
from julee.domain.models.knowledge_service_config import ServiceApi
|
|
24
24
|
from julee.domain.models.policy import (
|
|
25
|
-
Policy,
|
|
26
|
-
PolicyStatus,
|
|
27
25
|
DocumentPolicyValidation,
|
|
28
26
|
DocumentPolicyValidationStatus,
|
|
27
|
+
Policy,
|
|
28
|
+
PolicyStatus,
|
|
29
29
|
)
|
|
30
|
-
from julee.domain.
|
|
30
|
+
from julee.domain.use_cases import ValidateDocumentUseCase
|
|
31
31
|
from julee.repositories.memory import (
|
|
32
|
+
MemoryDocumentPolicyValidationRepository,
|
|
32
33
|
MemoryDocumentRepository,
|
|
33
|
-
MemoryKnowledgeServiceQueryRepository,
|
|
34
34
|
MemoryKnowledgeServiceConfigRepository,
|
|
35
|
+
MemoryKnowledgeServiceQueryRepository,
|
|
35
36
|
MemoryPolicyRepository,
|
|
36
|
-
MemoryDocumentPolicyValidationRepository,
|
|
37
37
|
)
|
|
38
|
+
from julee.services.knowledge_service import QueryResult
|
|
38
39
|
from julee.services.knowledge_service.memory import (
|
|
39
40
|
MemoryKnowledgeService,
|
|
40
41
|
)
|
|
41
|
-
from julee.services.knowledge_service import QueryResult
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class TestValidateDocumentUseCase:
|
|
@@ -11,8 +11,8 @@ import hashlib
|
|
|
11
11
|
import io
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
+
from collections.abc import Callable
|
|
14
15
|
from datetime import datetime
|
|
15
|
-
from typing import Callable, Dict, List, Tuple
|
|
16
16
|
|
|
17
17
|
import multihash
|
|
18
18
|
|
|
@@ -397,13 +397,13 @@ class ValidateDocumentUseCase:
|
|
|
397
397
|
@try_use_case_step("all_queries_retrieval")
|
|
398
398
|
async def _retrieve_all_queries(
|
|
399
399
|
self, policy: Policy
|
|
400
|
-
) ->
|
|
400
|
+
) -> dict[str, KnowledgeServiceQuery]:
|
|
401
401
|
"""Retrieve all knowledge service queries needed for validation and
|
|
402
402
|
transformation."""
|
|
403
403
|
all_queries = {}
|
|
404
404
|
|
|
405
405
|
# Get validation queries
|
|
406
|
-
for query_id,
|
|
406
|
+
for query_id, _required_score in policy.validation_scores:
|
|
407
407
|
query = await self.knowledge_service_query_repo.get(query_id)
|
|
408
408
|
if not query:
|
|
409
409
|
raise ValueError(f"Validation query not found: {query_id}")
|
|
@@ -423,8 +423,8 @@ class ValidateDocumentUseCase:
|
|
|
423
423
|
async def _register_document_with_services(
|
|
424
424
|
self,
|
|
425
425
|
document: Document,
|
|
426
|
-
queries:
|
|
427
|
-
) ->
|
|
426
|
+
queries: dict[str, KnowledgeServiceQuery],
|
|
427
|
+
) -> dict[str, str]:
|
|
428
428
|
"""
|
|
429
429
|
Register the document with all knowledge services needed for
|
|
430
430
|
validation.
|
|
@@ -464,9 +464,9 @@ class ValidateDocumentUseCase:
|
|
|
464
464
|
self,
|
|
465
465
|
document: Document,
|
|
466
466
|
policy: Policy,
|
|
467
|
-
document_registrations:
|
|
468
|
-
queries:
|
|
469
|
-
) ->
|
|
467
|
+
document_registrations: dict[str, str],
|
|
468
|
+
queries: dict[str, KnowledgeServiceQuery],
|
|
469
|
+
) -> list[tuple[str, int]]:
|
|
470
470
|
"""
|
|
471
471
|
Execute all validation queries and return the actual scores achieved.
|
|
472
472
|
|
|
@@ -528,7 +528,7 @@ class ValidateDocumentUseCase:
|
|
|
528
528
|
|
|
529
529
|
return validation_scores
|
|
530
530
|
|
|
531
|
-
def _extract_score_from_result(self, result_data:
|
|
531
|
+
def _extract_score_from_result(self, result_data: dict) -> int:
|
|
532
532
|
"""
|
|
533
533
|
Extract a numeric score from the knowledge service query result.
|
|
534
534
|
|
|
@@ -551,8 +551,8 @@ class ValidateDocumentUseCase:
|
|
|
551
551
|
|
|
552
552
|
def _determine_validation_result(
|
|
553
553
|
self,
|
|
554
|
-
actual_scores:
|
|
555
|
-
required_scores:
|
|
554
|
+
actual_scores: list[tuple[str, int]],
|
|
555
|
+
required_scores: list[tuple[str, int]],
|
|
556
556
|
) -> bool:
|
|
557
557
|
"""
|
|
558
558
|
Determine if validation passed based on actual vs required scores.
|
|
@@ -591,8 +591,8 @@ class ValidateDocumentUseCase:
|
|
|
591
591
|
self,
|
|
592
592
|
document: Document,
|
|
593
593
|
policy: Policy,
|
|
594
|
-
all_queries:
|
|
595
|
-
document_registrations:
|
|
594
|
+
all_queries: dict[str, KnowledgeServiceQuery],
|
|
595
|
+
document_registrations: dict[str, str],
|
|
596
596
|
) -> Document:
|
|
597
597
|
"""
|
|
598
598
|
Apply transformation queries to a document and return the
|
|
@@ -714,7 +714,7 @@ class ValidateDocumentUseCase:
|
|
|
714
714
|
|
|
715
715
|
return transformed_document
|
|
716
716
|
|
|
717
|
-
def _extract_transformed_content(self, result_data:
|
|
717
|
+
def _extract_transformed_content(self, result_data: dict) -> str:
|
|
718
718
|
"""
|
|
719
719
|
Extract transformed document content from knowledge service result.
|
|
720
720
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Maintenance utilities for julee and julee-based solutions."""
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Release preparation and tagging script.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
release.py prepare X.Y.Z # Create release branch and PR
|
|
7
|
+
release.py tag X.Y.Z # Tag after PR is merged
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run(cmd: str, check: bool = True, capture: bool = True) -> subprocess.CompletedProcess:
|
|
17
|
+
"""Run a shell command."""
|
|
18
|
+
result = subprocess.run(cmd, shell=True, capture_output=capture, text=True)
|
|
19
|
+
if check and result.returncode != 0:
|
|
20
|
+
print(f"ERROR: {cmd}", file=sys.stderr)
|
|
21
|
+
if result.stderr:
|
|
22
|
+
print(result.stderr, file=sys.stderr)
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_repo_root() -> Path:
|
|
28
|
+
"""Get the repository root directory."""
|
|
29
|
+
result = run("git rev-parse --show-toplevel")
|
|
30
|
+
return Path(result.stdout.strip())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_package_init(repo_root: Path) -> Path | None:
|
|
34
|
+
"""Find __init__.py with __version__ in src/ directory."""
|
|
35
|
+
src_dir = repo_root / "src"
|
|
36
|
+
if not src_dir.exists():
|
|
37
|
+
return None
|
|
38
|
+
packages = [p for p in src_dir.iterdir() if p.is_dir() and not p.name.startswith("_")]
|
|
39
|
+
if len(packages) != 1:
|
|
40
|
+
# Multiple packages (bounded contexts) - no single __init__.py to update
|
|
41
|
+
return None
|
|
42
|
+
init_file = packages[0] / "__init__.py"
|
|
43
|
+
if init_file.exists() and "__version__" in init_file.read_text():
|
|
44
|
+
return init_file
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def validate_version(version: str) -> None:
|
|
49
|
+
"""Validate version string format."""
|
|
50
|
+
if not re.match(r"^\d+\.\d+\.\d+$", version):
|
|
51
|
+
print(f"ERROR: Invalid version format '{version}'. Expected X.Y.Z", file=sys.stderr)
|
|
52
|
+
sys.exit(1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def validate_git_state(require_master: bool = True) -> None:
|
|
56
|
+
"""Validate git working tree is clean and on correct branch."""
|
|
57
|
+
# Check for uncommitted changes
|
|
58
|
+
result = run("git status --porcelain")
|
|
59
|
+
if result.stdout.strip():
|
|
60
|
+
print("ERROR: Working tree has uncommitted changes", file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
if require_master:
|
|
64
|
+
# Check we're on master
|
|
65
|
+
result = run("git branch --show-current")
|
|
66
|
+
branch = result.stdout.strip()
|
|
67
|
+
if branch not in ("master", "main"):
|
|
68
|
+
print(f"ERROR: Must be on master or main branch, currently on '{branch}'", file=sys.stderr)
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
# Check we're up to date with remote
|
|
72
|
+
run("git fetch origin")
|
|
73
|
+
result = run("git rev-list HEAD...origin/master --count 2>/dev/null || git rev-list HEAD...origin/main --count", check=False)
|
|
74
|
+
if result.stdout.strip() != "0":
|
|
75
|
+
print("ERROR: Branch is not up to date with remote", file=sys.stderr)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def update_version_in_file(file_path: Path, version: str, pattern: str, replacement: str) -> None:
|
|
80
|
+
"""Update version string in a file."""
|
|
81
|
+
content = file_path.read_text()
|
|
82
|
+
new_content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
|
|
83
|
+
if content == new_content:
|
|
84
|
+
print(f"WARNING: No version replacement made in {file_path}", file=sys.stderr)
|
|
85
|
+
file_path.write_text(new_content)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def prepare(version: str) -> None:
|
|
89
|
+
"""Prepare a release: create branch, update versions, push, create PR."""
|
|
90
|
+
validate_version(version)
|
|
91
|
+
validate_git_state(require_master=True)
|
|
92
|
+
|
|
93
|
+
repo_root = get_repo_root()
|
|
94
|
+
branch_name = f"release/v{version}"
|
|
95
|
+
|
|
96
|
+
# Create release branch
|
|
97
|
+
print(f"Creating branch {branch_name}...")
|
|
98
|
+
run(f"git checkout -b {branch_name}")
|
|
99
|
+
|
|
100
|
+
# Update pyproject.toml
|
|
101
|
+
pyproject = repo_root / "pyproject.toml"
|
|
102
|
+
print(f"Updating {pyproject}...")
|
|
103
|
+
update_version_in_file(
|
|
104
|
+
pyproject,
|
|
105
|
+
version,
|
|
106
|
+
r'^version\s*=\s*"[^"]*"',
|
|
107
|
+
f'version = "{version}"',
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Update __init__.py if it exists with __version__
|
|
111
|
+
init_file = get_package_init(repo_root)
|
|
112
|
+
if init_file:
|
|
113
|
+
print(f"Updating {init_file}...")
|
|
114
|
+
update_version_in_file(
|
|
115
|
+
init_file,
|
|
116
|
+
version,
|
|
117
|
+
r'^__version__\s*=\s*"[^"]*"',
|
|
118
|
+
f'__version__ = "{version}"',
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Commit
|
|
122
|
+
print("Committing version bump...")
|
|
123
|
+
run(f'git add -A && git commit -m "release: bump version to {version}"')
|
|
124
|
+
|
|
125
|
+
# Push
|
|
126
|
+
print(f"Pushing {branch_name}...")
|
|
127
|
+
run(f"git push -u origin {branch_name}")
|
|
128
|
+
|
|
129
|
+
# Create PR
|
|
130
|
+
print("Creating pull request...")
|
|
131
|
+
result = run(
|
|
132
|
+
f'gh pr create --title "Release v{version}" --body "Bump version to {version}"',
|
|
133
|
+
check=False,
|
|
134
|
+
)
|
|
135
|
+
if result.returncode != 0:
|
|
136
|
+
print(f"\nTo create PR manually:\n gh pr create --title 'Release v{version}'")
|
|
137
|
+
|
|
138
|
+
print(f"\nRelease branch ready. After PR is merged, run:\n ./maintenance/release.py tag {version}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def tag(version: str) -> None:
|
|
142
|
+
"""Tag a release after PR is merged."""
|
|
143
|
+
validate_version(version)
|
|
144
|
+
|
|
145
|
+
# Checkout master and pull
|
|
146
|
+
print("Checking out master...")
|
|
147
|
+
run("git checkout master || git checkout main")
|
|
148
|
+
run("git pull")
|
|
149
|
+
|
|
150
|
+
validate_git_state(require_master=True)
|
|
151
|
+
|
|
152
|
+
tag_name = f"v{version}"
|
|
153
|
+
|
|
154
|
+
# Check tag doesn't already exist
|
|
155
|
+
result = run(f"git tag -l {tag_name}")
|
|
156
|
+
if result.stdout.strip():
|
|
157
|
+
print(f"ERROR: Tag {tag_name} already exists", file=sys.stderr)
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
|
|
160
|
+
# Create and push tag
|
|
161
|
+
print(f"Creating tag {tag_name}...")
|
|
162
|
+
run(f"git tag {tag_name}")
|
|
163
|
+
print(f"Pushing tag {tag_name}...")
|
|
164
|
+
run(f"git push origin {tag_name}")
|
|
165
|
+
|
|
166
|
+
print(f"\nRelease {tag_name} tagged and pushed.")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def main() -> None:
|
|
170
|
+
if len(sys.argv) < 3:
|
|
171
|
+
print(__doc__)
|
|
172
|
+
sys.exit(1)
|
|
173
|
+
|
|
174
|
+
command = sys.argv[1]
|
|
175
|
+
version = sys.argv[2]
|
|
176
|
+
|
|
177
|
+
if command == "prepare":
|
|
178
|
+
prepare(version)
|
|
179
|
+
elif command == "tag":
|
|
180
|
+
tag(version)
|
|
181
|
+
else:
|
|
182
|
+
print(f"Unknown command: {command}", file=sys.stderr)
|
|
183
|
+
print(__doc__)
|
|
184
|
+
sys.exit(1)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
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:
|
|
@@ -109,7 +110,7 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
109
110
|
"""
|
|
110
111
|
return self.generate_entity_id("doc")
|
|
111
112
|
|
|
112
|
-
async def get_many(self, document_ids:
|
|
113
|
+
async def get_many(self, document_ids: list[str]) -> dict[str, Document | None]:
|
|
113
114
|
"""Retrieve multiple documents by ID.
|
|
114
115
|
|
|
115
116
|
Args:
|
|
@@ -120,7 +121,7 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
120
121
|
"""
|
|
121
122
|
return self.get_many_entities(document_ids)
|
|
122
123
|
|
|
123
|
-
async def list_all(self) ->
|
|
124
|
+
async def list_all(self) -> list[Document]:
|
|
124
125
|
"""List all documents.
|
|
125
126
|
|
|
126
127
|
Returns:
|
|
@@ -142,7 +143,7 @@ class MemoryDocumentRepository(DocumentRepository, MemoryRepositoryMixin[Documen
|
|
|
142
143
|
return documents
|
|
143
144
|
|
|
144
145
|
def _add_entity_specific_log_data(
|
|
145
|
-
self, entity: Document, log_data:
|
|
146
|
+
self, entity: Document, log_data: dict[str, Any]
|
|
146
147
|
) -> None:
|
|
147
148
|
"""Add document-specific data to log entries."""
|
|
148
149
|
super()._add_entity_specific_log_data(entity, log_data)
|