htmlgraph 0.9.3__py3-none-any.whl → 0.27.5__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +192 -100
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +190 -14
- htmlgraph/analytics_index.py +135 -51
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +45 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/orchestration/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic Models for Deployment Configuration and Operations
|
|
3
|
+
|
|
4
|
+
These models provide validated, type-safe deployment configurations with:
|
|
5
|
+
- Semantic version validation
|
|
6
|
+
- Configuration schema validation
|
|
7
|
+
- Automatic field documentation
|
|
8
|
+
- JSON schema generation for tooling
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DeploymentStep(str, Enum):
|
|
20
|
+
"""Available deployment steps."""
|
|
21
|
+
|
|
22
|
+
GIT_PUSH = "git-push"
|
|
23
|
+
BUILD = "build"
|
|
24
|
+
PYPI_PUBLISH = "pypi-publish"
|
|
25
|
+
LOCAL_INSTALL = "local-install"
|
|
26
|
+
UPDATE_PLUGINS = "update-plugins"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SemanticVersion(BaseModel):
|
|
30
|
+
"""Semantic version string (major.minor.patch)."""
|
|
31
|
+
|
|
32
|
+
major: int = Field(..., ge=0, description="Major version number")
|
|
33
|
+
minor: int = Field(..., ge=0, description="Minor version number")
|
|
34
|
+
patch: int = Field(..., ge=0, description="Patch version number")
|
|
35
|
+
prerelease: str | None = Field(
|
|
36
|
+
None, description="Pre-release identifier (alpha, beta, rc)"
|
|
37
|
+
)
|
|
38
|
+
build: str | None = Field(None, description="Build metadata")
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_string(cls, version_str: str) -> "SemanticVersion":
|
|
42
|
+
"""Parse semantic version from string.
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
- "1.2.3"
|
|
46
|
+
- "2.0.0-alpha.1"
|
|
47
|
+
- "3.1.4-beta+build.123"
|
|
48
|
+
"""
|
|
49
|
+
# SemVer regex pattern
|
|
50
|
+
pattern = r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
|
51
|
+
|
|
52
|
+
match = re.match(pattern, version_str)
|
|
53
|
+
if not match:
|
|
54
|
+
raise ValueError(f"Invalid semantic version: {version_str}")
|
|
55
|
+
|
|
56
|
+
return cls(
|
|
57
|
+
major=int(match.group("major")),
|
|
58
|
+
minor=int(match.group("minor")),
|
|
59
|
+
patch=int(match.group("patch")),
|
|
60
|
+
prerelease=match.group("prerelease"),
|
|
61
|
+
build=match.group("build"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def to_string(self) -> str:
|
|
65
|
+
"""Convert to semantic version string."""
|
|
66
|
+
version = f"{self.major}.{self.minor}.{self.patch}"
|
|
67
|
+
if self.prerelease:
|
|
68
|
+
version += f"-{self.prerelease}"
|
|
69
|
+
if self.build:
|
|
70
|
+
version += f"+{self.build}"
|
|
71
|
+
return version
|
|
72
|
+
|
|
73
|
+
def __str__(self) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Convert version to string representation.
|
|
76
|
+
|
|
77
|
+
Enables using str() and string formatting on SemanticVersion instances.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
str: Version string in semver format (e.g., "1.2.3", "2.0.0-beta.1")
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> version = SemanticVersion(1, 2, 3)
|
|
84
|
+
>>> str(version)
|
|
85
|
+
'1.2.3'
|
|
86
|
+
>>> print(f"Version: {version}")
|
|
87
|
+
Version: 1.2.3
|
|
88
|
+
>>> version_pre = SemanticVersion(2, 0, 0, prerelease="beta.1")
|
|
89
|
+
>>> str(version_pre)
|
|
90
|
+
'2.0.0-beta.1'
|
|
91
|
+
"""
|
|
92
|
+
return self.to_string()
|
|
93
|
+
|
|
94
|
+
def __lt__(self, other: "SemanticVersion") -> bool:
|
|
95
|
+
"""
|
|
96
|
+
Compare versions for sorting.
|
|
97
|
+
|
|
98
|
+
Enables using <, >, <=, >= operators and sorting SemanticVersion instances.
|
|
99
|
+
Follows semantic versioning precedence rules:
|
|
100
|
+
- Major, minor, patch compared numerically
|
|
101
|
+
- Prerelease versions have lower precedence than release versions
|
|
102
|
+
- Build metadata is ignored in comparisons
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
other: SemanticVersion to compare with
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
bool: True if self is less than other
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
>>> v1 = SemanticVersion(1, 0, 0)
|
|
112
|
+
>>> v2 = SemanticVersion(2, 0, 0)
|
|
113
|
+
>>> v1 < v2
|
|
114
|
+
True
|
|
115
|
+
>>> v1_pre = SemanticVersion(1, 0, 0, prerelease="beta.1")
|
|
116
|
+
>>> v1_pre < v1
|
|
117
|
+
True
|
|
118
|
+
>>> versions = [v2, v1_pre, v1]
|
|
119
|
+
>>> sorted(versions)
|
|
120
|
+
[SemanticVersion(1, 0, 0, prerelease='beta.1'), SemanticVersion(1, 0, 0), SemanticVersion(2, 0, 0)]
|
|
121
|
+
"""
|
|
122
|
+
if self.major != other.major:
|
|
123
|
+
return self.major < other.major
|
|
124
|
+
if self.minor != other.minor:
|
|
125
|
+
return self.minor < other.minor
|
|
126
|
+
if self.patch != other.patch:
|
|
127
|
+
return self.patch < other.patch
|
|
128
|
+
|
|
129
|
+
# Prerelease versions have lower precedence
|
|
130
|
+
if self.prerelease and not other.prerelease:
|
|
131
|
+
return True
|
|
132
|
+
if not self.prerelease and other.prerelease:
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class GitConfig(BaseModel):
|
|
139
|
+
"""Git configuration for deployment."""
|
|
140
|
+
|
|
141
|
+
branch: str = Field(default="main", description="Git branch to push to")
|
|
142
|
+
remote: str = Field(default="origin", description="Git remote name")
|
|
143
|
+
push_tags: bool = Field(default=True, description="Push tags with commits")
|
|
144
|
+
|
|
145
|
+
@field_validator("branch")
|
|
146
|
+
@classmethod
|
|
147
|
+
def validate_branch(cls, v: str) -> str:
|
|
148
|
+
"""Validate git branch name."""
|
|
149
|
+
if not v or not v.strip():
|
|
150
|
+
raise ValueError("Branch name cannot be empty")
|
|
151
|
+
# Basic validation - no spaces, special characters
|
|
152
|
+
if re.search(r"[^\w\-/.]", v):
|
|
153
|
+
raise ValueError(f"Invalid branch name: {v}")
|
|
154
|
+
return v
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class BuildConfig(BaseModel):
|
|
158
|
+
"""Build configuration for package."""
|
|
159
|
+
|
|
160
|
+
command: str = Field(default="uv build", description="Build command to execute")
|
|
161
|
+
clean_dist: bool = Field(default=True, description="Clean dist/ before building")
|
|
162
|
+
verify_artifacts: bool = Field(
|
|
163
|
+
default=True, description="Verify build artifacts exist"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@field_validator("command")
|
|
167
|
+
@classmethod
|
|
168
|
+
def validate_command(cls, v: str) -> str:
|
|
169
|
+
"""Validate build command."""
|
|
170
|
+
if not v or not v.strip():
|
|
171
|
+
raise ValueError("Build command cannot be empty")
|
|
172
|
+
return v.strip()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class PyPIConfig(BaseModel):
|
|
176
|
+
"""PyPI publishing configuration."""
|
|
177
|
+
|
|
178
|
+
token_env_var: str = Field(
|
|
179
|
+
default="PyPI_API_TOKEN",
|
|
180
|
+
description="Environment variable containing PyPI API token",
|
|
181
|
+
)
|
|
182
|
+
wait_after_publish: int = Field(
|
|
183
|
+
default=10,
|
|
184
|
+
ge=0,
|
|
185
|
+
le=60,
|
|
186
|
+
description="Seconds to wait after publishing (for PyPI to process)",
|
|
187
|
+
)
|
|
188
|
+
verify_publication: bool = Field(
|
|
189
|
+
default=True, description="Verify package appears on PyPI after publishing"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
@field_validator("token_env_var")
|
|
193
|
+
@classmethod
|
|
194
|
+
def validate_token_var(cls, v: str) -> str:
|
|
195
|
+
"""Validate environment variable name."""
|
|
196
|
+
if not v or not v.strip():
|
|
197
|
+
raise ValueError("Token environment variable name cannot be empty")
|
|
198
|
+
# Must be valid env var name
|
|
199
|
+
if not re.match(r"^[A-Z_][A-Z0-9_]*$", v):
|
|
200
|
+
raise ValueError(f"Invalid environment variable name: {v}")
|
|
201
|
+
return v
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class PluginConfig(BaseModel):
|
|
205
|
+
"""Plugin update configuration."""
|
|
206
|
+
|
|
207
|
+
name: str = Field(..., description="Plugin name")
|
|
208
|
+
command: str = Field(
|
|
209
|
+
..., description="Update command with {package} and {version} placeholders"
|
|
210
|
+
)
|
|
211
|
+
required: bool = Field(
|
|
212
|
+
default=False, description="Fail deployment if plugin update fails"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
@field_validator("command")
|
|
216
|
+
@classmethod
|
|
217
|
+
def validate_command(cls, v: str) -> str:
|
|
218
|
+
"""Validate plugin update command."""
|
|
219
|
+
if not v or not v.strip():
|
|
220
|
+
raise ValueError("Plugin command cannot be empty")
|
|
221
|
+
|
|
222
|
+
# Check for required placeholders
|
|
223
|
+
if "{package}" not in v and "{version}" not in v:
|
|
224
|
+
raise ValueError(
|
|
225
|
+
"Plugin command must contain {package} or {version} placeholder"
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return v.strip()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class DeploymentHooks(BaseModel):
|
|
232
|
+
"""Custom deployment hooks."""
|
|
233
|
+
|
|
234
|
+
pre_build: list[str] = Field(
|
|
235
|
+
default_factory=list, description="Commands to run before build"
|
|
236
|
+
)
|
|
237
|
+
post_build: list[str] = Field(
|
|
238
|
+
default_factory=list, description="Commands to run after build"
|
|
239
|
+
)
|
|
240
|
+
pre_publish: list[str] = Field(
|
|
241
|
+
default_factory=list, description="Commands to run before publish"
|
|
242
|
+
)
|
|
243
|
+
post_publish: list[str] = Field(
|
|
244
|
+
default_factory=list, description="Commands to run after publish"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@field_validator("pre_build", "post_build", "pre_publish", "post_publish")
|
|
248
|
+
@classmethod
|
|
249
|
+
def validate_hooks(cls, v: list[str]) -> list[str]:
|
|
250
|
+
"""Validate hook commands."""
|
|
251
|
+
return [cmd.strip() for cmd in v if cmd.strip()]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class DeploymentConfig(BaseModel):
|
|
255
|
+
"""Complete deployment configuration with validation.
|
|
256
|
+
|
|
257
|
+
Example:
|
|
258
|
+
```python
|
|
259
|
+
config = DeploymentConfig(
|
|
260
|
+
project_name="htmlgraph",
|
|
261
|
+
pypi_package="htmlgraph",
|
|
262
|
+
version="0.10.0",
|
|
263
|
+
steps=[
|
|
264
|
+
DeploymentStep.GIT_PUSH,
|
|
265
|
+
DeploymentStep.BUILD,
|
|
266
|
+
DeploymentStep.PYPI_PUBLISH
|
|
267
|
+
]
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
# Project info
|
|
273
|
+
project_name: str = Field(..., min_length=1, description="Project name")
|
|
274
|
+
pypi_package: str | None = Field(
|
|
275
|
+
None, description="PyPI package name (if different from project)"
|
|
276
|
+
)
|
|
277
|
+
version: str | None = Field(None, description="Version to deploy")
|
|
278
|
+
|
|
279
|
+
# Deployment steps
|
|
280
|
+
steps: list[DeploymentStep] = Field(
|
|
281
|
+
default=[
|
|
282
|
+
DeploymentStep.GIT_PUSH,
|
|
283
|
+
DeploymentStep.BUILD,
|
|
284
|
+
DeploymentStep.PYPI_PUBLISH,
|
|
285
|
+
DeploymentStep.LOCAL_INSTALL,
|
|
286
|
+
DeploymentStep.UPDATE_PLUGINS,
|
|
287
|
+
],
|
|
288
|
+
description="Deployment steps to execute in order",
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# Component configs
|
|
292
|
+
git: GitConfig = Field(default_factory=GitConfig, description="Git configuration")
|
|
293
|
+
build: BuildConfig = Field(
|
|
294
|
+
default_factory=BuildConfig, description="Build configuration"
|
|
295
|
+
)
|
|
296
|
+
pypi: PyPIConfig = Field(
|
|
297
|
+
default_factory=PyPIConfig, description="PyPI configuration"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Plugins and hooks
|
|
301
|
+
plugins: list[PluginConfig] = Field(
|
|
302
|
+
default_factory=list, description="Plugin update configs"
|
|
303
|
+
)
|
|
304
|
+
hooks: DeploymentHooks = Field(
|
|
305
|
+
default_factory=DeploymentHooks, description="Custom deployment hooks"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Flags
|
|
309
|
+
dry_run: bool = Field(
|
|
310
|
+
default=False, description="Simulate deployment without executing"
|
|
311
|
+
)
|
|
312
|
+
skip_confirmations: bool = Field(
|
|
313
|
+
default=False, description="Skip confirmation prompts"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
@field_validator("project_name")
|
|
317
|
+
@classmethod
|
|
318
|
+
def validate_project_name(cls, v: str) -> str:
|
|
319
|
+
"""Validate project name."""
|
|
320
|
+
if not v or not v.strip():
|
|
321
|
+
raise ValueError("Project name cannot be empty")
|
|
322
|
+
|
|
323
|
+
# Basic validation - alphanumeric, hyphens, underscores
|
|
324
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", v):
|
|
325
|
+
raise ValueError(f"Invalid project name: {v}")
|
|
326
|
+
|
|
327
|
+
return v.strip()
|
|
328
|
+
|
|
329
|
+
@field_validator("version")
|
|
330
|
+
@classmethod
|
|
331
|
+
def validate_version(cls, v: str | None) -> str | None:
|
|
332
|
+
"""Validate semantic version string."""
|
|
333
|
+
if v is None:
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
# Validate it's a valid semantic version
|
|
337
|
+
try:
|
|
338
|
+
SemanticVersion.from_string(v)
|
|
339
|
+
except ValueError as e:
|
|
340
|
+
raise ValueError(f"Invalid version format: {e}")
|
|
341
|
+
|
|
342
|
+
return v
|
|
343
|
+
|
|
344
|
+
@field_validator("steps")
|
|
345
|
+
@classmethod
|
|
346
|
+
def validate_steps(cls, v: list[DeploymentStep]) -> list[DeploymentStep]:
|
|
347
|
+
"""Validate deployment steps."""
|
|
348
|
+
if not v:
|
|
349
|
+
raise ValueError("Must specify at least one deployment step")
|
|
350
|
+
|
|
351
|
+
# Check for duplicate steps
|
|
352
|
+
if len(v) != len(set(v)):
|
|
353
|
+
raise ValueError("Duplicate deployment steps found")
|
|
354
|
+
|
|
355
|
+
return v
|
|
356
|
+
|
|
357
|
+
@model_validator(mode="after")
|
|
358
|
+
def validate_config_consistency(self) -> "DeploymentConfig":
|
|
359
|
+
"""Validate configuration consistency."""
|
|
360
|
+
# If PyPI publish is enabled, ensure we have build step first
|
|
361
|
+
if DeploymentStep.PYPI_PUBLISH in self.steps:
|
|
362
|
+
if DeploymentStep.BUILD not in self.steps:
|
|
363
|
+
raise ValueError("PyPI publish requires build step")
|
|
364
|
+
|
|
365
|
+
# Build must come before publish
|
|
366
|
+
build_idx = self.steps.index(DeploymentStep.BUILD)
|
|
367
|
+
publish_idx = self.steps.index(DeploymentStep.PYPI_PUBLISH)
|
|
368
|
+
if build_idx > publish_idx:
|
|
369
|
+
raise ValueError("Build step must come before PyPI publish")
|
|
370
|
+
|
|
371
|
+
# If local install is enabled, must have either build or publish
|
|
372
|
+
if DeploymentStep.LOCAL_INSTALL in self.steps:
|
|
373
|
+
if (
|
|
374
|
+
DeploymentStep.BUILD not in self.steps
|
|
375
|
+
and DeploymentStep.PYPI_PUBLISH not in self.steps
|
|
376
|
+
):
|
|
377
|
+
raise ValueError("Local install requires build or PyPI publish step")
|
|
378
|
+
|
|
379
|
+
return self
|
|
380
|
+
|
|
381
|
+
@classmethod
|
|
382
|
+
def from_toml(cls, config_path: Path) -> "DeploymentConfig":
|
|
383
|
+
"""Load configuration from TOML file.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
config_path: Path to deployment config TOML file
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
Validated DeploymentConfig instance
|
|
390
|
+
|
|
391
|
+
Raises:
|
|
392
|
+
FileNotFoundError: If config file doesn't exist
|
|
393
|
+
ValueError: If config is invalid
|
|
394
|
+
"""
|
|
395
|
+
if not config_path.exists():
|
|
396
|
+
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
397
|
+
|
|
398
|
+
try:
|
|
399
|
+
import tomllib
|
|
400
|
+
except ImportError:
|
|
401
|
+
import tomli as tomllib # fallback for Python < 3.11
|
|
402
|
+
|
|
403
|
+
with open(config_path, "rb") as f:
|
|
404
|
+
data = tomllib.load(f)
|
|
405
|
+
|
|
406
|
+
# Extract sections
|
|
407
|
+
project = data.get("project", {})
|
|
408
|
+
deployment = data.get("deployment", {})
|
|
409
|
+
|
|
410
|
+
# Parse plugins
|
|
411
|
+
plugins = []
|
|
412
|
+
for name, cmd in deployment.get("plugins", {}).items():
|
|
413
|
+
plugins.append(PluginConfig(name=name, command=cmd))
|
|
414
|
+
|
|
415
|
+
# Build config
|
|
416
|
+
return cls(
|
|
417
|
+
project_name=project.get("name", "my-project"),
|
|
418
|
+
pypi_package=project.get("pypi_package"),
|
|
419
|
+
version=project.get("version"),
|
|
420
|
+
steps=[DeploymentStep(s) for s in deployment.get("steps", [])],
|
|
421
|
+
git=GitConfig(**deployment.get("git", {})),
|
|
422
|
+
build=BuildConfig(**deployment.get("build", {})),
|
|
423
|
+
pypi=PyPIConfig(**deployment.get("pypi", {})),
|
|
424
|
+
plugins=plugins,
|
|
425
|
+
hooks=DeploymentHooks(**deployment.get("hooks", {})),
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
def to_toml_dict(self) -> dict[str, Any]:
|
|
429
|
+
"""Export configuration as TOML-compatible dict."""
|
|
430
|
+
return {
|
|
431
|
+
"project": {
|
|
432
|
+
"name": self.project_name,
|
|
433
|
+
"pypi_package": self.pypi_package,
|
|
434
|
+
"version": self.version,
|
|
435
|
+
},
|
|
436
|
+
"deployment": {
|
|
437
|
+
"steps": [step.value for step in self.steps],
|
|
438
|
+
"git": self.git.model_dump(),
|
|
439
|
+
"build": self.build.model_dump(),
|
|
440
|
+
"pypi": self.pypi.model_dump(),
|
|
441
|
+
"plugins": {p.name: p.command for p in self.plugins},
|
|
442
|
+
"hooks": self.hooks.model_dump(),
|
|
443
|
+
},
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class DeploymentResult(BaseModel):
|
|
448
|
+
"""Result of a deployment operation."""
|
|
449
|
+
|
|
450
|
+
success: bool = Field(..., description="Whether deployment succeeded")
|
|
451
|
+
version: str = Field(..., description="Version that was deployed")
|
|
452
|
+
steps_completed: list[DeploymentStep] = Field(
|
|
453
|
+
default_factory=list, description="Steps that completed"
|
|
454
|
+
)
|
|
455
|
+
steps_failed: list[DeploymentStep] = Field(
|
|
456
|
+
default_factory=list, description="Steps that failed"
|
|
457
|
+
)
|
|
458
|
+
errors: list[str] = Field(default_factory=list, description="Error messages")
|
|
459
|
+
duration_seconds: float = Field(
|
|
460
|
+
default=0.0, ge=0, description="Total deployment duration"
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def is_partial(self) -> bool:
|
|
465
|
+
"""Check if deployment was only partially successful."""
|
|
466
|
+
return self.success and bool(self.steps_failed)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def completion_rate(self) -> float:
|
|
470
|
+
"""Calculate completion rate (0.0 to 1.0)."""
|
|
471
|
+
total_steps = len(self.steps_completed) + len(self.steps_failed)
|
|
472
|
+
if total_steps == 0:
|
|
473
|
+
return 0.0
|
|
474
|
+
return len(self.steps_completed) / total_steps
|