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.
Files changed (24) hide show
  1. {astoncli-0.1.1 → astoncli-0.2.1}/PKG-INFO +1 -1
  2. {astoncli-0.1.1 → astoncli-0.2.1}/aston/axios.py +1 -1
  3. {astoncli-0.1.1 → astoncli-0.2.1}/aston/config.py +25 -3
  4. {astoncli-0.1.1 → astoncli-0.2.1}/aston/ingestion.py +29 -13
  5. {astoncli-0.1.1 → astoncli-0.2.1}/aston/llm.py +7 -2
  6. {astoncli-0.1.1 → astoncli-0.2.1}/aston/pipeline.py +3 -3
  7. {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/PKG-INFO +1 -1
  8. {astoncli-0.1.1 → astoncli-0.2.1}/pyproject.toml +1 -1
  9. {astoncli-0.1.1 → astoncli-0.2.1}/README.md +0 -0
  10. {astoncli-0.1.1 → astoncli-0.2.1}/aston/__init__.py +0 -0
  11. {astoncli-0.1.1 → astoncli-0.2.1}/aston/docker.py +0 -0
  12. {astoncli-0.1.1 → astoncli-0.2.1}/aston/exceptions.py +0 -0
  13. {astoncli-0.1.1 → astoncli-0.2.1}/aston/poller.py +0 -0
  14. {astoncli-0.1.1 → astoncli-0.2.1}/aston/relevance.py +0 -0
  15. {astoncli-0.1.1 → astoncli-0.2.1}/aston/repo_verification.py +0 -0
  16. {astoncli-0.1.1 → astoncli-0.2.1}/aston/rule_validation.py +0 -0
  17. {astoncli-0.1.1 → astoncli-0.2.1}/aston/scanner.py +0 -0
  18. {astoncli-0.1.1 → astoncli-0.2.1}/aston/watcher.py +0 -0
  19. {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/SOURCES.txt +0 -0
  20. {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/dependency_links.txt +0 -0
  21. {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/entry_points.txt +0 -0
  22. {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/requires.txt +0 -0
  23. {astoncli-0.1.1 → astoncli-0.2.1}/astoncli.egg-info/top_level.txt +0 -0
  24. {astoncli-0.1.1 → astoncli-0.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astoncli
3
- Version: 0.1.1
3
+ Version: 0.2.1
4
4
  Summary: Automated Python repository migration CLI tool
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -313,7 +313,7 @@ CURRENT SOURCE CODE TO MIGRATE OR REPAIR:
313
313
  time.sleep(delay)
314
314
  delay *= 2.0
315
315
 
316
- patched = response.choices[0].message.content
316
+ patched = response
317
317
  clean = clean_mistral_code(patched)
318
318
 
319
319
  try:
@@ -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 and applying environment variable overrides.
50
+ falling back to defaults, applying environment variable overrides,
51
+ and overlaying runtime CLI overrides.
42
52
  """
43
- config = DEFAULT_CONFIG.copy()
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(prompt=user_prompt, provider=provider_name, model=model_name, temperature=temperature, system_prompt=VALIDATOR_SYSTEM_PROMPT, response_format=ValidatedRuleset)
332
- parsed = response.choices[0].message.parsed
333
- if parsed is None:
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
- # Match validator output back to source candidates. If the validator
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(prompt=content, provider=provider_name, model=model_name, temperature=temperature, system_prompt=prompt, response_format=MigrationRule)
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.choices[0].message.parsed
747
- if parsed is None:
748
- raise RuntimeError(
749
- f"Codestral returned no parsed ruleset for {package}, chunk {chunk_number + 1}"
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
- return response.text.strip()
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
- dokcer_file_time
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=dokcer_file_time
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=dokcer_file_time,
737
+ docker_time=docker_file_time,
738
738
  initial_result=pr,
739
739
  max_retries=max_tries,
740
740
  passed_files=passed_files,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: astoncli
3
- Version: 0.1.1
3
+ Version: 0.2.1
4
4
  Summary: Automated Python repository migration CLI tool
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -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.1.1"
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