-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Implement CLI functionality for user query processing #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,3 +11,4 @@ __pycache__ | |
| .whoosh | ||
|
|
||
| bin/ | ||
| .docs_buddy.yaml | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| """dotconfig - Create config objects from JSON/YAML""" | ||
|
|
||
| import json | ||
| from pathlib import Path | ||
|
|
||
| import yaml | ||
|
|
||
|
|
||
| class Config: | ||
| """Wrapper for nested config with dot-notation access. | ||
|
|
||
| >>> config = Config({"database": {"host": "localhost", "port": 5432}}) | ||
| >>> config.database.host | ||
| 'localhost' | ||
| >>> config.database.port | ||
| 5432 | ||
|
|
||
| Supports loading JSON/YAML | ||
| """ | ||
|
|
||
| def __new__(cls, config): | ||
| if isinstance(config, list): | ||
| return [cls(c) if isinstance(c, (list, dict)) else c for c in config] | ||
| return super().__new__(cls) | ||
|
|
||
| def __init__(self, config: dict): | ||
| self._config = config | ||
|
|
||
| @classmethod | ||
| def from_json(cls, file): | ||
| """Load from JSON file""" | ||
| data = cls._get_data(file, json.load) | ||
| return cls(data) | ||
|
|
||
| @classmethod | ||
| def from_yaml(cls, file): | ||
| """Load from YAML file""" | ||
| data = cls._get_data(file, yaml.safe_load) | ||
| return cls(data) | ||
|
|
||
| @staticmethod | ||
| def _get_data(source, loader): | ||
| """Load data from file object or path""" | ||
| try: | ||
| filepath = Path(source) | ||
| except TypeError: | ||
| # treat as filelike | ||
| data = loader(source) | ||
| else: | ||
| with open(filepath) as f: | ||
| data = loader(f) | ||
| return data | ||
|
|
||
| def __getattr__(self, name): | ||
| try: | ||
| value = self._config[name] | ||
| except KeyError as e: | ||
| raise AttributeError(f"No config '{name}' found.") from e | ||
|
|
||
| if isinstance(value, (dict, list)): | ||
| return Config(value) | ||
| return value | ||
|
|
||
| def __getitem__(self, name): | ||
| value = self._config[name] | ||
| if isinstance(value, (list, dict)): | ||
| return Config(value) | ||
| return value | ||
|
|
||
| def __str__(self): | ||
| return json.dumps(self._config) | ||
|
|
||
| def __repr__(self): | ||
| cls_name = type(self).__name__ | ||
| return f"{cls_name}({self._config!r})" |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||||||||||||||||
| from typing import TypeAlias | ||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||
| import functools | ||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| PathLike: TypeAlias = str | os.PathLike | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
@@ -45,3 +46,11 @@ def wrapper(*args, **kwargs): | |||||||||||||||||||||||||
| return wrapper | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return decorator | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| def sanitize_to_python_id(identifier: str) -> str: | ||||||||||||||||||||||||||
| """Converts string to valid Python identifier""" | ||||||||||||||||||||||||||
| sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", identifier) | ||||||||||||||||||||||||||
| if sanitized[0].isdigit(): | ||||||||||||||||||||||||||
| sanitized = "name_" + sanitized | ||||||||||||||||||||||||||
| return sanitized | ||||||||||||||||||||||||||
|
Comment on lines
+51
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Guard against empty string input in
🛡️ Proposed fix def sanitize_to_python_id(identifier: str) -> str:
"""Converts string to valid Python identifier"""
sanitized = re.sub(r"[^a-zA-Z0-9_]", "_", identifier)
- if sanitized[0].isdigit():
+ if sanitized and sanitized[0].isdigit():
sanitized = "name_" + sanitized
return sanitized📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add
text=Truetosubprocess.runcalls for clean error messages.Without
text=True,capture_output=Truecaptures stderr as bytes. The error messages inpull_repo/clone_repointerpolateexc.stderr, so users will seeb'fatal: ...'instead offatal: ....🔧 Proposed fix
`@staticmethod` def _clone_repository(url: str, target_dir: str, branch: str) -> None: """Clone a git repository with depth 1.""" subprocess.run( ["git", "clone", "--depth", "1", "--branch", branch, url, target_dir], check=True, capture_output=True, + text=True, ) `@staticmethod` def _update_repository(directory: str, branch: str) -> None: """Pull latest changes from a git repository.""" subprocess.run( [ "git", "fetch", "--depth", "1", "origin", f"{branch}:refs/remotes/origin/{branch}", ], cwd=directory, check=True, capture_output=True, + text=True, ) subprocess.run( ["git", "checkout", "-B", branch, f"origin/{branch}"], cwd=directory, capture_output=True, check=True, + text=True, )📝 Committable suggestion
🧰 Tools
🪛 ast-grep (0.44.1)
[error] 140-144: Command coming from incoming request
Context: subprocess.run(
["git", "clone", "--depth", "1", "--branch", branch, url, target_dir],
check=True,
capture_output=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(subprocess-from-request)
[error] 149-161: Command coming from incoming request
Context: subprocess.run(
[
"git",
"fetch",
"--depth",
"1",
"origin",
f"{branch}:refs/remotes/origin/{branch}",
],
cwd=directory,
check=True,
capture_output=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(subprocess-from-request)
[error] 163-168: Command coming from incoming request
Context: subprocess.run(
["git", "checkout", "-B", branch, f"origin/{branch}"],
cwd=directory,
capture_output=True,
check=True,
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(subprocess-from-request)
🪛 Ruff (0.15.20)
[error] 141-141:
subprocesscall: check for execution of untrusted input(S603)
[error] 142-142: Starting a process with a partial executable path
(S607)
[error] 150-150:
subprocesscall: check for execution of untrusted input(S603)
[error] 151-158: Starting a process with a partial executable path
(S607)
[error] 164-164:
subprocesscall: check for execution of untrusted input(S603)
[error] 165-165: Starting a process with a partial executable path
(S607)
🤖 Prompt for AI Agents