astoncli 0.1.1__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {astoncli-0.1.1 → astoncli-0.2.1}/PKG-INFO +1 -1
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/axios.py +1 -1
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/config.py +25 -3
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/ingestion.py +29 -13
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/llm.py +7 -2
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/pipeline.py +3 -3
- {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/PKG-INFO +1 -1
- {astoncli-0.1.1 → astoncli-0.2.1}/pyproject.toml +1 -1
- {astoncli-0.1.1 → astoncli-0.2.1}/README.md +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/__init__.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/docker.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/exceptions.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/poller.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/relevance.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/repo_verification.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/rule_validation.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/scanner.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/aston/watcher.py +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/SOURCES.txt +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/dependency_links.txt +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/entry_points.txt +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/requires.txt +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/top_level.txt +0 -0
- {astoncli-0.1.1 → astoncli-0.2.1}/setup.cfg +0 -0
|
@@ -27,6 +27,15 @@ DEFAULT_CONFIG = {
|
|
|
27
27
|
},
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
# Runtime overrides storage (populated by CLI flags before execution)
|
|
31
|
+
_RUNTIME_OVERRIDES = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_runtime_config(overrides: dict):
|
|
35
|
+
"""Set temporary runtime configuration overrides from CLI flags."""
|
|
36
|
+
global _RUNTIME_OVERRIDES
|
|
37
|
+
_RUNTIME_OVERRIDES.update(overrides)
|
|
38
|
+
|
|
30
39
|
|
|
31
40
|
def get_config_dir() -> Path:
|
|
32
41
|
"""Ensure and return the user config directory (~/.config/aston/)."""
|
|
@@ -38,9 +47,14 @@ def get_config_dir() -> Path:
|
|
|
38
47
|
def load_config() -> dict:
|
|
39
48
|
"""
|
|
40
49
|
Load configuration from ~/.config/aston/config.yaml,
|
|
41
|
-
falling back to defaults
|
|
50
|
+
falling back to defaults, applying environment variable overrides,
|
|
51
|
+
and overlaying runtime CLI overrides.
|
|
42
52
|
"""
|
|
43
|
-
|
|
53
|
+
# Create a deep-ish copy of defaults to prevent mutating the global dictionary
|
|
54
|
+
config = {
|
|
55
|
+
section: values.copy() if isinstance(values, dict) else values
|
|
56
|
+
for section, values in DEFAULT_CONFIG.items()
|
|
57
|
+
}
|
|
44
58
|
|
|
45
59
|
if CONFIG_FILE.is_file():
|
|
46
60
|
try:
|
|
@@ -49,7 +63,7 @@ def load_config() -> dict:
|
|
|
49
63
|
if isinstance(user_config, dict):
|
|
50
64
|
# Deep merge user config into defaults
|
|
51
65
|
for section, values in user_config.items():
|
|
52
|
-
if section in config and isinstance(values, dict):
|
|
66
|
+
if section in config and isinstance(values, dict) and isinstance(config[section], dict):
|
|
53
67
|
config[section].update(values)
|
|
54
68
|
else:
|
|
55
69
|
config[section] = values
|
|
@@ -60,6 +74,14 @@ def load_config() -> dict:
|
|
|
60
74
|
if os.getenv("ASTON_MODEL"):
|
|
61
75
|
config["llm"]["model"] = os.getenv("ASTON_MODEL")
|
|
62
76
|
|
|
77
|
+
# Apply runtime CLI overrides on top
|
|
78
|
+
if _RUNTIME_OVERRIDES:
|
|
79
|
+
for section, values in _RUNTIME_OVERRIDES.items():
|
|
80
|
+
if section in config and isinstance(values, dict) and isinstance(config[section], dict):
|
|
81
|
+
config[section].update(values)
|
|
82
|
+
else:
|
|
83
|
+
config[section] = values
|
|
84
|
+
|
|
63
85
|
return config
|
|
64
86
|
|
|
65
87
|
|
|
@@ -328,17 +328,23 @@ Validate each candidate independently. Preserve source metadata and exact
|
|
|
328
328
|
version boundary values supplied by the caller. Do not invent evidence or URLs.
|
|
329
329
|
"""
|
|
330
330
|
try:
|
|
331
|
-
response = call_llm(
|
|
332
|
-
|
|
333
|
-
|
|
331
|
+
response = call_llm(
|
|
332
|
+
prompt=user_prompt,
|
|
333
|
+
provider=provider_name,
|
|
334
|
+
model=model_name,
|
|
335
|
+
temperature=temperature,
|
|
336
|
+
system_prompt=VALIDATOR_SYSTEM_PROMPT,
|
|
337
|
+
response_format=ValidatedRuleset
|
|
338
|
+
)
|
|
339
|
+
parsed = response
|
|
340
|
+
if parsed is None or not hasattr(parsed, "rules"):
|
|
334
341
|
warning("Validator returned no parsed result; preserving candidates as needs_review.")
|
|
335
342
|
return preserve_for_review(rules, "the validator returned no parsed result")
|
|
336
|
-
|
|
337
|
-
# omitted any input, preserve it for review instead of silently losing it.
|
|
343
|
+
|
|
338
344
|
unmatched = list(enumerate(rules))
|
|
339
345
|
matched_results: list[tuple[dict, dict]] = []
|
|
340
346
|
for validated_model in parsed.rules:
|
|
341
|
-
validated = validated_model.model_dump()
|
|
347
|
+
validated = validated_model.model_dump() if hasattr(validated_model, "model_dump") else dict(validated_model)
|
|
342
348
|
key = _candidate_key(validated)
|
|
343
349
|
match_index = next(
|
|
344
350
|
(i for i, (_, source) in enumerate(unmatched) if _candidate_key(source) == key),
|
|
@@ -741,16 +747,26 @@ def extract_rules(
|
|
|
741
747
|
f"Documentation chunk:\n{chunk_text}"
|
|
742
748
|
)
|
|
743
749
|
|
|
744
|
-
response = call_llm(
|
|
750
|
+
response = call_llm(
|
|
751
|
+
prompt=content,
|
|
752
|
+
provider=provider_name,
|
|
753
|
+
model=model_name,
|
|
754
|
+
temperature=temperature,
|
|
755
|
+
system_prompt=prompt,
|
|
756
|
+
response_format=MigrationRuleset
|
|
757
|
+
)
|
|
745
758
|
|
|
746
|
-
parsed = response
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
)
|
|
759
|
+
parsed = response
|
|
760
|
+
|
|
761
|
+
# Gracefully handle empty or unparsed chunks instead of raising an error
|
|
762
|
+
if parsed is None or not hasattr(parsed, "rules") or not parsed.rules:
|
|
763
|
+
info(f"Chunk {chunk_number + 1} returned no rules for {package}; skipping chunk.")
|
|
764
|
+
continue
|
|
765
|
+
|
|
751
766
|
normalized = normalize_rule_symbols(
|
|
752
|
-
[rule.model_dump() for rule in parsed.rules]
|
|
767
|
+
[rule.model_dump() if hasattr(rule, "model_dump") else dict(rule) for rule in parsed.rules]
|
|
753
768
|
)
|
|
769
|
+
|
|
754
770
|
for rule in normalized:
|
|
755
771
|
rule["package"] = package
|
|
756
772
|
source_url = rule.get("source_url")
|
|
@@ -10,7 +10,7 @@ class BaseLLMProvider(ABC):
|
|
|
10
10
|
|
|
11
11
|
class MistralProvider(BaseLLMProvider):
|
|
12
12
|
def generate(self, prompt: str, model: str, temperature: float = 0.0, system_prompt: str = None, response_format=None) -> str:
|
|
13
|
-
from mistralai import Mistral
|
|
13
|
+
from mistralai.client import Mistral
|
|
14
14
|
api_key = os.getenv("MISTRAL_API_KEY")
|
|
15
15
|
if not api_key:
|
|
16
16
|
raise LLMError(
|
|
@@ -140,7 +140,12 @@ class GoogleProvider(BaseLLMProvider):
|
|
|
140
140
|
contents=prompt,
|
|
141
141
|
config=types.GenerateContentConfig(**config_kwargs)
|
|
142
142
|
)
|
|
143
|
-
|
|
143
|
+
|
|
144
|
+
# If response_format is used and parsed object is available, return it directly
|
|
145
|
+
if response_format and hasattr(response, "parsed") and response.parsed is not None:
|
|
146
|
+
return response.parsed
|
|
147
|
+
|
|
148
|
+
return response.text.strip() if response.text else ""
|
|
144
149
|
except Exception as exc:
|
|
145
150
|
raise LLMError(
|
|
146
151
|
message="Google Gemini API generation failed.",
|
|
@@ -410,7 +410,7 @@ def run_for_repo(
|
|
|
410
410
|
passed_files: list,
|
|
411
411
|
failed_files: list,
|
|
412
412
|
max_tries: int,
|
|
413
|
-
|
|
413
|
+
docker_file_time
|
|
414
414
|
):
|
|
415
415
|
with open("repo_index.json", "r", encoding="utf-8") as f:
|
|
416
416
|
repo_index = json.load(f)
|
|
@@ -683,7 +683,7 @@ def run_for_repo(
|
|
|
683
683
|
str(candidate_path),
|
|
684
684
|
temp_req_path,
|
|
685
685
|
repo_root=root_path,
|
|
686
|
-
timeout=
|
|
686
|
+
timeout=docker_file_time
|
|
687
687
|
)
|
|
688
688
|
|
|
689
689
|
context_data = {
|
|
@@ -734,7 +734,7 @@ def run_for_repo(
|
|
|
734
734
|
temp_req_path=temp_req_path,
|
|
735
735
|
context_data=context_data,
|
|
736
736
|
root_path=root_path,
|
|
737
|
-
docker_time=
|
|
737
|
+
docker_time=docker_file_time,
|
|
738
738
|
initial_result=pr,
|
|
739
739
|
max_retries=max_tries,
|
|
740
740
|
passed_files=passed_files,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "astoncli" # Available on PyPI, while your internal app and CLI command remain 'aston'
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Automated Python repository migration CLI tool"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|