mirror of
https://github.com/msoedov/agentic_security.git
synced 2026-06-24 22:29:56 +02:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21180b53e5 | |||
| a8808b3165 | |||
| 87c26ca3cc | |||
| e06c6932de | |||
| 51fcc38885 | |||
| 06a7bbfd87 | |||
| 50f3e52445 | |||
| 2bd62c21be | |||
| d5d5dd48aa | |||
| bb2437197a | |||
| 51bb79aa6d | |||
| 94f034fa9f | |||
| f69de8720b | |||
| 7c9d83b1a7 | |||
| a9d4d671ba | |||
| 554a219535 | |||
| 32e99006bf | |||
| 8c09d65687 | |||
| a2842755fa | |||
| b923f7fea5 | |||
| 7f30a8ff7a | |||
| 909cbd69b4 | |||
| 4f0ebf180e | |||
| 6be9673aa7 | |||
| bd9ed97d85 | |||
| 3c88a4d6ba | |||
| 2001eeb125 | |||
| a26b5dd448 | |||
| 716a0f67f3 | |||
| c1bbf6b422 |
@@ -84,6 +84,7 @@ agentic_security --port=PORT --host=HOST
|
|||||||
## UI 🧙
|
## UI 🧙
|
||||||
|
|
||||||
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1736433557/z0bsyzhsqlgcr3w4ovwp.gif">
|
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1736433557/z0bsyzhsqlgcr3w4ovwp.gif">
|
||||||
|
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1741192668/final_aa9jhb.gif">
|
||||||
|
|
||||||
## LLM kwargs
|
## LLM kwargs
|
||||||
|
|
||||||
@@ -408,10 +409,15 @@ For more detailed information on how to use Agentic Security, including advanced
|
|||||||
|
|
||||||
## Roadmap and Future Goals
|
## Roadmap and Future Goals
|
||||||
|
|
||||||
- \[ \] Expand dataset variety
|
|
||||||
- \[ \] Introduce two new attack vectors
|
|
||||||
- \[ \] Develop initial attacker LLM
|
We’re just getting started! Here’s what’s on the horizon:
|
||||||
- \[ \] Complete integration of OWASP Top 10 classification
|
|
||||||
|
- **RL-Powered Attacks**: An attacker LLM trained with reinforcement learning to dynamically evolve jailbreaks and outsmart defenses.
|
||||||
|
- **Massive Dataset Expansion**: Scaling to 100,000+ prompts across text, image, and audio modalities—curated for real-world threats.
|
||||||
|
- **Daily Attack Updates**: Fresh attack vectors delivered daily, keeping your scans ahead of the curve.
|
||||||
|
- **Community Modules**: A plug-and-play ecosystem where you can share and deploy custom probes, datasets, and integrations.
|
||||||
|
|
||||||
|
|
||||||
| Tool | Source | Integrated |
|
| Tool | Source | Integrated |
|
||||||
|-------------------------|-------------------------------------------------------------------------------|------------|
|
|-------------------------|-------------------------------------------------------------------------------|------------|
|
||||||
@@ -439,4 +445,9 @@ Before contributing, please read the contributing guidelines.
|
|||||||
|
|
||||||
Agentic Security is released under the Apache License v2.
|
Agentic Security is released under the Apache License v2.
|
||||||
|
|
||||||
|
|
||||||
|
## 🚫 No Cryptocurrency Affiliation
|
||||||
|
|
||||||
|
Agentic Security is focused solely on AI security and has no affiliation with cryptocurrency projects, blockchain technologies, or related initiatives. Our mission is to advance the safety and reliability of AI systems—no tokens, no coins, just code.
|
||||||
|
|
||||||
## Contact us
|
## Contact us
|
||||||
|
|||||||
@@ -81,7 +81,11 @@ def generate_banner(
|
|||||||
|
|
||||||
def init_banner():
|
def init_banner():
|
||||||
ver = version("agentic_security")
|
ver = version("agentic_security")
|
||||||
print(generate_banner(version=ver))
|
try:
|
||||||
|
print(generate_banner(version=ver))
|
||||||
|
except Exception:
|
||||||
|
# UnicodeEncodeError with codec on some systems
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -107,6 +107,17 @@ async def process_prompt_batch(
|
|||||||
return total_tokens, failures
|
return total_tokens, failures
|
||||||
|
|
||||||
|
|
||||||
|
async def with_error_handling(agen):
|
||||||
|
try:
|
||||||
|
async for t in agen:
|
||||||
|
yield t
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Scan failed")
|
||||||
|
yield ScanResult.status_msg(f"Scan failed: {str(e)}")
|
||||||
|
finally:
|
||||||
|
yield ScanResult.status_msg("Scan completed.")
|
||||||
|
|
||||||
|
|
||||||
async def perform_single_shot_scan(
|
async def perform_single_shot_scan(
|
||||||
request_factory,
|
request_factory,
|
||||||
max_budget: int,
|
max_budget: int,
|
||||||
@@ -120,125 +131,117 @@ async def perform_single_shot_scan(
|
|||||||
max_budget = max_budget * BUDGET_MULTIPLIER
|
max_budget = max_budget * BUDGET_MULTIPLIER
|
||||||
selected_datasets = [m for m in datasets if m["selected"]]
|
selected_datasets = [m for m in datasets if m["selected"]]
|
||||||
request_factory = multi_modality_spec(request_factory)
|
request_factory = multi_modality_spec(request_factory)
|
||||||
try:
|
yield ScanResult.status_msg("Loading datasets...")
|
||||||
yield ScanResult.status_msg("Loading datasets...")
|
prompt_modules = prepare_prompts(
|
||||||
prompt_modules = prepare_prompts(
|
dataset_names=[m["dataset_name"] for m in selected_datasets],
|
||||||
dataset_names=[m["dataset_name"] for m in selected_datasets],
|
budget=max_budget,
|
||||||
budget=max_budget,
|
tools_inbox=tools_inbox,
|
||||||
tools_inbox=tools_inbox,
|
options=[m.get("opts", {}) for m in selected_datasets],
|
||||||
options=[m.get("opts", {}) for m in selected_datasets],
|
)
|
||||||
)
|
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
||||||
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
refusals = []
|
refusals = []
|
||||||
outputs = []
|
outputs = []
|
||||||
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
||||||
processed_prompts = 0
|
processed_prompts = 0
|
||||||
|
|
||||||
optimizer = (
|
optimizer = (
|
||||||
Optimizer([Real(0, 1)], base_estimator="GP", n_initial_points=25)
|
Optimizer([Real(0, 1)], base_estimator="GP", n_initial_points=25)
|
||||||
if optimize
|
if optimize
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
failure_rates = []
|
failure_rates = []
|
||||||
|
|
||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
|
tokens = 0
|
||||||
|
should_stop = False
|
||||||
|
for module in prompt_modules:
|
||||||
|
if should_stop:
|
||||||
|
break
|
||||||
tokens = 0
|
tokens = 0
|
||||||
should_stop = False
|
module_failures = 0
|
||||||
for module in prompt_modules:
|
module_size = 0 if module.lazy else len(module.prompts)
|
||||||
if should_stop:
|
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
||||||
break
|
module_prompts = 0 # Reset for each module
|
||||||
tokens = 0
|
|
||||||
module_failures = 0
|
|
||||||
module_size = 0 if module.lazy else len(module.prompts)
|
|
||||||
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
|
||||||
module_prompts = 0 # Reset for each module
|
|
||||||
|
|
||||||
async for prompt in generate_prompts(module.prompts):
|
async for prompt in generate_prompts(module.prompts):
|
||||||
if stop_event and stop_event.is_set():
|
if stop_event and stop_event.is_set():
|
||||||
stop_event.clear()
|
stop_event.clear()
|
||||||
logger.info("Scan stopped by user.")
|
logger.info("Scan stopped by user.")
|
||||||
yield ScanResult.status_msg("Scan stopped by user.")
|
yield ScanResult.status_msg("Scan stopped by user.")
|
||||||
return
|
return
|
||||||
|
|
||||||
processed_prompts += 1
|
processed_prompts += 1
|
||||||
module_prompts += 1 # Fixed increment syntax
|
module_prompts += 1 # Fixed increment syntax
|
||||||
# Calculate progress based on total processed prompts
|
# Calculate progress based on total processed prompts
|
||||||
progress = (
|
progress = 100 * processed_prompts / total_prompts if total_prompts else 0
|
||||||
100 * processed_prompts / total_prompts if total_prompts else 0
|
progress = progress % 100
|
||||||
)
|
|
||||||
|
|
||||||
total_tokens -= tokens
|
total_tokens -= tokens
|
||||||
start = time.time()
|
start = time.time()
|
||||||
tokens, failed = await process_prompt(
|
tokens, failed = await process_prompt(
|
||||||
request_factory,
|
request_factory,
|
||||||
prompt,
|
prompt,
|
||||||
tokens,
|
tokens,
|
||||||
module.dataset_name,
|
module.dataset_name,
|
||||||
refusals,
|
refusals,
|
||||||
errors,
|
errors,
|
||||||
outputs,
|
outputs,
|
||||||
)
|
)
|
||||||
end = time.time()
|
end = time.time()
|
||||||
total_tokens += tokens
|
total_tokens += tokens
|
||||||
|
|
||||||
if failed:
|
if failed:
|
||||||
module_failures += 1
|
module_failures += 1
|
||||||
failure_rate = module_failures / max(module_prompts, 1)
|
failure_rate = module_failures / max(module_prompts, 1)
|
||||||
failure_rates.append(failure_rate)
|
failure_rates.append(failure_rate)
|
||||||
cost = calculate_cost(tokens)
|
cost = calculate_cost(tokens)
|
||||||
|
|
||||||
last_output = outputs[-1] if outputs else None
|
last_output = outputs[-1] if outputs else None
|
||||||
if last_output and last_output[1] == prompt:
|
if last_output and last_output[1] == prompt:
|
||||||
response_text = last_output[2]
|
response_text = last_output[2]
|
||||||
else:
|
else:
|
||||||
response_text = ""
|
response_text = ""
|
||||||
|
|
||||||
yield ScanResult(
|
yield ScanResult(
|
||||||
module=module.dataset_name,
|
module=module.dataset_name,
|
||||||
tokens=round(tokens / 1000, 1),
|
tokens=round(tokens / 1000, 1),
|
||||||
cost=cost,
|
cost=cost,
|
||||||
progress=round(progress, 2),
|
progress=round(progress, 2),
|
||||||
failureRate=round(failure_rate * 100, 2),
|
failureRate=round(failure_rate * 100, 2),
|
||||||
prompt=prompt[:MAX_PROMPT_LENGTH],
|
prompt=prompt[:MAX_PROMPT_LENGTH],
|
||||||
latency=end - start,
|
latency=end - start,
|
||||||
model=response_text,
|
model=response_text,
|
||||||
).model_dump_json()
|
).model_dump_json()
|
||||||
|
|
||||||
if optimize and len(failure_rates) >= 5:
|
if optimize and len(failure_rates) >= 5:
|
||||||
next_point = optimizer.ask()
|
next_point = optimizer.ask()
|
||||||
optimizer.tell(next_point, -failure_rate)
|
optimizer.tell(next_point, -failure_rate)
|
||||||
best_failure_rate = -optimizer.get_result().fun
|
best_failure_rate = -optimizer.get_result().fun
|
||||||
if best_failure_rate > 0.5:
|
if best_failure_rate > 0.5:
|
||||||
yield ScanResult.status_msg(
|
|
||||||
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
|
||||||
)
|
|
||||||
should_stop = True
|
|
||||||
break
|
|
||||||
if total_tokens > max_budget:
|
|
||||||
logger.info(
|
|
||||||
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
|
||||||
)
|
|
||||||
yield ScanResult.status_msg(
|
yield ScanResult.status_msg(
|
||||||
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
||||||
)
|
)
|
||||||
should_stop = True
|
should_stop = True
|
||||||
break
|
break
|
||||||
|
if total_tokens > max_budget:
|
||||||
|
logger.info(
|
||||||
|
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
||||||
|
)
|
||||||
|
yield ScanResult.status_msg(
|
||||||
|
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
||||||
|
)
|
||||||
|
should_stop = True
|
||||||
|
break
|
||||||
|
|
||||||
yield ScanResult.status_msg("Scan completed.")
|
yield ScanResult.status_msg("Scan completed.")
|
||||||
|
|
||||||
failure_data = errors + refusals
|
failure_data = errors + refusals
|
||||||
df = pd.DataFrame(
|
df = pd.DataFrame(
|
||||||
failure_data, columns=["module", "prompt", "status_code", "content"]
|
failure_data, columns=["module", "prompt", "status_code", "content"]
|
||||||
)
|
)
|
||||||
df.to_csv("failures.csv", index=False)
|
df.to_csv("failures.csv", index=False)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception("Scan failed")
|
|
||||||
yield ScanResult.status_msg(f"Scan failed: {str(e)}")
|
|
||||||
finally:
|
|
||||||
yield ScanResult.status_msg("Scan completed.")
|
|
||||||
|
|
||||||
|
|
||||||
async def perform_many_shot_scan(
|
async def perform_many_shot_scan(
|
||||||
@@ -255,114 +258,107 @@ async def perform_many_shot_scan(
|
|||||||
) -> AsyncGenerator[str, None]:
|
) -> AsyncGenerator[str, None]:
|
||||||
"""Perform a multi-step security scan with probe injection."""
|
"""Perform a multi-step security scan with probe injection."""
|
||||||
request_factory = multi_modality_spec(request_factory)
|
request_factory = multi_modality_spec(request_factory)
|
||||||
try:
|
# Load main and probe datasets
|
||||||
# Load main and probe datasets
|
yield ScanResult.status_msg("Loading datasets...")
|
||||||
yield ScanResult.status_msg("Loading datasets...")
|
prompt_modules = prepare_prompts(
|
||||||
prompt_modules = prepare_prompts(
|
dataset_names=[m["dataset_name"] for m in datasets if m["selected"]],
|
||||||
dataset_names=[m["dataset_name"] for m in datasets if m["selected"]],
|
budget=max_budget,
|
||||||
budget=max_budget,
|
tools_inbox=tools_inbox,
|
||||||
tools_inbox=tools_inbox,
|
)
|
||||||
)
|
yield ScanResult.status_msg("Loading datasets for MSJ...")
|
||||||
yield ScanResult.status_msg("Loading datasets for MSJ...")
|
msj_modules = msj_data.prepare_prompts(probe_datasets)
|
||||||
msj_modules = msj_data.prepare_prompts(probe_datasets)
|
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
||||||
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
refusals = []
|
refusals = []
|
||||||
outputs = []
|
outputs = []
|
||||||
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
||||||
processed_prompts = 0
|
processed_prompts = 0
|
||||||
|
|
||||||
optimizer = (
|
optimizer = (
|
||||||
Optimizer([Real(0, 1)], base_estimator="GP", n_initial_points=25)
|
Optimizer([Real(0, 1)], base_estimator="GP", n_initial_points=25)
|
||||||
if optimize
|
if optimize
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
failure_rates = []
|
failure_rates = []
|
||||||
|
|
||||||
for module in prompt_modules:
|
for module in prompt_modules:
|
||||||
module_failures = 0
|
module_failures = 0
|
||||||
module_size = 0 if module.lazy else len(module.prompts)
|
module_size = 0 if module.lazy else len(module.prompts)
|
||||||
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
||||||
|
|
||||||
async for prompt in generate_prompts(module.prompts):
|
async for prompt in generate_prompts(module.prompts):
|
||||||
if stop_event and stop_event.is_set():
|
if stop_event and stop_event.is_set():
|
||||||
stop_event.clear()
|
stop_event.clear()
|
||||||
logger.info("Scan stopped by user.")
|
logger.info("Scan stopped by user.")
|
||||||
yield ScanResult.status_msg("Scan stopped by user.")
|
yield ScanResult.status_msg("Scan stopped by user.")
|
||||||
return
|
return
|
||||||
tokens = 0
|
tokens = 0
|
||||||
processed_prompts += 1
|
processed_prompts += 1
|
||||||
progress = (
|
progress = 100 * processed_prompts / total_prompts if total_prompts else 0
|
||||||
100 * processed_prompts / total_prompts if total_prompts else 0
|
progress = progress % 100
|
||||||
|
|
||||||
|
full_prompt = ""
|
||||||
|
msj_module = random.choice(msj_modules)
|
||||||
|
|
||||||
|
prompt_tokens = len(full_prompt.split())
|
||||||
|
tokens += prompt_tokens
|
||||||
|
|
||||||
|
injected = False
|
||||||
|
for _ in range(20):
|
||||||
|
if injected:
|
||||||
|
break
|
||||||
|
|
||||||
|
m_prompt = random.choice(msj_module.prompts)
|
||||||
|
full_prompt += "\n" + m_prompt
|
||||||
|
if tokens > max_ctx_length:
|
||||||
|
full_prompt = "\n" + prompt
|
||||||
|
injected = True
|
||||||
|
|
||||||
|
tokens, failed = await process_prompt(
|
||||||
|
request_factory,
|
||||||
|
full_prompt,
|
||||||
|
tokens,
|
||||||
|
module.dataset_name,
|
||||||
|
refusals,
|
||||||
|
errors,
|
||||||
|
outputs,
|
||||||
)
|
)
|
||||||
|
if failed:
|
||||||
|
module_failures += 1
|
||||||
|
break
|
||||||
|
if injected:
|
||||||
|
break
|
||||||
|
|
||||||
full_prompt = ""
|
failure_rate = module_failures / max(processed_prompts, 1)
|
||||||
msj_module = random.choice(msj_modules)
|
failure_rates.append(failure_rate)
|
||||||
|
cost = calculate_cost(tokens)
|
||||||
|
|
||||||
prompt_tokens = len(full_prompt.split())
|
yield ScanResult(
|
||||||
tokens += prompt_tokens
|
module=module.dataset_name,
|
||||||
|
tokens=round(tokens / 1000, 1),
|
||||||
|
cost=cost,
|
||||||
|
progress=round(progress, 2),
|
||||||
|
failureRate=round(failure_rate * 100, 2),
|
||||||
|
prompt=prompt[:MAX_PROMPT_LENGTH],
|
||||||
|
).model_dump_json()
|
||||||
|
|
||||||
injected = False
|
if optimize and len(failure_rates) >= 5:
|
||||||
for _ in range(20):
|
next_point = optimizer.ask()
|
||||||
if injected:
|
optimizer.tell(next_point, -failure_rate)
|
||||||
break
|
best_failure_rate = -optimizer.get_result().fun
|
||||||
|
if best_failure_rate > 0.5:
|
||||||
m_prompt = random.choice(msj_module.prompts)
|
yield ScanResult.status_msg(
|
||||||
full_prompt += "\n" + m_prompt
|
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
||||||
if tokens > max_ctx_length:
|
|
||||||
full_prompt = "\n" + prompt
|
|
||||||
injected = True
|
|
||||||
|
|
||||||
tokens, failed = await process_prompt(
|
|
||||||
request_factory,
|
|
||||||
full_prompt,
|
|
||||||
tokens,
|
|
||||||
module.dataset_name,
|
|
||||||
refusals,
|
|
||||||
errors,
|
|
||||||
outputs,
|
|
||||||
)
|
)
|
||||||
if failed:
|
break
|
||||||
module_failures += 1
|
|
||||||
break
|
|
||||||
if injected:
|
|
||||||
break
|
|
||||||
|
|
||||||
failure_rate = module_failures / max(processed_prompts, 1)
|
yield ScanResult.status_msg("Scan completed.")
|
||||||
failure_rates.append(failure_rate)
|
|
||||||
cost = calculate_cost(tokens)
|
|
||||||
|
|
||||||
yield ScanResult(
|
df = pd.DataFrame(
|
||||||
module=module.dataset_name,
|
errors + refusals, columns=["module", "prompt", "status_code", "content"]
|
||||||
tokens=round(tokens / 1000, 1),
|
)
|
||||||
cost=cost,
|
df.to_csv("failures.csv", index=False)
|
||||||
progress=round(progress, 2),
|
|
||||||
failureRate=round(failure_rate * 100, 2),
|
|
||||||
prompt=prompt[:MAX_PROMPT_LENGTH],
|
|
||||||
).model_dump_json()
|
|
||||||
|
|
||||||
if optimize and len(failure_rates) >= 5:
|
|
||||||
next_point = optimizer.ask()
|
|
||||||
optimizer.tell(next_point, -failure_rate)
|
|
||||||
best_failure_rate = -optimizer.get_result().fun
|
|
||||||
if best_failure_rate > 0.5:
|
|
||||||
yield ScanResult.status_msg(
|
|
||||||
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
yield ScanResult.status_msg("Scan completed.")
|
|
||||||
|
|
||||||
df = pd.DataFrame(
|
|
||||||
errors + refusals, columns=["module", "prompt", "status_code", "content"]
|
|
||||||
)
|
|
||||||
df.to_csv("failures.csv", index=False)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception("Scan failed")
|
|
||||||
yield ScanResult.status_msg(f"Scan failed: {str(e)}")
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
def scan_router(
|
def scan_router(
|
||||||
@@ -372,23 +368,27 @@ def scan_router(
|
|||||||
stop_event: asyncio.Event = None,
|
stop_event: asyncio.Event = None,
|
||||||
):
|
):
|
||||||
if scan_parameters.enableMultiStepAttack:
|
if scan_parameters.enableMultiStepAttack:
|
||||||
return perform_many_shot_scan(
|
return with_error_handling(
|
||||||
request_factory=request_factory,
|
perform_many_shot_scan(
|
||||||
max_budget=scan_parameters.maxBudget,
|
request_factory=request_factory,
|
||||||
datasets=scan_parameters.datasets,
|
max_budget=scan_parameters.maxBudget,
|
||||||
probe_datasets=scan_parameters.probe_datasets,
|
datasets=scan_parameters.datasets,
|
||||||
tools_inbox=tools_inbox,
|
probe_datasets=scan_parameters.probe_datasets,
|
||||||
optimize=scan_parameters.optimize,
|
tools_inbox=tools_inbox,
|
||||||
stop_event=stop_event,
|
optimize=scan_parameters.optimize,
|
||||||
secrets=scan_parameters.secrets,
|
stop_event=stop_event,
|
||||||
|
secrets=scan_parameters.secrets,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return perform_single_shot_scan(
|
return with_error_handling(
|
||||||
request_factory=request_factory,
|
perform_single_shot_scan(
|
||||||
max_budget=scan_parameters.maxBudget,
|
request_factory=request_factory,
|
||||||
datasets=scan_parameters.datasets,
|
max_budget=scan_parameters.maxBudget,
|
||||||
tools_inbox=tools_inbox,
|
datasets=scan_parameters.datasets,
|
||||||
optimize=scan_parameters.optimize,
|
tools_inbox=tools_inbox,
|
||||||
stop_event=stop_event,
|
optimize=scan_parameters.optimize,
|
||||||
secrets=scan_parameters.secrets,
|
stop_event=stop_event,
|
||||||
|
secrets=scan_parameters.secrets,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from fastapi import (
|
|||||||
UploadFile,
|
UploadFile,
|
||||||
)
|
)
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from ..core.app import get_stop_event, get_tools_inbox, set_current_run
|
from ..core.app import get_stop_event, get_tools_inbox, set_current_run
|
||||||
from ..dependencies import InMemorySecrets, get_in_memory_secrets
|
from ..dependencies import InMemorySecrets, get_in_memory_secrets
|
||||||
@@ -25,7 +26,12 @@ async def verify(
|
|||||||
info: LLMInfo, secrets: InMemorySecrets = Depends(get_in_memory_secrets)
|
info: LLMInfo, secrets: InMemorySecrets = Depends(get_in_memory_secrets)
|
||||||
):
|
):
|
||||||
spec = LLMSpec.from_string(info.spec)
|
spec = LLMSpec.from_string(info.spec)
|
||||||
r = await spec.verify()
|
try:
|
||||||
|
r = await spec.verify()
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
if r.status_code >= 400:
|
if r.status_code >= 400:
|
||||||
raise HTTPException(status_code=r.status_code, detail=r.text)
|
raise HTTPException(status_code=r.status_code, detail=r.text)
|
||||||
return dict(
|
return dict(
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
from:python-pytest-poetry
|
|
||||||
# This file was generated automatically by CodeBeaver based on your repository. Learn how to customize it here: https://docs.codebeaver.ai/configuration/
|
|
||||||
+1
-1
@@ -21,4 +21,4 @@ Note: Please be aware that Agentic Security is designed as a safety scanner tool
|
|||||||
|
|
||||||
## UI 🧙
|
## UI 🧙
|
||||||
|
|
||||||
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1736433557/z0bsyzhsqlgcr3w4ovwp.gif">
|
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1741192668/final_aa9jhb.gif">
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
:root {
|
:root {
|
||||||
--md-primary-fg-color: #e92063;
|
--md-primary-fg-color: #2E4053;
|
||||||
--md-primary-fg-color--light: #e92063;
|
/* Primary color changed to pinkish */
|
||||||
--md-primary-fg-color--dark: #e92063;
|
--md-primary-fg-color--light: #E0A3B6;
|
||||||
|
--md-primary-fg-color--dark: #1C3F74;
|
||||||
|
/* Dark variant changed to blue */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Updated slate color scheme with new background */
|
||||||
/* Revert hue value to that of pre mkdocs-material v9.4.0 */
|
|
||||||
[data-md-color-scheme="slate"] {
|
[data-md-color-scheme="slate"] {
|
||||||
--md-hue: 230;
|
--md-hue: 230;
|
||||||
--md-default-bg-color: hsla(230, 15%, 21%, 1);
|
--md-default-bg-color: #1A1A1A;
|
||||||
|
/* Background changed to dark gray */
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide {
|
.hide {
|
||||||
@@ -24,12 +26,15 @@ img.index-header {
|
|||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Updated custom colors */
|
||||||
.pydantic-pink {
|
.pydantic-pink {
|
||||||
color: #FF007F;
|
color: #E0A3B6;
|
||||||
|
/* Updated to match new theme */
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-blue {
|
.team-blue {
|
||||||
color: #0072CE;
|
color: #1C3F74;
|
||||||
|
/* Updated to match new theme */
|
||||||
}
|
}
|
||||||
|
|
||||||
.secure-green {
|
.secure-green {
|
||||||
@@ -67,7 +72,6 @@ img.index-header {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Hide the entire footer */
|
/* Hide the entire footer */
|
||||||
.md-footer {
|
.md-footer {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
+1
-1
@@ -89,7 +89,7 @@ theme:
|
|||||||
name: Switch to light mode
|
name: Switch to light mode
|
||||||
icon:
|
icon:
|
||||||
repo: fontawesome/brands/github
|
repo: fontawesome/brands/github
|
||||||
favicon: "https://res.cloudinary.com/dq0w2rtm9/image/upload/v1737555066/r17hrkre246doczwmvbv.png"
|
favicon: https://res.cloudinary.com/dq0w2rtm9/image/upload/v1741195421/favicon_kuz6xr.png
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
generator: false
|
generator: false
|
||||||
|
|||||||
Generated
+165
-256
@@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiohappyeyeballs"
|
name = "aiohappyeyeballs"
|
||||||
@@ -229,6 +229,24 @@ files = [
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backrefs"
|
||||||
|
version = "5.8"
|
||||||
|
description = "A wrapper around re and regex that adds additional back references."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
files = [
|
||||||
|
{file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"},
|
||||||
|
{file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"},
|
||||||
|
{file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"},
|
||||||
|
{file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"},
|
||||||
|
{file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"},
|
||||||
|
{file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
extras = ["regex"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "beautifulsoup4"
|
name = "beautifulsoup4"
|
||||||
version = "4.12.3"
|
version = "4.12.3"
|
||||||
@@ -1311,13 +1329,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.5"
|
version = "3.1.6"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||||
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1695,45 +1713,45 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matplotlib"
|
name = "matplotlib"
|
||||||
version = "3.10.0"
|
version = "3.10.1"
|
||||||
description = "Python plotting package"
|
description = "Python plotting package"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6"},
|
{file = "matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16"},
|
||||||
{file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e"},
|
{file = "matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2"},
|
||||||
{file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5"},
|
{file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698"},
|
||||||
{file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6"},
|
{file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19"},
|
||||||
{file = "matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1"},
|
{file = "matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044"},
|
||||||
{file = "matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3"},
|
{file = "matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f"},
|
||||||
{file = "matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363"},
|
{file = "matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401"},
|
||||||
{file = "matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997"},
|
{file = "matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe"},
|
||||||
{file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef"},
|
{file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd"},
|
||||||
{file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683"},
|
{file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c"},
|
||||||
{file = "matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765"},
|
{file = "matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7"},
|
||||||
{file = "matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a"},
|
{file = "matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a"},
|
||||||
{file = "matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59"},
|
{file = "matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107"},
|
||||||
{file = "matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a"},
|
{file = "matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be"},
|
||||||
{file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95"},
|
{file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6"},
|
||||||
{file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8"},
|
{file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d"},
|
||||||
{file = "matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12"},
|
{file = "matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea"},
|
||||||
{file = "matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc"},
|
{file = "matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25"},
|
{file = "matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908"},
|
{file = "matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2"},
|
{file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf"},
|
{file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae"},
|
{file = "matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442"},
|
{file = "matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06"},
|
{file = "matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff"},
|
{file = "matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593"},
|
{file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e"},
|
{file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede"},
|
{file = "matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f"},
|
||||||
{file = "matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c"},
|
{file = "matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9"},
|
||||||
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03"},
|
{file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc"},
|
||||||
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea"},
|
{file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4"},
|
||||||
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef"},
|
{file = "matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779"},
|
||||||
{file = "matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278"},
|
{file = "matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1848,13 +1866,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-autorefs"
|
name = "mkdocs-autorefs"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
description = "Automatically link across pages in MkDocs."
|
description = "Automatically link across pages in MkDocs."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocs_autorefs-1.3.0-py3-none-any.whl", hash = "sha256:d180f9778a04e78b7134e31418f238bba56f56d6a8af97873946ff661befffb3"},
|
{file = "mkdocs_autorefs-1.4.0-py3-none-any.whl", hash = "sha256:bad19f69655878d20194acd0162e29a89c3f7e6365ffe54e72aa3fd1072f240d"},
|
||||||
{file = "mkdocs_autorefs-1.3.0.tar.gz", hash = "sha256:6867764c099ace9025d6ac24fd07b85a98335fbd30107ef01053697c8f46db61"},
|
{file = "mkdocs_autorefs-1.4.0.tar.gz", hash = "sha256:a9c0aa9c90edbce302c09d050a3c4cb7c76f8b7b2c98f84a7a05f53d00392156"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1899,17 +1917,18 @@ pygments = ">2.12.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocs-material"
|
name = "mkdocs-material"
|
||||||
version = "9.6.4"
|
version = "9.6.7"
|
||||||
description = "Documentation that simply works"
|
description = "Documentation that simply works"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocs_material-9.6.4-py3-none-any.whl", hash = "sha256:414e8376551def6d644b8e6f77226022868532a792eb2c9accf52199009f568f"},
|
{file = "mkdocs_material-9.6.7-py3-none-any.whl", hash = "sha256:8a159e45e80fcaadd9fbeef62cbf928569b93df954d4dc5ba76d46820caf7b47"},
|
||||||
{file = "mkdocs_material-9.6.4.tar.gz", hash = "sha256:4d1d35e1c1d3e15294cb7fa5d02e0abaee70d408f75027dc7be6e30fb32e6867"},
|
{file = "mkdocs_material-9.6.7.tar.gz", hash = "sha256:3e2c1fceb9410056c2d91f334a00cdea3215c28750e00c691c1e46b2a33309b4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
babel = ">=2.10,<3.0"
|
babel = ">=2.10,<3.0"
|
||||||
|
backrefs = ">=5.7.post1,<6.0"
|
||||||
colorama = ">=0.4,<1.0"
|
colorama = ">=0.4,<1.0"
|
||||||
jinja2 = ">=3.0,<4.0"
|
jinja2 = ">=3.0,<4.0"
|
||||||
markdown = ">=3.2,<4.0"
|
markdown = ">=3.2,<4.0"
|
||||||
@@ -1918,7 +1937,6 @@ mkdocs-material-extensions = ">=1.3,<2.0"
|
|||||||
paginate = ">=0.5,<1.0"
|
paginate = ">=0.5,<1.0"
|
||||||
pygments = ">=2.16,<3.0"
|
pygments = ">=2.16,<3.0"
|
||||||
pymdown-extensions = ">=10.2,<11.0"
|
pymdown-extensions = ">=10.2,<11.0"
|
||||||
regex = ">=2022.4"
|
|
||||||
requests = ">=2.26,<3.0"
|
requests = ">=2.26,<3.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -1939,13 +1957,13 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mkdocstrings"
|
name = "mkdocstrings"
|
||||||
version = "0.28.1"
|
version = "0.28.2"
|
||||||
description = "Automatic documentation from sources, for MkDocs."
|
description = "Automatic documentation from sources, for MkDocs."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "mkdocstrings-0.28.1-py3-none-any.whl", hash = "sha256:a5878ae5cd1e26f491ff084c1f9ab995687d52d39a5c558e9b7023d0e4e0b740"},
|
{file = "mkdocstrings-0.28.2-py3-none-any.whl", hash = "sha256:57f79c557e2718d217d6f6a81bf75a0de097f10e922e7e5e00f085c3f0ff6895"},
|
||||||
{file = "mkdocstrings-0.28.1.tar.gz", hash = "sha256:fb64576906771b7701e8e962fd90073650ff689e95eb86e86751a66d65ab4489"},
|
{file = "mkdocstrings-0.28.2.tar.gz", hash = "sha256:9b847266d7a588ea76a8385eaebe1538278b4361c0d1ce48ed005be59f053569"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -1953,7 +1971,7 @@ Jinja2 = ">=2.11.1"
|
|||||||
Markdown = ">=3.6"
|
Markdown = ">=3.6"
|
||||||
MarkupSafe = ">=1.1"
|
MarkupSafe = ">=1.1"
|
||||||
mkdocs = ">=1.4"
|
mkdocs = ">=1.4"
|
||||||
mkdocs-autorefs = ">=1.3"
|
mkdocs-autorefs = ">=1.4"
|
||||||
mkdocs-get-deps = ">=0.2"
|
mkdocs-get-deps = ">=0.2"
|
||||||
pymdown-extensions = ">=6.3"
|
pymdown-extensions = ">=6.3"
|
||||||
|
|
||||||
@@ -2087,49 +2105,43 @@ dill = ">=0.3.8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.14.1"
|
version = "1.15.0"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
|
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
|
||||||
{file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
|
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
|
||||||
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
|
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"},
|
||||||
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
|
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"},
|
||||||
{file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
|
{file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"},
|
||||||
{file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
|
{file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"},
|
||||||
{file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
|
{file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"},
|
||||||
{file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
|
{file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"},
|
||||||
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
|
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"},
|
||||||
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
|
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"},
|
||||||
{file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
|
{file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"},
|
||||||
{file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
|
{file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"},
|
||||||
{file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
|
{file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"},
|
||||||
{file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
|
{file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"},
|
||||||
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
|
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"},
|
||||||
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
|
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"},
|
||||||
{file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
|
{file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"},
|
||||||
{file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
|
{file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"},
|
||||||
{file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
|
{file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"},
|
||||||
{file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
|
{file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"},
|
||||||
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
|
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"},
|
||||||
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
|
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"},
|
||||||
{file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
|
{file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"},
|
||||||
{file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
|
{file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"},
|
||||||
{file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
|
{file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"},
|
||||||
{file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
|
{file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"},
|
||||||
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
|
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"},
|
||||||
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
|
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"},
|
||||||
{file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
|
{file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"},
|
||||||
{file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
|
{file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"},
|
||||||
{file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
|
{file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"},
|
||||||
{file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
|
{file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"},
|
||||||
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
|
|
||||||
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
|
|
||||||
{file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
|
|
||||||
{file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
|
|
||||||
{file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
|
|
||||||
{file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2257,66 +2269,66 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "numpy"
|
name = "numpy"
|
||||||
version = "2.2.2"
|
version = "2.2.3"
|
||||||
description = "Fundamental package for array computing in Python"
|
description = "Fundamental package for array computing in Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10"
|
python-versions = ">=3.10"
|
||||||
files = [
|
files = [
|
||||||
{file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"},
|
{file = "numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"},
|
{file = "numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"},
|
{file = "numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"},
|
{file = "numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"},
|
{file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"},
|
{file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"},
|
{file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"},
|
{file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"},
|
{file = "numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094"},
|
||||||
{file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"},
|
{file = "numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"},
|
{file = "numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"},
|
{file = "numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"},
|
{file = "numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"},
|
{file = "numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"},
|
{file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"},
|
{file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"},
|
{file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"},
|
{file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"},
|
{file = "numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5"},
|
||||||
{file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"},
|
{file = "numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"},
|
{file = "numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"},
|
{file = "numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"},
|
{file = "numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"},
|
{file = "numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"},
|
{file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"},
|
{file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"},
|
{file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"},
|
{file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"},
|
{file = "numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe"},
|
||||||
{file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"},
|
{file = "numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"},
|
{file = "numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"},
|
{file = "numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"},
|
{file = "numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"},
|
{file = "numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"},
|
{file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"},
|
{file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"},
|
{file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"},
|
{file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"},
|
{file = "numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"},
|
{file = "numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"},
|
{file = "numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"},
|
{file = "numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"},
|
{file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"},
|
{file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"},
|
{file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"},
|
{file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"},
|
{file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"},
|
{file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"},
|
{file = "numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef"},
|
||||||
{file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"},
|
{file = "numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082"},
|
||||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"},
|
{file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d"},
|
||||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"},
|
{file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9"},
|
||||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"},
|
{file = "numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e"},
|
||||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"},
|
{file = "numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4"},
|
||||||
{file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"},
|
{file = "numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3161,13 +3173,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.4"
|
version = "8.3.5"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
|
||||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -3507,109 +3519,6 @@ files = [
|
|||||||
attrs = ">=22.2.0"
|
attrs = ">=22.2.0"
|
||||||
rpds-py = ">=0.7.0"
|
rpds-py = ">=0.7.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex"
|
|
||||||
version = "2024.11.6"
|
|
||||||
description = "Alternative regular expression module, to replace re."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
files = [
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
|
|
||||||
{file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
|
|
||||||
{file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
|
|
||||||
{file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
|
|
||||||
{file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
|
|
||||||
{file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
|
|
||||||
{file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
|
|
||||||
{file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "requests"
|
name = "requests"
|
||||||
version = "2.32.3"
|
version = "2.32.3"
|
||||||
@@ -4030,13 +3939,13 @@ widechars = ["wcwidth"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
description = "ANSI color formatting for output in terminal"
|
description = "ANSI color formatting for output in terminal"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"},
|
{file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"},
|
||||||
{file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"},
|
{file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "agentic_security"
|
name = "agentic_security"
|
||||||
version = "0.5.1"
|
version = "0.6.0"
|
||||||
description = "Agentic LLM vulnerability scanner"
|
description = "Agentic LLM vulnerability scanner"
|
||||||
authors = ["Alexander Miasoiedov <msoedov@gmail.com>"]
|
authors = ["Alexander Miasoiedov <msoedov@gmail.com>"]
|
||||||
maintainers = ["Alexander Miasoiedov <msoedov@gmail.com>"]
|
maintainers = ["Alexander Miasoiedov <msoedov@gmail.com>"]
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
import pytest
|
|
||||||
import asyncio
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from asyncio import Queue, Event
|
|
||||||
from agentic_security.core.app import create_app, get_tools_inbox, get_stop_event, get_current_run, set_current_run
|
|
||||||
|
|
||||||
class TestApp:
|
|
||||||
"""Test suite for agentic_security.core.app module."""
|
|
||||||
|
|
||||||
def test_create_app(self):
|
|
||||||
"""Test that create_app returns a FastAPI instance."""
|
|
||||||
app = create_app()
|
|
||||||
assert isinstance(app, FastAPI)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_get_tools_inbox(self):
|
|
||||||
"""Test that get_tools_inbox returns the global Queue instance."""
|
|
||||||
queue1 = get_tools_inbox()
|
|
||||||
await queue1.put("test item")
|
|
||||||
queue2 = get_tools_inbox()
|
|
||||||
result = queue2.get_nowait()
|
|
||||||
assert result == "test item"
|
|
||||||
|
|
||||||
def test_get_stop_event(self):
|
|
||||||
"""Test that get_stop_event returns the global Event instance and is not set initially."""
|
|
||||||
event = get_stop_event()
|
|
||||||
assert isinstance(event, Event)
|
|
||||||
assert not event.is_set()
|
|
||||||
|
|
||||||
def test_current_run_initial(self):
|
|
||||||
"""Test that get_current_run returns the global current_run with default values initially."""
|
|
||||||
run = get_current_run()
|
|
||||||
# Default values should be empty strings
|
|
||||||
assert run["spec"] == ""
|
|
||||||
assert run["id"] == ""
|
|
||||||
|
|
||||||
def test_set_current_run(self):
|
|
||||||
"""Test that set_current_run correctly updates current_run."""
|
|
||||||
spec = "test run"
|
|
||||||
result = set_current_run(spec)
|
|
||||||
expected_id = hash(id(spec))
|
|
||||||
# Verify that spec is set correctly
|
|
||||||
assert result["spec"] == spec
|
|
||||||
assert result["id"] == expected_id
|
|
||||||
|
|
||||||
def test_current_run_after_set(self):
|
|
||||||
"""Test that get_current_run returns the updated current_run after set_current_run is called."""
|
|
||||||
spec = "another test run"
|
|
||||||
set_current_run(spec)
|
|
||||||
current = get_current_run()
|
|
||||||
assert current["spec"] == spec
|
|
||||||
assert current["id"] == hash(id(spec))
|
|
||||||
def test_tools_inbox_same_instance(self):
|
|
||||||
"""Test that get_tools_inbox returns the same Queue instance by default."""
|
|
||||||
queue1 = get_tools_inbox()
|
|
||||||
queue2 = get_tools_inbox()
|
|
||||||
assert queue1 is queue2
|
|
||||||
|
|
||||||
def test_stop_event_set(self):
|
|
||||||
"""Test that setting the stop event is reflected in subsequent calls."""
|
|
||||||
event = get_stop_event()
|
|
||||||
event.set() # set the global event
|
|
||||||
# Now, subsequent calls should return the same event which is set.
|
|
||||||
event2 = get_stop_event()
|
|
||||||
assert event2.is_set()
|
|
||||||
|
|
||||||
def test_set_current_run_with_none(self):
|
|
||||||
"""Test that set_current_run handles None as a valid input and updates current_run accordingly."""
|
|
||||||
result = set_current_run(None)
|
|
||||||
expected_id = hash(id(None))
|
|
||||||
assert result["spec"] is None
|
|
||||||
assert result["id"] == expected_id
|
|
||||||
|
|
||||||
def test_multiple_current_run_assignments(self):
|
|
||||||
"""Test multiple assignments to current_run to ensure it always updates correctly."""
|
|
||||||
first_spec = "first run"
|
|
||||||
result1 = set_current_run(first_spec)
|
|
||||||
expected_id1 = hash(id(first_spec))
|
|
||||||
assert result1["spec"] == first_spec
|
|
||||||
assert result1["id"] == expected_id1
|
|
||||||
|
|
||||||
second_spec = "second run"
|
|
||||||
result2 = set_current_run(second_spec)
|
|
||||||
expected_id2 = hash(id(second_spec))
|
|
||||||
assert result2["spec"] == second_spec
|
|
||||||
assert result2["id"] == expected_id2
|
|
||||||
|
|
||||||
current = get_current_run()
|
|
||||||
# The current_run should reflect the latest assignment.
|
|
||||||
assert current["spec"] == second_spec
|
|
||||||
assert current["id"] == expected_id2
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_empty_tools_inbox_exception(self):
|
|
||||||
"""Test that calling get_nowait on an empty tools_inbox raises QueueEmpty."""
|
|
||||||
from asyncio import QueueEmpty
|
|
||||||
queue = get_tools_inbox()
|
|
||||||
# Clear any existing items in the queue
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
queue.get_nowait()
|
|
||||||
except QueueEmpty:
|
|
||||||
break
|
|
||||||
with pytest.raises(QueueEmpty):
|
|
||||||
queue.get_nowait()
|
|
||||||
|
|
||||||
def test_set_current_run_with_dict(self):
|
|
||||||
"""Test that set_current_run correctly handles a dictionary input as spec."""
|
|
||||||
spec = {"key": "value"}
|
|
||||||
result = set_current_run(spec)
|
|
||||||
expected_id = hash(id(spec))
|
|
||||||
assert result["spec"] == spec
|
|
||||||
assert result["id"] == expected_id
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_stop_event_wait(self):
|
|
||||||
"""Test that waiting on the stop event returns once the event is set."""
|
|
||||||
event = get_stop_event()
|
|
||||||
event.clear() # ensure event is not set
|
|
||||||
async def waiter():
|
|
||||||
await event.wait()
|
|
||||||
return True
|
|
||||||
waiter_task = asyncio.create_task(waiter())
|
|
||||||
# Wait a moment to ensure the waiter is pending
|
|
||||||
await asyncio.sleep(0.1)
|
|
||||||
assert not waiter_task.done()
|
|
||||||
event.set()
|
|
||||||
result = await waiter_task
|
|
||||||
assert result is True
|
|
||||||
|
|
||||||
def test_set_current_run_with_int(self):
|
|
||||||
"""Test that set_current_run handles an integer input as spec."""
|
|
||||||
spec = 12345
|
|
||||||
result = set_current_run(spec)
|
|
||||||
expected_id = hash(id(spec))
|
|
||||||
assert result["spec"] == spec
|
|
||||||
assert result["id"] == expected_id
|
|
||||||
|
|
||||||
def test_create_app_routes(self):
|
|
||||||
"""Test that create_app returns a FastAPI instance with default routes available."""
|
|
||||||
app = create_app()
|
|
||||||
paths = [route.path for route in app.routes]
|
|
||||||
# Check that the default OpenAPI route exists
|
|
||||||
assert "/openapi.json" in paths
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_tools_inbox_async_put_get_order(self):
|
|
||||||
"""Test that tools_inbox preserves order when items are added and retrieved asynchronously."""
|
|
||||||
queue = get_tools_inbox()
|
|
||||||
# Clear any existing items in the queue
|
|
||||||
from asyncio import QueueEmpty
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
queue.get_nowait()
|
|
||||||
except QueueEmpty:
|
|
||||||
break
|
|
||||||
items = ["first", "second", "third"]
|
|
||||||
for item in items:
|
|
||||||
await queue.put(item)
|
|
||||||
result_items = []
|
|
||||||
for _ in items:
|
|
||||||
result_items.append(await queue.get())
|
|
||||||
assert result_items == items
|
|
||||||
@@ -1,341 +0,0 @@
|
|||||||
import pytest
|
|
||||||
import base64
|
|
||||||
import httpx
|
|
||||||
import asyncio
|
|
||||||
from agentic_security.http_spec import (
|
|
||||||
LLMSpec,
|
|
||||||
parse_http_spec,
|
|
||||||
escape_special_chars_for_json,
|
|
||||||
encode_image_base64_by_url,
|
|
||||||
encode_audio_base64_by_url,
|
|
||||||
InvalidHTTPSpecError,
|
|
||||||
Modality
|
|
||||||
)
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Tests for agentic_security/http_spec.py
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
def test_escape_special_chars_for_json():
|
|
||||||
"""Test escaping special characters in a prompt for JSON safety."""
|
|
||||||
prompt = 'Line1\nLine2\t"Quote"\\Backslash'
|
|
||||||
escaped = escape_special_chars_for_json(prompt)
|
|
||||||
assert '\\n' in escaped
|
|
||||||
assert '\\t' in escaped
|
|
||||||
assert '\\"' in escaped
|
|
||||||
assert '\\\\' in escaped
|
|
||||||
|
|
||||||
def test_parse_http_spec_text():
|
|
||||||
"""Test parsing a text HTTP spec without image/audio/files requirements."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\nThis is a prompt: <<PROMPT>>"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
assert llm_spec.method == "POST"
|
|
||||||
assert llm_spec.url == "http://example.com/api"
|
|
||||||
assert llm_spec.headers["Content-Type"] == "application/json"
|
|
||||||
assert "<<PROMPT>>" in llm_spec.body
|
|
||||||
assert not llm_spec.has_files
|
|
||||||
assert not llm_spec.has_image
|
|
||||||
assert not llm_spec.has_audio
|
|
||||||
|
|
||||||
def test_parse_http_spec_files():
|
|
||||||
"""Test parsing a HTTP spec with multipart/form-data header indicating files."""
|
|
||||||
spec = "PUT http://example.com/upload\nContent-Type: multipart/form-data\n\nFile upload test"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
assert llm_spec.has_files
|
|
||||||
|
|
||||||
def test_parse_http_spec_image_audio():
|
|
||||||
"""Test parsing a HTTP spec that requires image and audio via placeholders."""
|
|
||||||
spec = "GET http://example.com/api\nContent-Type: application/json\n\nImage: <<BASE64_IMAGE>> and Audio: <<BASE64_AUDIO>>"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
assert llm_spec.has_image
|
|
||||||
assert llm_spec.has_audio
|
|
||||||
|
|
||||||
def test_encode_image_base64_by_url(monkeypatch):
|
|
||||||
"""Test that image encoding returns the correct base64 string with prefix."""
|
|
||||||
dummy_content = b'test_image'
|
|
||||||
class DummyResponse:
|
|
||||||
def __init__(self, content):
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
def dummy_get(url):
|
|
||||||
return DummyResponse(dummy_content)
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx, "get", dummy_get)
|
|
||||||
result = encode_image_base64_by_url("http://dummyurl.com/image.jpg")
|
|
||||||
expected = "data:image/jpeg;base64," + base64.b64encode(dummy_content).decode("utf-8")
|
|
||||||
assert result == expected
|
|
||||||
|
|
||||||
def test_encode_audio_base64_by_url(monkeypatch):
|
|
||||||
"""Test that audio encoding returns the correct base64 string with prefix."""
|
|
||||||
dummy_content = b'test_audio'
|
|
||||||
class DummyResponse:
|
|
||||||
def __init__(self, content):
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
def dummy_get(url):
|
|
||||||
return DummyResponse(dummy_content)
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx, "get", dummy_get)
|
|
||||||
result = encode_audio_base64_by_url("http://dummyurl.com/audio.mp3")
|
|
||||||
expected = "data:audio/mpeg;base64," + base64.b64encode(dummy_content).decode("utf-8")
|
|
||||||
assert result == expected
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_probe_text(monkeypatch):
|
|
||||||
"""Test the probe function for text modality by replacing <<PROMPT>>."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\n{\"prompt\": \"<<PROMPT>>\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, content, timeout):
|
|
||||||
return httpx.Response(200, text="ok")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
response = await llm_spec.probe("Hello")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "ok" in response.text
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_probe_with_files(monkeypatch):
|
|
||||||
"""Test that probe correctly branches to _probe_with_files when files are provided."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: multipart/form-data\n\nFile data"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
files = {"file": ("dummy.txt", b"data")}
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, files, timeout):
|
|
||||||
return httpx.Response(200, text="file upload ok")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
response = await llm_spec.probe("Unused", files=files)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "file upload ok" in response.text
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_verify_image(monkeypatch):
|
|
||||||
"""Test verify method branch for image modality by monkeypatching image encoder."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\n{\"image\": \"<<BASE64_IMAGE>>\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
# Replace the image encoder to return a dummy string
|
|
||||||
monkeypatch.setattr("agentic_security.http_spec.encode_image_base64_by_url", lambda url="": "dummy_image")
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, content, timeout):
|
|
||||||
# Check that the dummy image is injected in the content
|
|
||||||
assert "dummy_image" in content
|
|
||||||
return httpx.Response(200, text="image ok")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
response = await llm_spec.verify()
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "image ok" in response.text
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_verify_audio(monkeypatch):
|
|
||||||
"""Test verify method branch for audio modality by monkeypatching audio encoder."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\n{\"audio\": \"<<BASE64_AUDIO>>\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
monkeypatch.setattr("agentic_security.http_spec.encode_audio_base64_by_url", lambda url: "dummy_audio")
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, content, timeout):
|
|
||||||
# Ensure that the dummy audio string is present in the request content
|
|
||||||
assert "dummy_audio" in content
|
|
||||||
return httpx.Response(200, text="audio ok")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
response = await llm_spec.verify()
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "audio ok" in response.text
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_verify_files(monkeypatch):
|
|
||||||
"""Test verify method branch for files modality where _probe_with_files is invoked."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: multipart/form-data\n\nFile data"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, files, timeout):
|
|
||||||
return httpx.Response(200, text="files ok")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
response = await llm_spec.verify()
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert "files ok" in response.text
|
|
||||||
|
|
||||||
def test_llm_spec_modality_property():
|
|
||||||
"""Test that the modality property reflects the correct modality."""
|
|
||||||
spec_text = "POST http://example.com/api\nContent-Type: application/json\n\nPrompt: <<PROMPT>>"
|
|
||||||
llm_spec_text = parse_http_spec(spec_text)
|
|
||||||
assert llm_spec_text.modality == Modality.TEXT
|
|
||||||
|
|
||||||
spec_image = "POST http://example.com/api\nContent-Type: application/json\n\nImage: <<BASE64_IMAGE>>"
|
|
||||||
llm_spec_image = parse_http_spec(spec_image)
|
|
||||||
assert llm_spec_image.modality == Modality.IMAGE
|
|
||||||
|
|
||||||
spec_audio = "POST http://example.com/api\nContent-Type: application/json\n\nAudio: <<BASE64_AUDIO>>"
|
|
||||||
llm_spec_audio = parse_http_spec(spec_audio)
|
|
||||||
assert llm_spec_audio.modality == Modality.AUDIO
|
|
||||||
|
|
||||||
def test_from_string_invalid():
|
|
||||||
"""Test that LLMSpec.from_string raises an error for an invalid spec."""
|
|
||||||
invalid_spec = "INVALID_SPEC"
|
|
||||||
with pytest.raises(InvalidHTTPSpecError):
|
|
||||||
LLMSpec.from_string(invalid_spec)
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_validate_missing_files():
|
|
||||||
"""Test that LLMSpec.validate raises a ValueError when files are required but missing."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: multipart/form-data\n\nFile upload test"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
with pytest.raises(ValueError, match="Files are required"):
|
|
||||||
llm_spec.validate("test prompt", "", "", {})
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_validate_missing_image():
|
|
||||||
"""Test that LLMSpec.validate raises a ValueError when an image is required but missing."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\nImage: <<BASE64_IMAGE>>"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
with pytest.raises(ValueError, match="An image is required"):
|
|
||||||
llm_spec.validate("test prompt", "", "dummy_audio", {})
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_validate_missing_audio():
|
|
||||||
"""Test that LLMSpec.validate raises a ValueError when audio is required but missing."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\nAudio: <<BASE64_AUDIO>>"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
with pytest.raises(ValueError, match="Audio is required"):
|
|
||||||
llm_spec.validate("test prompt", "dummy_image", "", {})
|
|
||||||
|
|
||||||
def test_fn_alias(monkeypatch):
|
|
||||||
"""Test that LLMSpec.fn is a functional alias for LLMSpec.probe."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\n{\"prompt\": \"<<PROMPT>>\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
# Instead of overriding the instance method, verify the alias at the class level.
|
|
||||||
assert LLMSpec.fn is LLMSpec.probe
|
|
||||||
|
|
||||||
def test_escape_special_chars_no_special():
|
|
||||||
"""Test that the escape function returns the original string if no special characters are present."""
|
|
||||||
prompt = "Simple text without specials"
|
|
||||||
escaped = escape_special_chars_for_json(prompt)
|
|
||||||
assert escaped == "Simple text without specials"
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_probe_text_with_special_chars(monkeypatch):
|
|
||||||
"""Test probe for text modality with special characters in prompt ensuring escaped content."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\n{\"prompt\": \"<<PROMPT>>\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
captured = {}
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, content, timeout):
|
|
||||||
captured['content'] = content
|
|
||||||
return httpx.Response(200, text="ok")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
test_prompt = 'Hello\nWorld\t"Test"'
|
|
||||||
response = await llm_spec.probe(test_prompt)
|
|
||||||
expected_escaped = escape_special_chars_for_json(test_prompt)
|
|
||||||
assert expected_escaped in captured['content']
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_verify_both_image_audio(monkeypatch):
|
|
||||||
"""Test verify method when both image and audio placeholders are present.
|
|
||||||
Expect a ValueError because only the image branch is triggered by pattern matching and the missing audio causes validation to fail."""
|
|
||||||
spec = ("POST http://example.com/api\nContent-Type: application/json\n\n"
|
|
||||||
"{\"audio\": \"<<BASE64_AUDIO>>\", \"image\":\"<<BASE64_IMAGE>>\"}")
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
# Monkey patch the image encoder to return a dummy value
|
|
||||||
monkeypatch.setattr("agentic_security.http_spec.encode_image_base64_by_url", lambda url="": "dummy_image")
|
|
||||||
with pytest.raises(ValueError, match="Audio is required"):
|
|
||||||
await llm_spec.verify()
|
|
||||||
|
|
||||||
def test_parse_http_spec_invalid_header_format():
|
|
||||||
"""Test that parse_http_spec raises an error when a header line doesn't have the expected 'key: value' format."""
|
|
||||||
invalid_spec = "GET http://example.com/api\nInvalidHeaderWithoutColon\n\nBody with <<PROMPT>>"
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
parse_http_spec(invalid_spec)
|
|
||||||
|
|
||||||
def test_from_string_valid():
|
|
||||||
"""Test that LLMSpec.from_string returns a valid LLMSpec object when given a proper spec string."""
|
|
||||||
spec = "GET http://example.com/api\nContent-Type: application/json\n\n{ \"prompt\": \"<<PROMPT>>\" }"
|
|
||||||
llm_spec = LLMSpec.from_string(spec)
|
|
||||||
assert llm_spec.method == "GET"
|
|
||||||
assert llm_spec.url == "http://example.com/api"
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_parse_http_spec_multiline_body():
|
|
||||||
"""Test parsing an HTTP spec with a multiline body to ensure body concatenation works."""
|
|
||||||
spec = (
|
|
||||||
"PATCH http://example.com/api\n"
|
|
||||||
"Content-Type: application/json\n"
|
|
||||||
"\n"
|
|
||||||
"Line one of body\n"
|
|
||||||
"Line two of body\n"
|
|
||||||
"Line three"
|
|
||||||
)
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
# As implemented, the parser concatenates lines without newline delimiters
|
|
||||||
expected_body = "Line one of bodyLine two of bodyLine three"
|
|
||||||
assert llm_spec.body == expected_body
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_encode_image_default_argument(monkeypatch):
|
|
||||||
"""Test that encode_image_base64_by_url works with its default URL argument."""
|
|
||||||
dummy_content = b'default_image'
|
|
||||||
class DummyResponse:
|
|
||||||
def __init__(self, content):
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
def dummy_get(url):
|
|
||||||
# check that the default URL (which includes 'fluidicon.png') is used
|
|
||||||
assert "fluidicon.png" in url
|
|
||||||
return DummyResponse(dummy_content)
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx, "get", dummy_get)
|
|
||||||
result = encode_image_base64_by_url()
|
|
||||||
expected = "data:image/jpeg;base64," + base64.b64encode(dummy_content).decode("utf-8")
|
|
||||||
assert result == expected
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_probe_without_prompt_placeholder(monkeypatch):
|
|
||||||
"""Test the probe function when the request body does not include the <<PROMPT>> placeholder."""
|
|
||||||
spec = "POST http://example.com/api\nContent-Type: application/json\n\n{\"message\": \"No placeholder here\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
captured = {}
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, content, timeout):
|
|
||||||
captured['content'] = content
|
|
||||||
return httpx.Response(200, text="ok without placeholder")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
response = await llm_spec.probe("Ignored prompt")
|
|
||||||
assert "No placeholder here" in captured['content']
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
def test_validate_success():
|
|
||||||
"""Test that LLMSpec.validate does not raise an error when all required data is provided."""
|
|
||||||
# Test case for files: files are provided as required
|
|
||||||
spec_files = "POST http://example.com/api\nContent-Type: multipart/form-data\n\nFile upload"
|
|
||||||
llm_spec_files = parse_http_spec(spec_files)
|
|
||||||
llm_spec_files.validate("some prompt", "dummy_image", "dummy_audio", {"file": ("dummy.txt", b"data")})
|
|
||||||
|
|
||||||
# Test case for image: image is provided as required
|
|
||||||
spec_image = "POST http://example.com/api\nContent-Type: application/json\n\nImage: <<BASE64_IMAGE>>"
|
|
||||||
llm_spec_image = parse_http_spec(spec_image)
|
|
||||||
llm_spec_image.validate("some prompt", "dummy_image", "dummy_audio", {})
|
|
||||||
|
|
||||||
# Test case for audio: audio is provided as required
|
|
||||||
spec_audio = "POST http://example.com/api\nContent-Type: application/json\n\nAudio: <<BASE64_AUDIO>>"
|
|
||||||
llm_spec_audio = parse_http_spec(spec_audio)
|
|
||||||
llm_spec_audio.validate("some prompt", "dummy_image", "dummy_audio", {})
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
async def test_probe_invalid_url(monkeypatch):
|
|
||||||
"""Test that probe raises an exception when the HTTP client fails due to an invalid URL."""
|
|
||||||
spec = "GET http://nonexistent_url/api\nContent-Type: application/json\n\n{\"prompt\": \"<<PROMPT>>\"}"
|
|
||||||
llm_spec = parse_http_spec(spec)
|
|
||||||
|
|
||||||
async def dummy_request(self, method, url, headers, content, timeout):
|
|
||||||
raise httpx.RequestError("Invalid URL")
|
|
||||||
|
|
||||||
monkeypatch.setattr(httpx.AsyncClient, "request", dummy_request)
|
|
||||||
with pytest.raises(httpx.RequestError):
|
|
||||||
await llm_spec.probe("Test")
|
|
||||||
Reference in New Issue
Block a user