diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..95d88e14 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,168 @@ +# GitHub Copilot Instructions for SSVC + +This repository contains the **Stakeholder-Specific Vulnerability Categorization (SSVC)** project, which provides a system for prioritizing actions during vulnerability management. + +## Project Overview + +SSVC is a modular decision-making framework for vulnerability management that includes: +- Python modules for decision points, decision tables, and outcomes +- MkDocs-based documentation website +- Interactive calculators and policy explorers +- JSON/CSV data files for decision tables +- Docker and Make-based development and deployment + +## Technology Stack + +- **Primary Language**: Python 3.x +- **Package Management**: uv (package and project manager) +- **Build Tool**: Make +- **Documentation**: MkDocs with Material theme +- **Testing**: pytest +- **Data Models**: Pydantic for JSON schema validation +- **Scientific Computing**: NumPy, SciPy, scikit-learn +- **Web Framework**: FastAPI (for API endpoints) +- **Containerization**: Docker and Docker Compose + +## Project Structure + +- `/src/ssvc/` - Core Python modules for SSVC functionality + - `decision_points/` - Decision point definitions + - `decision_tables/` - Decision table implementations + - `api/` - FastAPI application + - `outcomes/` - Outcome definitions + - `dp_groups/` - Decision point groups + - `registry/` - Registry functionality +- `/docs/` - Markdown documentation source files +- `/data/` - JSON and CSV data files for decision tables +- `/src/test/` - Unit tests +- `/docker/` - Docker configurations +- `/obsolete/` - Deprecated code (do not modify) + +## Make Commands + +Use `make help` to see all available commands. Common targets include: + +- `make dev` - Set up development environment +- `make test` - Run tests locally +- `make docker_test` - Run tests in Docker +- `make docs_local` - Serve documentation locally (http://localhost:8000/SSVC/) +- `make docs` - Build and run documentation in Docker +- `make api_dev` - Run API locally with auto-reload +- `make api` - Build and run API in Docker +- `make mdlint_fix` - Run markdown linting with auto-fix +- `make regenerate_json` - Regenerate JSON files from Python modules + +## Development Workflow + +## Coding Conventions + +### Python Code + +- Follow PEP 8 style guidelines +- Use type hints for function signatures and return types +- Use Pydantic models for data validation +- Document classes and functions with docstrings +- Prefer explicit imports over wildcard imports +- Module structure uses absolute imports from `ssvc` package + +### Naming Conventions + +- Python files: `snake_case.py` +- Classes: `PascalCase` +- Functions/variables: `snake_case` +- Constants: `UPPER_SNAKE_CASE` + +## Testing Requirements + +### Test Structure + +- Unit tests use pytest framework +- Tests are located in `/src/test/` +- Test files follow pattern: `test_*.py` +- Run tests with: `make test` or `uv run pytest -v` + +### Test Coverage + +- Write tests for new Python modules +- Ensure decision points and tables have corresponding tests +- Test JSON schema validation +- Validate data model serialization/deserialization + +### Before Committing + +1. Run all tests: `make test` +2. Ensure no test failures +3. Fix any linting issues: `make mdlint_fix` +4. Verify documentation builds: `make docs_local` + +## Documentation + +### Writing Documentation + +- Documentation uses MkDocs with Material theme +- Files are in Markdown format in `/docs/` +- Use Python exec blocks for dynamic content generation +- Include examples and code snippets +- Follow existing documentation structure + +### Documentation Features + +- Automatic API documentation via mkdocstrings +- Python module imports for dynamic content generation +- BibTeX citations via mkdocs-bibtex +- Add markdown files to site navigation by specifying them in `mkdocs.yml` +- Include markdown files in other markdown files with `mkdocs-include-markdown-plugin` +- Dynamically generate content from python code blocks using the `markdown-exec` plugin + +## Data Files + +### JSON Files + +- Located in `/data/json/` +- Generated from Python Pydantic models +- Use JSON schema validation + +### CSV Files + +- Located in `/data/csv/` +- Define decision table outcomes +- Generated from python modules (The python data objects are authoritative) +- Allows users to explore customizing SSVC for specific environments + +## Common Pitfalls + +1. **Import Paths**: Use absolute imports like `from ssvc.module import Class`, not relative imports +2. **PYTHONPATH**: When running scripts directly, set `export PYTHONPATH=$PYTHONPATH:$(pwd)/src` +3. **JSON Regeneration**: After modifying decision points/tables, regenerate JSON with `make regenerate_json` +4. **Docker Context**: Some make targets use Docker, others run locally - check the Makefile +5. **Package Management**: Use `make` commands or `uv` directly, not pip +6. **Obsolete Code and Documentation**: Never modify files in `/obsolete/`, `/doc/`, or `/pdfs/` directories + +## API Development + +- FastAPI application is in `/src/ssvc/api/` +- Run locally with auto-reload: `make api_dev` (serves on http://127.0.0.1:8000/docs) +- Run in Docker: `make api` (serves on http://127.0.0.1:8001/SSVC/) + +## Git Workflow + +- Create feature branches for new work +- Write descriptive commit messages +- Reference issue numbers in commits when applicable +- Keep commits focused and atomic +- Run tests before pushing + +## Additional Resources + +- Main documentation: https://certcc.github.io/SSVC/ +- Source repository: https://github.com/CERTCC/SSVC +- SSVC Calculator: https://certcc.github.io/SSVC/ssvc-calc/ +- Contributing guide: See CONTRIBUTING.md +- Project wiki: https://github.com/CERTCC/SSVC/wiki + +## Special Notes + +- This project uses a MIT (SEI)-style license with Carnegie Mellon University copyright (see LICENSE file) +- Decision points and tables follow SSVC specification +- Backward compatibility is important for existing data files +- Documentation changes should be reflected in both `/docs/` and `/src/README.md` when applicable diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9eb89965..e4c8851c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,6 @@ updates: - package-ecosystem: "uv" # See documentation for possible values directories: - "/" - - "/src" # Location of package manifests schedule: interval: "weekly" groups: diff --git a/.github/workflows/deploy_site.yml b/.github/workflows/deploy_site.yml index 1b51a7f3..b266e69f 100644 --- a/.github/workflows/deploy_site.yml +++ b/.github/workflows/deploy_site.yml @@ -43,7 +43,7 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install uv - uv sync --project=src --no-dev + uv sync --no-dev - name: Setup Pages uses: actions/configure-pages@v5 @@ -51,7 +51,7 @@ jobs: - name: Build Site run: | export PYTHONPATH=src:$PYTHONPATH - uv run --project=src mkdocs build --clean --config-file mkdocs.yml + uv run mkdocs build --clean --config-file mkdocs.yml - name: Upload artifact uses: actions/upload-pages-artifact@v4 diff --git a/.github/workflows/link_checker.yml b/.github/workflows/link_checker.yml index 92f87994..9c953389 100644 --- a/.github/workflows/link_checker.yml +++ b/.github/workflows/link_checker.yml @@ -11,8 +11,7 @@ on: # run on any PR that changes this workflow - .github/workflows/linkchecker.yml # run on any PR that changes the pip requirements - - requirements.txt - - src/pyproject.toml + - pyproject.toml # let us trigger it manually workflow_dispatch: @@ -31,13 +30,13 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip uv - uv sync --dev --project=src + uv sync --dev - name: Build Site run: | - uv run --project=src mkdocs build --verbose --clean --config-file mkdocs.yml + uv run mkdocs build --verbose --clean --config-file mkdocs.yml - name: Check links run: | - uv run --project=src linkchecker site/index.html + uv run linkchecker site/index.html diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 05f5f658..03c2950b 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -28,16 +28,16 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip uv - uv sync --project=src --dev --frozen + uv sync --dev --frozen # - uses: psf/black@stable - name: Test with pytest run: | - uv run --project=src pytest + uv run pytest - name: Build run: | - uv build --project=src + uv build - name: Upload Artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ssvc path: src/dist/ssvc-*.tar.gz diff --git a/.markdownlint.yml b/.markdownlint.yml index 3c95d570..7be5007a 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,3 +1,5 @@ +ignores: + - .github/** default: true # disable noisy rules # 0004 Unordered List style diff --git a/Makefile b/Makefile index 1ae2d193..deaab0a4 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,8 @@ # Project-specific vars MKDOCS_PORT=8765 DOCKER_DIR=docker -PROJECT_DIR = ./src DOCKER_COMPOSE=docker-compose --project-directory $(DOCKER_DIR) -UV_RUN=uv run --project $(PROJECT_DIR) +UV_RUN=uv run # Targets .PHONY: all test docs api docker_test clean help mdlint_fix up down regenerate_json @@ -13,7 +12,7 @@ all: help dev: @echo "Set up dev environment..." - uv sync --dev --project $(PROJECT_DIR) + uv sync --dev mdlint_fix: @echo "Running markdownlint..." @@ -21,7 +20,7 @@ mdlint_fix: test: @echo "Running tests locally..." - uv run --project $(PROJECT_DIR) pytest -v + $(UV_RUN) pytest -v docker_test: @echo "Building the latest test image..." @@ -60,7 +59,7 @@ regenerate_json: clean: @echo "Cleaning up Docker resources..." $(DOCKER_COMPOSE) down --rmi local || true - rm -rf $(PROJECT_DIR)/.venv $(PROJECT_DIR)/uv.lock + help: @echo "Usage: make [target]" @echo "" diff --git a/docker/Dockerfile b/docker/Dockerfile index df7f668b..3fd4c214 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,35 +3,26 @@ RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf / RUN pip install --upgrade pip uv WORKDIR /app -ENV VIRTUAL_ENV=/app/.venv -ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" - -RUN python -m venv "${VIRTUAL_ENV}" FROM base AS dependencies -ARG BASE_DIR=.. -ARG SRC_DIR=${BASE_DIR}/src - # Copy the files we need -COPY ${BASE_DIR}/ /app +COPY . /app # Set the environment variable ENV PYTHONPATH=/app/src -COPY ${SRC_DIR}/pyproject.toml /app/src/pyproject.toml -COPY ${SRC_DIR}/uv.lock /app/src/uv.lock # install requirements -RUN uv sync --project=/app/src --frozen - +RUN uv sync --frozen FROM dependencies AS test + ENV PYTHONPATH=/app/src # Install pytest and dev dependencies -RUN uv sync --project=/app/src --frozen --dev +RUN uv sync --frozen --dev # Run the unit tests -CMD ["uv", "run", "--project=/app/src", "pytest"] +CMD ["uv", "run", "pytest"] FROM dependencies AS docs -CMD ["uv", "run", "--project=/app/src", "mkdocs", "serve", "--dev-addr", "0.0.0.0:8000"] +CMD ["uv", "run", "mkdocs", "serve", "--dev-addr", "0.0.0.0:8000"] FROM dependencies AS registry_api -CMD ["uv", "run", "--project=/app/src", "uvicorn", "ssvc.api.main:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uv", "run", "uvicorn", "ssvc.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker/env_example b/docker/env_example new file mode 100644 index 00000000..a4f3c626 --- /dev/null +++ b/docker/env_example @@ -0,0 +1,3 @@ +# copy or link this file to .env in this directory +# this helps avoid docker image/container naming collisions +COMPOSE_PROJECT_NAME=ssvc diff --git a/docs/_includes/default_automatable_values.md b/docs/_includes/default_automatable_values.md new file mode 100644 index 00000000..3f18db82 --- /dev/null +++ b/docs/_includes/default_automatable_values.md @@ -0,0 +1,5 @@ +!!! tip "Default Automatable Values" + + If nothing is known about [*Automatable*](/reference/decision_points/automatable.md), the safer answer to assume is [*yes*](/reference/decision_points/automatable.md). + [*Value Density*](/reference/decision_points/value_density.md) should always be answerable; if the product is uncommon, it is probably + [*diffuse*](/reference/decision_points/value_density.md). diff --git a/docs/_includes/default_exploitation_values.md b/docs/_includes/default_exploitation_values.md new file mode 100644 index 00000000..cdecbd66 --- /dev/null +++ b/docs/_includes/default_exploitation_values.md @@ -0,0 +1,4 @@ +!!! tip "Default Exploitation Values" + + [*Exploitation*](../reference/decision_points/exploitation.md) needs no special default; if adequate searches are made for exploit code and none is + found, the answer is [*none*](../reference/decision_points/exploitation.md). diff --git a/docs/_includes/default_mission_impact_values.md b/docs/_includes/default_mission_impact_values.md new file mode 100644 index 00000000..75fbba1b --- /dev/null +++ b/docs/_includes/default_mission_impact_values.md @@ -0,0 +1,5 @@ +!!! tip "Default Mission Impact Values" + + Similarly, with [*Mission Impact*](/reference/decision_points/mission_impact.md), the deployer should assume that the software is in use at the + organization for a reason, and that it supports essential functions unless they have evidence otherwise. + With a total lack of information, assume [*support crippled*](/reference/decision_points/mission_impact.md) as a default. diff --git a/docs/_includes/default_safety_values.md b/docs/_includes/default_safety_values.md new file mode 100644 index 00000000..1e1404ca --- /dev/null +++ b/docs/_includes/default_safety_values.md @@ -0,0 +1,6 @@ +!!! tip "Default Safety Values" + + If the decision maker knows nothing about the environment in which the device is used, we suggest assuming a + [*marginal*](../reference/decision_points/safety_impact.md) [*Safety Impact*](../reference/decision_points/safety_impact.md). + This position is conservative, but software is thoroughly embedded in daily life now, so we suggest that the decision + maker provide evidence that no one's well-being will suffer. diff --git a/docs/_includes/default_system_exposure_values.md b/docs/_includes/default_system_exposure_values.md new file mode 100644 index 00000000..248419c7 --- /dev/null +++ b/docs/_includes/default_system_exposure_values.md @@ -0,0 +1,5 @@ +!!! tip "Default System Exposure Values" + + If the deployer does not know their exposure, that + means they do not know where the devices are or how they are controlled, so they should assume + [*System Exposure*](../reference/decision_points/system_exposure.md) is [*open*](../reference/decision_points/system_exposure.md). diff --git a/docs/howto/bootstrap/collect.md b/docs/howto/bootstrap/collect.md index cc28d073..fad0255c 100644 --- a/docs/howto/bootstrap/collect.md +++ b/docs/howto/bootstrap/collect.md @@ -94,35 +94,15 @@ deployer may want to use that information to favor the latter. In the case where no information is available or the organization has not yet matured its initial situational analysis, we can suggest something like defaults for some decision points. -!!! tip "Default Exploitation Values" +{% include-markdown "../../_includes/default_exploitation_values.md" %} - [*Exploitation*](../../reference/decision_points/exploitation.md) needs no special default; if adequate searches are made for exploit code and none is - found, the answer is [*none*](../../reference/decision_points/exploitation.md). +{% include-markdown "../../_includes/default_system_exposure_values.md" %} -!!! tip "Default System Exposure Values" +{% include-markdown "../../_includes/default_automatable_values.md" %} - If the deployer does not know their exposure, that - means they do not know where the devices are or how they are controlled, so they should assume - [*System Exposure*](../../reference/decision_points/system_exposure.md) is [*open*](../../reference/decision_points/system_exposure.md). +{% include-markdown "../../_includes/default_safety_values.md" %} -!!! tip "Default Automatable Values" - - If nothing is known about [*Automatable*](../../reference/decision_points/automatable.md), the safer answer to assume is [*yes*](../../reference/decision_points/automatable.md). - [*Value Density*](../../reference/decision_points/value_density.md) should always be answerable; if the product is uncommon, it is probably - [*diffuse*](../../reference/decision_points/value_density.md). - -!!! tip "Default Safety Values" - - If the decision maker knows nothing about the environment in which the device is used, we suggest assuming a - [*marginal*](../../reference/decision_points/safety_impact.md) [*Safety Impact*](../../reference/decision_points/safety_impact.md). - This position is conservative, but software is thoroughly embedded in daily life now, so we suggest that the decision - maker provide evidence that no one’s well-being will suffer. - -!!! tip "Default Mission Impact Values" - - Similarly, with [*Mission Impact*](../../reference/decision_points/mission_impact.md), the deployer should assume that the software is in use at the - organization for a reason, and that it supports essential functions unless they have evidence otherwise. - With a total lack of information, assume [*support crippled*](../../reference/decision_points/mission_impact.md) as a default. +{% include-markdown "../../_includes/default_mission_impact_values.md" %} !!! example "Using Defaults" diff --git a/docs/howto/gathering_info/automatable.md b/docs/howto/gathering_info/automatable.md index d8ba917c..aa5a1c10 100644 --- a/docs/howto/gathering_info/automatable.md +++ b/docs/howto/gathering_info/automatable.md @@ -20,3 +20,5 @@ Liveness of Internet-connected services means quite a few overlapping things [@b For most vulnerabilities, an open port does not automatically mean that reconnaissance, weaponization, and delivery are automatable. Furthermore, discovery of a vulnerable service is not automatable in a situation where only two hosts are misconfigured to expose the service out of 2 million hosts that are properly configured. As discussed in in [Reasoning Steps Forward](../../topics/scope.md), the analyst should consider *credible* effects based on *known* use cases of the software system to be pragmatic about scope and providing values to decision points. + +{% include-markdown "../../_includes/default_automatable_values.md" %} diff --git a/docs/howto/gathering_info/exploitation.md b/docs/howto/gathering_info/exploitation.md index 9b391a52..7196f0f5 100644 --- a/docs/howto/gathering_info/exploitation.md +++ b/docs/howto/gathering_info/exploitation.md @@ -7,6 +7,8 @@ from ssvc.doc_helpers import example_block print(example_block(LATEST)) ``` +{% include-markdown "../../_includes/default_exploitation_values.md" %} + ## Public PoC [Historical Analysis of Exploit Availability Timelines](https://dl.acm.org/doi/10.5555/3485754.3485760) presents a method for searching the GitHub repositories of open-source exploit databases. This method could be employed to gather information about whether *PoC* is true. diff --git a/docs/howto/gathering_info/mission_impact.md b/docs/howto/gathering_info/mission_impact.md index 13936a51..1a70b5dd 100644 --- a/docs/howto/gathering_info/mission_impact.md +++ b/docs/howto/gathering_info/mission_impact.md @@ -12,3 +12,5 @@ At a minimum, understanding mission impact should include gathering information There are various sources of guidance on how to gather this information; see for example the FEMA guidance in [Continuity Directive 2](https://www.fema.gov/sites/default/files/2020-07/Federal_Continuity_Directive-2_June132017.pdf) or [OCTAVE FORTE](https://insights.sei.cmu.edu/insider-threat/2018/06/octave-forte-and-fair-connect-cyber-risk-practitioners-with-the-boardroom.html). This is part of risk management more broadly. It should require the vulnerability management team to interact with more senior management to understand mission priorities and other aspects of risk mitigation. + +{% include-markdown "../../_includes/default_mission_impact_values.md" %} diff --git a/docs/howto/gathering_info/system_exposure.md b/docs/howto/gathering_info/system_exposure.md index baf7a76c..ebc8f935 100644 --- a/docs/howto/gathering_info/system_exposure.md +++ b/docs/howto/gathering_info/system_exposure.md @@ -7,6 +7,8 @@ from ssvc.doc_helpers import example_block print(example_block(LATEST)) ``` +{% include-markdown "../../_includes/default_system_exposure_values.md" %} + *System Exposure* is primarily used by [Deployers](../../deployer_tree), so the question is about whether some specific system is in fact exposed, not a hypothetical or aggregate question about systems of that type. Therefore, it generally has a concrete answer, even though it may vary from vulnerable component to vulnerable component, based on their respective configurations. diff --git a/docs/reference/decision_points/automatable.md b/docs/reference/decision_points/automatable.md index c20ce7c1..1a2b527f 100644 --- a/docs/reference/decision_points/automatable.md +++ b/docs/reference/decision_points/automatable.md @@ -11,6 +11,8 @@ print(example_block(LATEST)) See this [HowTo](../../howto/gathering_info/automatable.md) for advice on gathering information about the Automatable decision point. +{% include-markdown "../../_includes/default_automatable_values.md" %} + !!! tip "See also" Automatable combines with [Value Density](./value_density.md) to inform diff --git a/docs/reference/decision_points/exploitation.md b/docs/reference/decision_points/exploitation.md index d2c0be4d..793a0232 100644 --- a/docs/reference/decision_points/exploitation.md +++ b/docs/reference/decision_points/exploitation.md @@ -11,6 +11,8 @@ print(example_block(LATEST)) See this [HowTo](../../howto/gathering_info/exploitation.md) for advice on gathering information about the Exploitation decision point. +{% include-markdown "../../_includes/default_exploitation_values.md" %} + The intent of this measure is the present state of exploitation of the vulnerability. The intent is not to predict future exploitation but only to acknowledge the current state of affairs. Predictive systems, such as EPSS, could be used to augment this decision or to notify stakeholders of likely changes [@jacobs2021epss]. ## CWE-IDs for *PoC* diff --git a/docs/reference/decision_points/mission_impact.md b/docs/reference/decision_points/mission_impact.md index 85b234e4..a480c300 100644 --- a/docs/reference/decision_points/mission_impact.md +++ b/docs/reference/decision_points/mission_impact.md @@ -11,6 +11,8 @@ print(example_block(LATEST)) See this [HowTo](../../howto/gathering_info/mission_impact.md) for advice on gathering information about the Mission Impact decision point. +{% include-markdown "../../_includes/default_mission_impact_values.md" %} + !!! tip "See also" Mission Impact combines with [Safety Impact](./safety_impact.md) to inform diff --git a/docs/reference/decision_points/safety_impact.md b/docs/reference/decision_points/safety_impact.md index 128275ba..1dc23e85 100644 --- a/docs/reference/decision_points/safety_impact.md +++ b/docs/reference/decision_points/safety_impact.md @@ -47,6 +47,8 @@ Aggregation suggests that the stakeholder’s response to this decision point ca ## Gathering Information About Safety Impact +{% include-markdown "../../_includes/default_safety_values.md" %} + The factors that influence the safety impact level are diverse. This paper does not exhaustively discuss how a stakeholder should answer a question; that is a topic for future work. At a minimum, understanding safety impact should include gathering information about survivability of the vulnerable component, determining available operator actions to compensate for the vulnerable component, understanding relevant insurance, and determining the viability of existing backup measures. diff --git a/docs/reference/decision_points/system_exposure.md b/docs/reference/decision_points/system_exposure.md index 32742d87..7fcd75d2 100644 --- a/docs/reference/decision_points/system_exposure.md +++ b/docs/reference/decision_points/system_exposure.md @@ -11,6 +11,8 @@ print(example_block(LATEST)) See this [HowTo](../../howto/gathering_info/system_exposure.md) for advice on gathering information about the System Exposure decision point. +{% include-markdown "../../_includes/default_system_exposure_values.md" %} + Measuring the attack surface precisely is difficult, and we do not propose to perfectly delineate between small and controlled access. Exposure should be judged against the system in its deployed context, which may differ from how it is commonly expected to be deployed. For example, the exposure of a device on a vehicle's CAN bus will vary depending on the presence of a cellular telemetry device on the same bus. diff --git a/docs/ssvc-explorer/simple.js b/docs/ssvc-explorer/simple.js index 58228f6e..4c815092 100644 --- a/docs/ssvc-explorer/simple.js +++ b/docs/ssvc-explorer/simple.js @@ -89,9 +89,9 @@ const graphModule = (function() { height = 800 - margin.top - margin.bottom if(showFullTree) { var add_offset = 0 - if(raw.length > 60 ) - add_offset = (raw.length - 60)*5 - height = 1300 - margin.top - margin.bottom + add_offset + if(raw.length > 60) + add_offset = (raw.length - 60)*10 + height = Math.max(1300, raw.length * 20) - margin.top - margin.bottom + add_offset } duration = 750 tree = d3.layout.tree() @@ -131,6 +131,7 @@ const graphModule = (function() { .attr("class","mgraph") .attr("width", svg_width) .attr("height", svg_height) + .attr("viewBox", "0 0 " + svg_width + " " + svg_height) .append("g") .attr("transform", default_translate) .attr("id","pgroup"); @@ -143,7 +144,20 @@ const graphModule = (function() { d3.select(self.frameElement).style("height", "700px"); } - + function truncateEllipsis(d) { + let dstr = d.name.split(":")[0]; + if (dstr.length > 20) { + let truncated = dstr.substring(0, 18); + if(/\s+$/.test(truncated) || /^\s/.test(dstr[18])) { + /* If it ends with spaces remove all spaces and the last + non-space character to show the word has been truncated */ + truncated = truncated.replace(/\s+$/, ""); + truncated = truncated.slice(0, -1); + } + dstr = truncated + "..."; + } + return dstr; + } function update(source) { var i = 0 var nodes = tree.nodes(root).reverse() @@ -197,14 +211,19 @@ const graphModule = (function() { .attr("dy", ".35em") .attr("class",function(d) { var fclass = d.name.split(":").shift().toLowerCase(); + fclass = fclass.replace(/^[^a-zA-Z_-]/,'C'); + fclass = fclass.replace(/[^a-zA-Z0-9_-]/g,'_'); if(!('children' in d)) - return "gvisible prechk-"+fclass+" finale"; + return "gvisible prechk-" + fclass + " finale"; return "gvisible prechk-"+fclass;}) - .text(function(d) { return d.name.split(":")[0]; }) + .attr("data-fullname", function(d) { + return d.name.split(":").shift(); + }) + .text(truncateEllipsis) .style("font-size",font) .style("fill", function(d) { - var t = d.name.split(":").shift(); - var x; + const t = d.name.split(":").shift(); + let x; if(t in lcolors) x = lcolors[t]; return x; @@ -309,6 +328,10 @@ const graphModule = (function() { var yOffset = 90; var xOffset = -xMin + yOffset; + var newHeight = xMax - xMin + 2 * yOffset; + if (newHeight > parseInt($('svg.mgraph').attr("height"))) { + $('svg.mgraph').attr("height", newHeight); + } svg.attr("transform", "translate(" + 100 + "," + xOffset + ")"); } function check_children(d,a,b) { @@ -363,13 +386,18 @@ const graphModule = (function() { var yoffset = -10 if(showFullTree) yoffset = -6 - d3.select("g#x"+id).append("text").attr("dx",-6).attr("dy",yoffset).attr("class","gtext") + d3.select("g#x"+id) + .append("text").attr("dx",-6) + .attr("dy",yoffset).attr("class","gtext") .append("textPath").attr("href","#f"+id).attr("class",xclass) .attr("text-anchor","middle") .attr("id","t"+id) .attr("csid",csid) .attr("parentname",pname) - .text(text).attr("startOffset",doffset+"%") + .attr("data-fullname", text) + .text(truncateEllipsis({name:text})) + .attr("fill", "#777") + .attr("startOffset",doffset+"%") .on("click",pathclick) .on("mouseover",showdiv) .on("mouseout",hidediv); diff --git a/mkdocs.yml b/mkdocs.yml index db1b780c..50276ec9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,7 +9,6 @@ nav: - 'tutorials/ssvc_overview.md' - Starting out with SSVC: 'tutorials/starting_points.md' - Other Resources: 'tutorials/other_resources.md' - - SSVC How-To: - Overview: 'howto/index.md' - Getting Started with SSVC: @@ -177,6 +176,7 @@ theme: plugins: - include-markdown: comments: false + rewrite_relative_links: true - search - table-reader: data_path: 'data/csvs' diff --git a/src/pyproject.toml b/pyproject.toml similarity index 94% rename from src/pyproject.toml rename to pyproject.toml index bf8874d3..93a556c4 100644 --- a/src/pyproject.toml +++ b/pyproject.toml @@ -67,8 +67,8 @@ exclude = ["test*"] # exclude packages matching these glob patterns (empty by d #namespaces = false # to disable scanning PEP 420 namespaces (true by default) [tool.setuptools_scm] -version_file = "ssvc/_version.py" -root = ".." +version_file = "./src/ssvc/_version.py" +root = "." local_scheme = "no-local-version" version_scheme = "no-guess-dev" @@ -77,7 +77,7 @@ version_scheme = "no-guess-dev" [tool.black] line-length = 79 -target-version = ['py38', 'py39', 'py310', 'py311'] +target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313'] [tool.pytest.ini_options] minversion = "6.0" @@ -88,6 +88,7 @@ testpaths = [ [dependency-groups] dev = [ + "black>=25.9.0", "linkchecker>=10.6.0", "pytest>=8.4.1", ] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index cd326bef..00000000 --- a/requirements.txt +++ /dev/null @@ -1,1164 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv export --project=src --no-editable -o ./requirements.txt -. -annotated-types==0.7.0 \ - --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ - --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 - # via pydantic -anyio==4.10.0 \ - --hash=sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6 \ - --hash=sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1 - # via - # httpx - # starlette - # watchfiles -attrs==25.3.0 \ - --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ - --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b - # via - # jsonschema - # referencing -babel==2.17.0 \ - --hash=sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d \ - --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2 - # via mkdocs-material -backrefs==5.9 \ - --hash=sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf \ - --hash=sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa \ - --hash=sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59 \ - --hash=sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b \ - --hash=sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f \ - --hash=sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9 \ - --hash=sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60 - # via mkdocs-material -beautifulsoup4==4.13.5 \ - --hash=sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695 \ - --hash=sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a - # via linkchecker -bracex==2.6 \ - --hash=sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952 \ - --hash=sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7 - # via wcmatch -certifi==2025.8.3 \ - --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ - --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 - # via - # httpcore - # httpx - # requests - # sentry-sdk -charset-normalizer==3.4.3 \ - --hash=sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154 \ - --hash=sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884 \ - --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ - --hash=sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f \ - --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ - --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ - --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ - --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ - --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ - --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ - --hash=sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f \ - --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ - --hash=sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392 \ - --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ - --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ - --hash=sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491 \ - --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ - --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ - --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ - --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ - --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ - --hash=sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc \ - --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ - --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ - --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ - --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ - --hash=sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018 \ - --hash=sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93 \ - --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ - --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ - --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ - --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ - --hash=sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1 \ - --hash=sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37 \ - --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 - # via requests -click==8.2.1 \ - --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ - --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b - # via - # mkdocs - # mkdocs-material - # rich-toolkit - # typer - # uvicorn -colorama==0.4.6 \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 - # via - # click - # griffe - # mkdocs - # mkdocs-material - # pytest - # uvicorn -dnspython==2.8.0 \ - --hash=sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af \ - --hash=sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f - # via - # email-validator - # linkchecker -email-validator==2.3.0 \ - --hash=sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4 \ - --hash=sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426 - # via - # fastapi - # pydantic -fastapi==0.117.1 \ - --hash=sha256:33c51a0d21cab2b9722d4e56dbb9316f3687155be6b276191790d8da03507552 \ - --hash=sha256:fb2d42082d22b185f904ca0ecad2e195b851030bd6c5e4c032d1c981240c631a - # via certcc-ssvc -fastapi-cli==0.0.13 \ - --hash=sha256:219b73ccfde7622559cef1d43197da928516acb4f21f2ec69128c4b90057baba \ - --hash=sha256:312addf3f57ba7139457cf0d345c03e2170cc5a034057488259c33cd7e494529 - # via fastapi -fastapi-cloud-cli==0.2.0 \ - --hash=sha256:115d9b1f198b09ecc66f67156d183babb4fc14431414cc2e57a7649624782da6 \ - --hash=sha256:8dc13f95246d80e625e2789a21760494e855d887f70caae109423d00064772d1 - # via fastapi-cli -ghp-import==2.1.0 \ - --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \ - --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343 - # via mkdocs -griffe==1.14.0 \ - --hash=sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0 \ - --hash=sha256:9d2a15c1eca966d68e00517de5d69dd1bc5c9f2335ef6c1775362ba5b8651a13 - # via mkdocstrings-python -h11==0.16.0 \ - --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ - --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 - # via - # httpcore - # uvicorn -httpcore==1.0.9 \ - --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ - --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 - # via httpx -httptools==0.6.4 \ - --hash=sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2 \ - --hash=sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8 \ - --hash=sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3 \ - --hash=sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5 \ - --hash=sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0 \ - --hash=sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071 \ - --hash=sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c \ - --hash=sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1 \ - --hash=sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44 \ - --hash=sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083 \ - --hash=sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660 \ - --hash=sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970 \ - --hash=sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2 \ - --hash=sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81 \ - --hash=sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f - # via uvicorn -httpx==0.28.1 \ - --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ - --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad - # via - # fastapi - # fastapi-cloud-cli -idna==3.10 \ - --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ - --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 - # via - # anyio - # email-validator - # httpx - # requests -iniconfig==2.1.0 \ - --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ - --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 - # via pytest -itsdangerous==2.2.0 \ - --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ - --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 - # via fastapi -jinja2==3.1.6 \ - --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ - --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 - # via - # fastapi - # mkdocs - # mkdocs-material - # mkdocstrings -joblib==1.5.2 \ - --hash=sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55 \ - --hash=sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241 - # via scikit-learn -jsonschema==4.25.1 \ - --hash=sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63 \ - --hash=sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85 - # via certcc-ssvc -jsonschema-specifications==2025.9.1 \ - --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe \ - --hash=sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d - # via jsonschema -latexcodec==3.0.1 \ - --hash=sha256:a9eb8200bff693f0437a69581f7579eb6bca25c4193515c09900ce76451e452e \ - --hash=sha256:e78a6911cd72f9dec35031c6ec23584de6842bfbc4610a9678868d14cdfb0357 - # via pybtex -linkchecker==10.6.0 \ - --hash=sha256:5268587ed0b0f7e7521b75905128c96856f30f67dad49f66e2c963bc174ca92d \ - --hash=sha256:fb7e8facda7749c2fa5fa5dc241c0adc302da3d31d588964a2570db501aa49e5 -markdown==3.9 \ - --hash=sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280 \ - --hash=sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a - # via - # mkdocs - # mkdocs-autorefs - # mkdocs-material - # mkdocstrings - # pymdown-extensions -markdown-exec==1.11.0 \ - --hash=sha256:0526957984980f55c02b425d32e8ac8bb21090c109c7012ff905d3ddcc468ceb \ - --hash=sha256:e0313a0dff715869a311d24853b3a7ecbbaa12e74eb0f3cf7d91401a7d8f0082 - # via certcc-ssvc -markdown-it-py==4.0.0 \ - --hash=sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147 \ - --hash=sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3 - # via rich -markupsafe==3.0.2 \ - --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ - --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ - --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ - --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ - --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ - --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ - --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ - --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ - --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ - --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ - --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ - --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ - --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ - --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ - --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ - --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ - --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ - --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ - --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ - --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ - --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ - --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ - --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ - --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ - --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ - --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ - --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ - --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ - --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ - --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ - --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 - # via - # jinja2 - # mkdocs - # mkdocs-autorefs - # mkdocstrings -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py -mergedeep==1.3.4 \ - --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \ - --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 - # via - # mkdocs - # mkdocs-get-deps -mkdocs==1.6.1 \ - --hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \ - --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e - # via - # certcc-ssvc - # mkdocs-autorefs - # mkdocs-bibtex - # mkdocs-include-markdown-plugin - # mkdocs-material - # mkdocs-table-reader-plugin - # mkdocstrings -mkdocs-autorefs==1.4.3 \ - --hash=sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9 \ - --hash=sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75 - # via - # mkdocstrings - # mkdocstrings-python -mkdocs-bibtex==4.4.0 \ - --hash=sha256:32a1e0624ab0e0edc3539a90a5ffe0a2cb965f03ad5df8746a9fc9e049b6778b \ - --hash=sha256:fc0ce0f9641b63f900585a48cc09f5817345bbaba1435abf361e21fafe279723 - # via certcc-ssvc -mkdocs-get-deps==0.2.0 \ - --hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \ - --hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 - # via mkdocs -mkdocs-include-markdown-plugin==7.2.0 \ - --hash=sha256:4a67a91ade680dc0e15f608e5b6343bec03372ffa112c40a4254c1bfb10f42f3 \ - --hash=sha256:d56cdaeb2d113fb66ed0fe4fb7af1da889926b0b9872032be24e19bbb09c9f5b - # via certcc-ssvc -mkdocs-material==9.6.18 \ - --hash=sha256:a2eb253bcc8b66f8c6eaf8379c10ed6e9644090c2e2e9d0971c7722dc7211c05 \ - --hash=sha256:dbc1e146a0ecce951a4d84f97b816a54936cdc9e1edd1667fc6868878ac06701 - # via - # certcc-ssvc - # mkdocs-print-site-plugin -mkdocs-material-extensions==1.3.1 \ - --hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \ - --hash=sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31 - # via - # certcc-ssvc - # mkdocs-material -mkdocs-print-site-plugin==2.8 \ - --hash=sha256:838bd0a9b7141c11c0f1fdaa51ffe70c35740bec1f07c0806f8018e92f93f9da \ - --hash=sha256:ab1c89cdb468352975e3bb3bb0ef25dcc2bb88931b03f173206dc95ab02f843f - # via certcc-ssvc -mkdocs-table-reader-plugin==3.1.0 \ - --hash=sha256:50a1302661c14d96b90ba0434ae96110441e0c653ce23559e3c6911fe79e7bd2 \ - --hash=sha256:eb15688ee8c0cd1a842f506f18973b87be22bd7baa5e2e551089de6b7f9ec25b - # via certcc-ssvc -mkdocstrings==0.30.0 \ - --hash=sha256:5d8019b9c31ddacd780b6784ffcdd6f21c408f34c0bd1103b5351d609d5b4444 \ - --hash=sha256:ae9e4a0d8c1789697ac776f2e034e2ddd71054ae1cf2c2bb1433ccfd07c226f2 - # via - # certcc-ssvc - # mkdocstrings-python -mkdocstrings-python==1.17.0 \ - --hash=sha256:49903fa355dfecc5ad0b891e78ff5d25d30ffd00846952801bbe8331e123d4b0 \ - --hash=sha256:c6295962b60542a9c7468a3b515ce8524616ca9f8c1a38c790db4286340ba501 - # via certcc-ssvc -networkx==3.4.2 \ - --hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \ - --hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f - # via certcc-ssvc -numpy==2.3.3 \ - --hash=sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b \ - --hash=sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54 \ - --hash=sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5 \ - --hash=sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970 \ - --hash=sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8 \ - --hash=sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3 \ - --hash=sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e \ - --hash=sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe \ - --hash=sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5 \ - --hash=sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b \ - --hash=sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652 \ - --hash=sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d \ - --hash=sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb \ - --hash=sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7 \ - --hash=sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a \ - --hash=sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf \ - --hash=sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93 \ - --hash=sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8 \ - --hash=sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19 \ - --hash=sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1 \ - --hash=sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b \ - --hash=sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d \ - --hash=sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc \ - --hash=sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86 \ - --hash=sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097 \ - --hash=sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a \ - --hash=sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7 \ - --hash=sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30 \ - --hash=sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c \ - --hash=sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8 \ - --hash=sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe \ - --hash=sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00 \ - --hash=sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6 \ - --hash=sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe \ - --hash=sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd \ - --hash=sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae \ - --hash=sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f \ - --hash=sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a \ - --hash=sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5 \ - --hash=sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0 \ - --hash=sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25 \ - --hash=sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593 \ - --hash=sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea \ - --hash=sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421 \ - --hash=sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf \ - --hash=sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7 \ - --hash=sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7 \ - --hash=sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf \ - --hash=sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20 \ - --hash=sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e \ - --hash=sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029 \ - --hash=sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021 \ - --hash=sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea \ - --hash=sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc \ - --hash=sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf \ - --hash=sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf - # via - # pandas - # scikit-learn - # scipy -orjson==3.11.3 \ - --hash=sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d \ - --hash=sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710 \ - --hash=sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f \ - --hash=sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce \ - --hash=sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a \ - --hash=sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077 \ - --hash=sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b \ - --hash=sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae \ - --hash=sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451 \ - --hash=sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804 \ - --hash=sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca \ - --hash=sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f \ - --hash=sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667 \ - --hash=sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27 \ - --hash=sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167 \ - --hash=sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1 \ - --hash=sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee \ - --hash=sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f \ - --hash=sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d \ - --hash=sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a \ - --hash=sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b \ - --hash=sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2 \ - --hash=sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc \ - --hash=sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43 \ - --hash=sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872 \ - --hash=sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e \ - --hash=sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be \ - --hash=sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810 \ - --hash=sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2 \ - --hash=sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424 \ - --hash=sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e \ - --hash=sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23 \ - --hash=sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d \ - --hash=sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4 \ - --hash=sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229 \ - --hash=sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633 \ - --hash=sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064 \ - --hash=sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc \ - --hash=sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049 \ - --hash=sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c \ - --hash=sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1 \ - --hash=sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c - # via fastapi -packaging==25.0 \ - --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ - --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f - # via - # mkdocs - # pytest -paginate==0.5.7 \ - --hash=sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945 \ - --hash=sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591 - # via mkdocs-material -pandas==2.3.2 \ - --hash=sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a \ - --hash=sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175 \ - --hash=sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e \ - --hash=sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2 \ - --hash=sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6 \ - --hash=sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b \ - --hash=sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b \ - --hash=sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811 \ - --hash=sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9 \ - --hash=sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9 \ - --hash=sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a \ - --hash=sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b \ - --hash=sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012 \ - --hash=sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae \ - --hash=sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9 \ - --hash=sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb \ - --hash=sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2 \ - --hash=sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57 \ - --hash=sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9 \ - --hash=sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370 \ - --hash=sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4 - # via - # certcc-ssvc - # mkdocs-table-reader-plugin -pathspec==0.12.1 \ - --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ - --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 - # via mkdocs -platformdirs==4.4.0 \ - --hash=sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85 \ - --hash=sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf - # via mkdocs-get-deps -pluggy==1.6.0 \ - --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ - --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 - # via pytest -pybtex==0.25.1 \ - --hash=sha256:9053b0d619409a0a83f38abad5d9921de5f7b3ede00742beafcd9f10ad0d8c5c \ - --hash=sha256:9eaf90267c7e83e225af89fea65c370afbf65f458220d3946a9e3049e1eca491 - # via mkdocs-bibtex -pydantic==2.11.7 \ - --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ - --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b - # via - # certcc-ssvc - # fastapi - # fastapi-cloud-cli - # pydantic-extra-types - # pydantic-settings -pydantic-core==2.33.2 \ - --hash=sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56 \ - --hash=sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef \ - --hash=sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a \ - --hash=sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f \ - --hash=sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916 \ - --hash=sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a \ - --hash=sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7 \ - --hash=sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025 \ - --hash=sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849 \ - --hash=sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b \ - --hash=sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e \ - --hash=sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea \ - --hash=sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac \ - --hash=sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162 \ - --hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \ - --hash=sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1 \ - --hash=sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5 \ - --hash=sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88 \ - --hash=sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290 \ - --hash=sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d \ - --hash=sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc \ - --hash=sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9 \ - --hash=sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9 \ - --hash=sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f \ - --hash=sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5 \ - --hash=sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab \ - --hash=sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1 \ - --hash=sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9 \ - --hash=sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011 \ - --hash=sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6 \ - --hash=sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2 \ - --hash=sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6 - # via pydantic -pydantic-extra-types==2.10.5 \ - --hash=sha256:1dcfa2c0cf741a422f088e0dbb4690e7bfadaaf050da3d6f80d6c3cf58a2bad8 \ - --hash=sha256:b60c4e23d573a69a4f1a16dd92888ecc0ef34fb0e655b4f305530377fa70e7a8 - # via fastapi -pydantic-settings==2.10.1 \ - --hash=sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee \ - --hash=sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796 - # via fastapi -pygments==2.19.2 \ - --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ - --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b - # via - # mkdocs-material - # pygments-ansi-color - # pytest - # rich -pygments-ansi-color==0.3.0 \ - --hash=sha256:7018954cf5b11d1e734383a1bafab5af613213f246109417fee3f76da26d5431 \ - --hash=sha256:7eb063feaecadad9d4d1fd3474cbfeadf3486b64f760a8f2a00fc25392180aba - # via markdown-exec -pymdown-extensions==10.16.1 \ - --hash=sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91 \ - --hash=sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d - # via - # markdown-exec - # mkdocs-material - # mkdocstrings -pypandoc==1.15 \ - --hash=sha256:4ededcc76c8770f27aaca6dff47724578428eca84212a31479403a9731fc2b16 \ - --hash=sha256:ea25beebe712ae41d63f7410c08741a3cab0e420f6703f95bc9b3a749192ce13 - # via mkdocs-bibtex -pytest==8.4.2 \ - --hash=sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01 \ - --hash=sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79 -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 - # via - # ghp-import - # pandas -python-dotenv==1.1.1 \ - --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ - --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab - # via - # pydantic-settings - # uvicorn -python-multipart==0.0.20 \ - --hash=sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 \ - --hash=sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13 - # via fastapi -pytz==2025.2 \ - --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \ - --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 - # via pandas -pyyaml==6.0.2 \ - --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ - --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ - --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ - --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ - --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ - --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ - --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ - --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ - --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ - --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ - --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ - --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ - --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ - --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ - --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ - --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ - --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ - --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ - --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba - # via - # fastapi - # mkdocs - # mkdocs-get-deps - # mkdocs-table-reader-plugin - # pybtex - # pymdown-extensions - # pyyaml-env-tag - # responses - # uvicorn -pyyaml-env-tag==1.1 \ - --hash=sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04 \ - --hash=sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff - # via mkdocs -rapidfuzz==3.14.1 \ - --hash=sha256:01eab10ec90912d7d28b3f08f6c91adbaf93458a53f849ff70776ecd70dd7a7a \ - --hash=sha256:0591df2e856ad583644b40a2b99fb522f93543c65e64b771241dda6d1cfdc96b \ - --hash=sha256:07a9fad3247e68798424bdc116c1094e88ecfabc17b29edf42a777520347648e \ - --hash=sha256:0afcf2d6cb633d0d4260d8df6a40de2d9c93e9546e2c6b317ab03f89aa120ad7 \ - --hash=sha256:13e0ea3d0c533969158727d1bb7a08c2cc9a816ab83f8f0dcfde7e38938ce3e6 \ - --hash=sha256:26db0e815213d04234298dea0d884d92b9cb8d4ba954cab7cf67a35853128a33 \ - --hash=sha256:2e3e61c9e80d8c26709d8aa5c51fdd25139c81a4ab463895f8a567f8347b0548 \ - --hash=sha256:37017b84953927807847016620d61251fe236bd4bcb25e27b6133d955bb9cafb \ - --hash=sha256:40301b93b99350edcd02dbb22e37ca5f2a75d0db822e9b3c522da451a93d6f27 \ - --hash=sha256:40875e0c06f1a388f1cab3885744f847b557e0b1642dfc31ff02039f9f0823ef \ - --hash=sha256:4373f914ff524ee0146919dea96a40a8200ab157e5a15e777a74a769f73d8a4a \ - --hash=sha256:44e741d785de57d1a7bae03599c1cbc7335d0b060a35e60c44c382566e22782e \ - --hash=sha256:474f416cbb9099676de54aa41944c154ba8d25033ee460f87bb23e54af6d01c9 \ - --hash=sha256:4acc20776f225ee37d69517a237c090b9fa7e0836a0b8bc58868e9168ba6ef6f \ - --hash=sha256:57047493a1f62f11354c7143c380b02f1b355c52733e6b03adb1cb0fe8fb8816 \ - --hash=sha256:5967d571243cfb9ad3710e6e628ab68c421a237b76e24a67ac22ee0ff12784d6 \ - --hash=sha256:5c1c3d07d53dcafee10599da8988d2b1f39df236aee501ecbd617bd883454fcd \ - --hash=sha256:60879fcae2f7618403c4c746a9a3eec89327d73148fb6e89a933b78442ff0669 \ - --hash=sha256:61458e83b0b3e2abc3391d0953c47d6325e506ba44d6a25c869c4401b3bc222c \ - --hash=sha256:61c5b9ab6f730e6478aa2def566223712d121c6f69a94c7cc002044799442afd \ - --hash=sha256:61d77e09b2b6bc38228f53b9ea7972a00722a14a6048be9a3672fb5cb08bad3a \ - --hash=sha256:6325ca435b99f4001aac919ab8922ac464999b100173317defb83eae34e82139 \ - --hash=sha256:67ea46fa8cc78174bad09d66b9a4b98d3068e85de677e3c71ed931a1de28171f \ - --hash=sha256:6ad3395a416f8b126ff11c788531f157c7debeb626f9d897c153ff8980da10fb \ - --hash=sha256:6c7c26025f7934a169a23dafea6807cfc3fb556f1dd49229faf2171e5d8101cc \ - --hash=sha256:6cb56b695421538fdbe2c0c85888b991d833b8637d2f2b41faa79cea7234c000 \ - --hash=sha256:6e9ee3e1eb0a027717ee72fe34dc9ac5b3e58119f1bd8dd15bc19ed54ae3e62b \ - --hash=sha256:6f571d20152fc4833b7b5e781b36d5e4f31f3b5a596a3d53cf66a1bd4436b4f4 \ - --hash=sha256:70c845b64a033a20c44ed26bc890eeb851215148cc3e696499f5f65529afb6cb \ - --hash=sha256:7a2d80cc1a4fcc7e259ed4f505e70b36433a63fa251f1bb69ff279fe376c5efd \ - --hash=sha256:7cd312c380d3ce9d35c3ec9726b75eee9da50e8a38e89e229a03db2262d3d96b \ - --hash=sha256:809515194f628004aac1b1b280c3734c5ea0ccbd45938c9c9656a23ae8b8f553 \ - --hash=sha256:83b8cc6336709fa5db0579189bfd125df280a554af544b2dc1c7da9cdad7e44d \ - --hash=sha256:876dc0c15552f3d704d7fb8d61bdffc872ff63bedf683568d6faad32e51bbce8 \ - --hash=sha256:893fdfd4f66ebb67f33da89eb1bd1674b7b30442fdee84db87f6cb9074bf0ce9 \ - --hash=sha256:8b41d95ef86a6295d353dc3bb6c80550665ba2c3bef3a9feab46074d12a9af8f \ - --hash=sha256:8d69f470d63ee824132ecd80b1974e1d15dd9df5193916901d7860cef081a260 \ - --hash=sha256:93b6294a3ffab32a9b5f9b5ca048fa0474998e7e8bb0f2d2b5e819c64cb71ec7 \ - --hash=sha256:9c83270e44a6ae7a39fc1d7e72a27486bccc1fa5f34e01572b1b90b019e6b566 \ - --hash=sha256:ace21f7a78519d8e889b1240489cd021c5355c496cb151b479b741a4c27f0a25 \ - --hash=sha256:ae2d57464b59297f727c4e201ea99ec7b13935f1f056c753e8103da3f2fc2404 \ - --hash=sha256:b02850e7f7152bd1edff27e9d584505b84968cacedee7a734ec4050c655a803c \ - --hash=sha256:b1fe6001baa9fa36bcb565e24e88830718f6c90896b91ceffcb48881e3adddbc \ - --hash=sha256:c8d1dd1146539e093b84d0805e8951475644af794ace81d957ca612e3eb31598 \ - --hash=sha256:cb5acf24590bc5e57027283b015950d713f9e4d155fda5cfa71adef3b3a84502 \ - --hash=sha256:cf75769662eadf5f9bd24e865c19e5ca7718e879273dce4e7b3b5824c4da0eb4 \ - --hash=sha256:d937dbeda71c921ef6537c6d41a84f1b8112f107589c9977059de57a1d726dd6 \ - --hash=sha256:da011a373722fac6e64687297a1d17dc8461b82cb12c437845d5a5b161bc24b9 \ - --hash=sha256:e06664c7fdb51c708e082df08a6888fce4c5c416d7e3cc2fa66dd80eb76a149d \ - --hash=sha256:e84d9a844dc2e4d5c4cabd14c096374ead006583304333c14a6fbde51f612a44 \ - --hash=sha256:f277801f55b2f3923ef2de51ab94689a0671a4524bf7b611de979f308a54cd6f \ - --hash=sha256:f51c7571295ea97387bac4f048d73cecce51222be78ed808263b45c79c40a440 \ - --hash=sha256:f8ff5dbe78db0a10c1f916368e21d328935896240f71f721e073cf6c4c8cdedd \ - --hash=sha256:f94d61e44db3fc95a74006a394257af90fa6e826c900a501d749979ff495d702 \ - --hash=sha256:fe2651258c1f1afa9b66f44bf82f639d5f83034f9804877a1bbbae2120539ad1 \ - --hash=sha256:fedd5097a44808dddf341466866e5c57a18a19a336565b4ff50aa8f09eb528f6 - # via thefuzz -referencing==0.36.2 \ - --hash=sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa \ - --hash=sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0 - # via - # jsonschema - # jsonschema-specifications -requests==2.32.5 \ - --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ - --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf - # via - # linkchecker - # mkdocs-bibtex - # mkdocs-material - # responses -responses==0.25.8 \ - --hash=sha256:0c710af92def29c8352ceadff0c3fe340ace27cf5af1bbe46fb71275bcd2831c \ - --hash=sha256:9374d047a575c8f781b94454db5cab590b6029505f488d12899ddb10a4af1cf4 - # via mkdocs-bibtex -rich==14.1.0 \ - --hash=sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f \ - --hash=sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8 - # via - # rich-toolkit - # typer -rich-toolkit==0.15.1 \ - --hash=sha256:36a0b1d9a135d26776e4b78f1d5c2655da6e0ef432380b5c6b523c8d8ab97478 \ - --hash=sha256:6f9630eb29f3843d19d48c3bd5706a086d36d62016687f9d0efa027ddc2dd08a - # via - # fastapi-cli - # fastapi-cloud-cli -rignore==0.6.4 \ - --hash=sha256:0a8184fcf567bd6b6d7b85a0c138d98dd40f63054141c96b175844414c5530d7 \ - --hash=sha256:0cc35773a8a9c119359ef974d0856988d4601d4daa6f532c05f66b4587cf35bc \ - --hash=sha256:145177f0e32716dc2f220b07b3cde2385b994b7ea28d5c96fbec32639e9eac6f \ - --hash=sha256:240777332b859dc89dcba59ab6e3f1e062bc8e862ffa3e5f456e93f7fd5cb415 \ - --hash=sha256:2521f7bf3ee1f2ab22a100a3a4eed39a97b025804e5afe4323528e9ce8f084a5 \ - --hash=sha256:2b3b1e266ce45189240d14dfa1057f8013ea34b9bc8b3b44125ec8d25fdb3985 \ - --hash=sha256:456456802b1e77d1e2d149320ee32505b8183e309e228129950b807d204ddd17 \ - --hash=sha256:45fe803628cc14714df10e8d6cdc23950a47eb9eb37dfea9a4779f4c672d2aa0 \ - --hash=sha256:465179bc30beb1f7a3439e428739a2b5777ed26660712b8c4e351b15a7c04483 \ - --hash=sha256:4a4877b4dca9cf31a4d09845b300c677c86267657540d0b4d3e6d0ce3110e6e9 \ - --hash=sha256:4c1ff2fc223f1d9473d36923160af37bf765548578eb9d47a2f52e90da8ae408 \ - --hash=sha256:4d1918221a249e5342b60fd5fa513bf3d6bf272a8738e66023799f0c82ecd788 \ - --hash=sha256:50359e0d5287b5e2743bd2f2fbf05df619c8282fd3af12f6628ff97b9675551d \ - --hash=sha256:52b0957b585ab48a445cf8ac1dbc33a272ab060835e583b4f95aa8c67c23fb2b \ - --hash=sha256:536392c5ec91755db48389546c833c4ab1426fe03e5a8522992b54ef8a244e7e \ - --hash=sha256:66b0e548753e55cc648f1e7b02d9f74285fe48bb49cec93643d31e563773ab3f \ - --hash=sha256:6971ac9fdd5a0bd299a181096f091c4f3fd286643adceba98eccc03c688a6637 \ - --hash=sha256:74720d074b79f32449d5d212ce732e0144a294a184246d1f1e7bcc1fc5c83b69 \ - --hash=sha256:7a6ccc0ea83d2c0c6df6b166f2acacedcc220a516436490f41e99a5ae73b6019 \ - --hash=sha256:84b5121650ae24621154c7bdba8b8970b0739d8146505c9f38e0cda9385d1004 \ - --hash=sha256:91dc94b1cc5af8d6d25ce6edd29e7351830f19b0a03b75cb3adf1f76d00f3007 \ - --hash=sha256:a63f5720dffc8d8fb0a4d02fafb8370a4031ebf3f99a4e79f334a91e905b7349 \ - --hash=sha256:b665b1ea14457d7b49e834baabc635a3b8c10cfb5cca5c21161fabdbfc2b850e \ - --hash=sha256:b79c212d9990a273ad91e8d9765e1766ef6ecedd3be65375d786a252762ba385 \ - --hash=sha256:bcb0d7d7ecc3fbccf6477bb187c04a091579ea139f15f139abe0b3b48bdfef69 \ - --hash=sha256:c6ffa7f2a8894c65aa5dc4e8ac8bbdf39a326c0c6589efd27686cfbb48f0197d \ - --hash=sha256:c7fd339f344a8548724f289495b835bed7b81174a0bc1c28c6497854bd8855db \ - --hash=sha256:ce33982da47ac5dc09d19b04fa8d7c9aa6292fc0bd1ecf33076989faa8886094 \ - --hash=sha256:d0615a6bf4890ec5a90b5fb83666822088fbd4e8fcd740c386fcce51e2f6feea \ - --hash=sha256:d899621867aa266824fbd9150e298f19d25b93903ef0133c09f70c65a3416eca \ - --hash=sha256:e02eecb9e1b9f9bf7c9030ae73308a777bed3b2486204cc74dfcfbe699ab1497 \ - --hash=sha256:e07d9c5270fc869bc431aadcfb6ed0447f89b8aafaa666914c077435dc76a123 \ - --hash=sha256:e439f034277a947a4126e2da79dbb43e33d73d7c09d3d72a927e02f8a16f59aa \ - --hash=sha256:e445fbc214ae18e0e644a78086ea5d0f579e210229a4fbe86367d11a4cd03c11 \ - --hash=sha256:e55bf8f9bbd186f58ab646b4a08718c77131d28a9004e477612b0cbbd5202db2 \ - --hash=sha256:e893fdd2d7fdcfa9407d0b7600ef2c2e2df97f55e1c45d4a8f54364829ddb0ab \ - --hash=sha256:efe18096dcb1596757dfe0b412aab6d32564473ae7ee58dea0a8b4be5b1a2e3b \ - --hash=sha256:f5f9dca46fc41c0a1e236767f68be9d63bdd2726db13a0ae3a30f68414472969 \ - --hash=sha256:feac73377a156fb77b3df626c76f7e5893d9b4e9e886ac8c0f9d44f1206a2a91 - # via fastapi-cloud-cli -rpds-py==0.27.1 \ - --hash=sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f \ - --hash=sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60 \ - --hash=sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2 \ - --hash=sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff \ - --hash=sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef \ - --hash=sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd \ - --hash=sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf \ - --hash=sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d \ - --hash=sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e \ - --hash=sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8 \ - --hash=sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5 \ - --hash=sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8 \ - --hash=sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418 \ - --hash=sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688 \ - --hash=sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502 \ - --hash=sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675 \ - --hash=sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a \ - --hash=sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734 \ - --hash=sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5 \ - --hash=sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92 \ - --hash=sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274 \ - --hash=sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3 \ - --hash=sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83 \ - --hash=sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817 \ - --hash=sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48 \ - --hash=sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772 \ - --hash=sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2 \ - --hash=sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802 \ - --hash=sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec \ - --hash=sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1 \ - --hash=sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a \ - --hash=sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39 \ - --hash=sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4 \ - --hash=sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797 \ - --hash=sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228 \ - --hash=sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881 \ - --hash=sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002 \ - --hash=sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998 \ - --hash=sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456 \ - --hash=sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd \ - --hash=sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e \ - --hash=sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334 \ - --hash=sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90 \ - --hash=sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2 \ - --hash=sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b \ - --hash=sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33 \ - --hash=sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2 \ - --hash=sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136 \ - --hash=sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212 \ - --hash=sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0 \ - --hash=sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb \ - --hash=sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a \ - --hash=sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21 \ - --hash=sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf \ - --hash=sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594 \ - --hash=sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e \ - --hash=sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7 \ - --hash=sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3 \ - --hash=sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723 \ - --hash=sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b \ - --hash=sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb \ - --hash=sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081 \ - --hash=sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7 \ - --hash=sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d \ - --hash=sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9 \ - --hash=sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444 \ - --hash=sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a \ - --hash=sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0 \ - --hash=sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83 \ - --hash=sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3 \ - --hash=sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2 \ - --hash=sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a \ - --hash=sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb \ - --hash=sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec - # via - # jsonschema - # referencing -scikit-learn==1.6.1 \ - --hash=sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691 \ - --hash=sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f \ - --hash=sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2 \ - --hash=sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86 \ - --hash=sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322 \ - --hash=sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f \ - --hash=sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35 \ - --hash=sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52 \ - --hash=sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb \ - --hash=sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b \ - --hash=sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236 \ - --hash=sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e \ - --hash=sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348 \ - --hash=sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1 \ - --hash=sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97 - # via certcc-ssvc -scipy==1.16.1 \ - --hash=sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f \ - --hash=sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919 \ - --hash=sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d \ - --hash=sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45 \ - --hash=sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc \ - --hash=sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6 \ - --hash=sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3 \ - --hash=sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b \ - --hash=sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc \ - --hash=sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3 \ - --hash=sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4 \ - --hash=sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731 \ - --hash=sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f \ - --hash=sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19 \ - --hash=sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608 \ - --hash=sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4 \ - --hash=sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921 \ - --hash=sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725 \ - --hash=sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3 \ - --hash=sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27 \ - --hash=sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318 \ - --hash=sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab \ - --hash=sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c \ - --hash=sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7 \ - --hash=sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8 \ - --hash=sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04 \ - --hash=sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb \ - --hash=sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695 \ - --hash=sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65 \ - --hash=sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6 \ - --hash=sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998 \ - --hash=sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39 \ - --hash=sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e \ - --hash=sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2 \ - --hash=sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65 \ - --hash=sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7 \ - --hash=sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c \ - --hash=sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0 \ - --hash=sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119 \ - --hash=sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d \ - --hash=sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86 \ - --hash=sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff \ - --hash=sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3 \ - --hash=sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a \ - --hash=sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618 \ - --hash=sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b - # via - # certcc-ssvc - # scikit-learn -semver==3.0.4 \ - --hash=sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746 \ - --hash=sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602 - # via certcc-ssvc -sentry-sdk==2.38.0 \ - --hash=sha256:2324aea8573a3fa1576df7fb4d65c4eb8d9929c8fa5939647397a07179eef8d0 \ - --hash=sha256:792d2af45e167e2f8a3347143f525b9b6bac6f058fb2014720b40b84ccbeb985 - # via fastapi-cloud-cli -setuptools==80.9.0 \ - --hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \ - --hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c - # via mkdocs-bibtex -shellingham==1.5.4 \ - --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \ - --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de - # via typer -six==1.17.0 \ - --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ - --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 - # via python-dateutil -sniffio==1.3.1 \ - --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ - --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc - # via anyio -soupsieve==2.8 \ - --hash=sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c \ - --hash=sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f - # via beautifulsoup4 -starlette==0.48.0 \ - --hash=sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659 \ - --hash=sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46 - # via fastapi -tabulate==0.9.0 \ - --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \ - --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f - # via mkdocs-table-reader-plugin -thefuzz==0.22.1 \ - --hash=sha256:59729b33556850b90e1093c4cf9e618af6f2e4c985df193fdf3c5b5cf02ca481 \ - --hash=sha256:7138039a7ecf540da323792d8592ef9902b1d79eb78c147d4f20664de79f3680 - # via certcc-ssvc -threadpoolctl==3.6.0 \ - --hash=sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb \ - --hash=sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e - # via scikit-learn -typer==0.19.1 \ - --hash=sha256:914b2b39a1da4bafca5f30637ca26fa622a5bf9f515e5fdc772439f306d5682a \ - --hash=sha256:cb881433a4b15dacc875bb0583d1a61e78497806741f9aba792abcab390c03e6 - # via - # fastapi-cli - # fastapi-cloud-cli -typing-extensions==4.15.0 \ - --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ - --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 - # via - # anyio - # beautifulsoup4 - # fastapi - # pydantic - # pydantic-core - # pydantic-extra-types - # referencing - # rich-toolkit - # starlette - # typer - # typing-inspection -typing-inspection==0.4.1 \ - --hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \ - --hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28 - # via - # pydantic - # pydantic-settings -tzdata==2025.2 \ - --hash=sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8 \ - --hash=sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9 - # via pandas -ujson==5.11.0 \ - --hash=sha256:0180a480a7d099082501cad1fe85252e4d4bf926b40960fb3d9e87a3a6fbbc80 \ - --hash=sha256:04c41afc195fd477a59db3a84d5b83a871bd648ef371cf8c6f43072d89144eef \ - --hash=sha256:090b4d11b380ae25453100b722d0609d5051ffe98f80ec52853ccf8249dfd840 \ - --hash=sha256:109f59885041b14ee9569bf0bb3f98579c3fa0652317b355669939e5fc5ede53 \ - --hash=sha256:10f29e71ecf4ecd93a6610bd8efa8e7b6467454a363c3d6416db65de883eb076 \ - --hash=sha256:1194b943e951092db611011cb8dbdb6cf94a3b816ed07906e14d3bc6ce0e90ab \ - --hash=sha256:12b5e7e22a1fe01058000d1b317d3b65cc3daf61bd2ea7a2b76721fe160fa74d \ - --hash=sha256:1a0a9b76a89827a592656fe12e000cf4f12da9692f51a841a4a07aa4c7ecc41c \ - --hash=sha256:1a325fd2c3a056cf6c8e023f74a0c478dd282a93141356ae7f16d5309f5ff823 \ - --hash=sha256:1aa8a2ab482f09f6c10fba37112af5f957689a79ea598399c85009f2f29898b5 \ - --hash=sha256:1d663b96eb34c93392e9caae19c099ec4133ba21654b081956613327f0e973ac \ - --hash=sha256:29113c003ca33ab71b1b480bde952fbab2a0b6b03a4ee4c3d71687cdcbd1a29d \ - --hash=sha256:34032aeca4510a7c7102bd5933f59a37f63891f30a0706fb46487ab6f0edf8f0 \ - --hash=sha256:3772e4fe6b0c1e025ba3c50841a0ca4786825a4894c8411bf8d3afe3a8061328 \ - --hash=sha256:48055e1061c1bb1f79e75b4ac39e821f3f35a9b82de17fce92c3140149009bec \ - --hash=sha256:49e56ef8066f11b80d620985ae36869a3ff7e4b74c3b6129182ec5d1df0255f3 \ - --hash=sha256:4c9f5d6a27d035dd90a146f7761c2272cf7103de5127c9ab9c4cd39ea61e878a \ - --hash=sha256:65724738c73645db88f70ba1f2e6fb678f913281804d5da2fd02c8c5839af302 \ - --hash=sha256:6cd2df62f24c506a0ba322d5e4fe4466d47a9467b57e881ee15a31f7ecf68ff6 \ - --hash=sha256:6dd703c3e86dc6f7044c5ac0b3ae079ed96bf297974598116aa5fb7f655c3a60 \ - --hash=sha256:787aff4a84da301b7f3bac09bc696e2e5670df829c6f8ecf39916b4e7e24e701 \ - --hash=sha256:7895f0d2d53bd6aea11743bd56e3cb82d729980636cd0ed9b89418bf66591702 \ - --hash=sha256:78c684fb21255b9b90320ba7e199780f653e03f6c2528663768965f4126a5b50 \ - --hash=sha256:7e3cff632c1d78023b15f7e3a81c3745cd3f94c044d1e8fa8efbd6b161997bbc \ - --hash=sha256:80017e870d882d5517d28995b62e4e518a894f932f1e242cbc802a2fd64d365c \ - --hash=sha256:8254e858437c00f17cb72e7a644fc42dad0ebb21ea981b71df6e84b1072aaa7c \ - --hash=sha256:837da4d27fed5fdc1b630bd18f519744b23a0b5ada1bbde1a36ba463f2900c03 \ - --hash=sha256:849e65b696f0d242833f1df4182096cedc50d414215d1371fca85c541fbff629 \ - --hash=sha256:8fa2af7c1459204b7a42e98263b069bd535ea0cd978b4d6982f35af5a04a4241 \ - --hash=sha256:a0af6574fc1d9d53f4ff371f58c96673e6d988ed2b5bf666a6143c782fa007e9 \ - --hash=sha256:a31c6b8004438e8c20fc55ac1c0e07dad42941db24176fe9acf2815971f8e752 \ - --hash=sha256:a638425d3c6eed0318df663df44480f4a40dc87cc7c6da44d221418312f6413b \ - --hash=sha256:aa6d7a5e09217ff93234e050e3e380da62b084e26b9f2e277d2606406a2fc2e5 \ - --hash=sha256:ab2cb8351d976e788669c8281465d44d4e94413718af497b4e7342d7b2f78018 \ - --hash=sha256:b16930f6a0753cdc7d637b33b4e8f10d5e351e1fb83872ba6375f1e87be39746 \ - --hash=sha256:b7b136cc6abc7619124fd897ef75f8e63105298b5ca9bdf43ebd0e1fa0ee105f \ - --hash=sha256:be6b0eaf92cae8cdee4d4c9e074bde43ef1c590ed5ba037ea26c9632fb479c88 \ - --hash=sha256:c44c703842024d796b4c78542a6fcd5c3cb948b9fc2a73ee65b9c86a22ee3638 \ - --hash=sha256:ce076f2df2e1aa62b685086fbad67f2b1d3048369664b4cdccc50707325401f9 \ - --hash=sha256:de6e88f62796372fba1de973c11138f197d3e0e1d80bcb2b8aae1e826096d433 \ - --hash=sha256:e204ae6f909f099ba6b6b942131cee359ddda2b6e4ea39c12eb8b991fe2010e0 \ - --hash=sha256:e73df8648c9470af2b6a6bf5250d4744ad2cf3d774dcf8c6e31f018bdd04d764 \ - --hash=sha256:e750c436fb90edf85585f5c62a35b35082502383840962c6983403d1bd96a02c \ - --hash=sha256:f278b31a7c52eb0947b2db55a5133fbc46b6f0ef49972cd1a80843b72e135aba \ - --hash=sha256:fa79fdb47701942c2132a9dd2297a1a85941d966d8c87bfd9e29b0cf423f26cc - # via fastapi -urllib3==2.5.0 \ - --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ - --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc - # via - # requests - # responses - # sentry-sdk -uvicorn==0.36.0 \ - --hash=sha256:527dc68d77819919d90a6b267be55f0e76704dca829d34aea9480be831a9b9d9 \ - --hash=sha256:6bb4ba67f16024883af8adf13aba3a9919e415358604ce46780d3f9bdc36d731 - # via - # fastapi - # fastapi-cli - # fastapi-cloud-cli -uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' \ - --hash=sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f \ - --hash=sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c \ - --hash=sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3 \ - --hash=sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb \ - --hash=sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6 \ - --hash=sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af \ - --hash=sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc \ - --hash=sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553 \ - --hash=sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d \ - --hash=sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc \ - --hash=sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281 \ - --hash=sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816 \ - --hash=sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2 - # via uvicorn -validators==0.35.0 \ - --hash=sha256:992d6c48a4e77c81f1b4daba10d16c3a9bb0dbb79b3a19ea847ff0928e70497a \ - --hash=sha256:e8c947097eae7892cb3d26868d637f79f47b4a0554bc6b80065dfe5aac3705dd - # via mkdocs-bibtex -watchdog==6.0.0 \ - --hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \ - --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 \ - --hash=sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f \ - --hash=sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c \ - --hash=sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c \ - --hash=sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0 \ - --hash=sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13 \ - --hash=sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 \ - --hash=sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e \ - --hash=sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379 \ - --hash=sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282 \ - --hash=sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b \ - --hash=sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f \ - --hash=sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948 \ - --hash=sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860 \ - --hash=sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 \ - --hash=sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26 - # via mkdocs -watchfiles==1.1.0 \ - --hash=sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6 \ - --hash=sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a \ - --hash=sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259 \ - --hash=sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297 \ - --hash=sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1 \ - --hash=sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a \ - --hash=sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b \ - --hash=sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb \ - --hash=sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b \ - --hash=sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339 \ - --hash=sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9 \ - --hash=sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb \ - --hash=sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4 \ - --hash=sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8 \ - --hash=sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30 \ - --hash=sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0 \ - --hash=sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5 \ - --hash=sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb \ - --hash=sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e \ - --hash=sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575 \ - --hash=sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f \ - --hash=sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f \ - --hash=sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92 \ - --hash=sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b \ - --hash=sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b \ - --hash=sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd \ - --hash=sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4 \ - --hash=sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0 \ - --hash=sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297 \ - --hash=sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef \ - --hash=sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179 \ - --hash=sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee \ - --hash=sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011 \ - --hash=sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e \ - --hash=sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf \ - --hash=sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db \ - --hash=sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20 \ - --hash=sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa \ - --hash=sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f \ - --hash=sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018 \ - --hash=sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd \ - --hash=sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47 \ - --hash=sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb \ - --hash=sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147 \ - --hash=sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8 \ - --hash=sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670 \ - --hash=sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c \ - --hash=sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5 \ - --hash=sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e \ - --hash=sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e \ - --hash=sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895 \ - --hash=sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7 \ - --hash=sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc \ - --hash=sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633 \ - --hash=sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f \ - --hash=sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77 \ - --hash=sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12 - # via uvicorn -wcmatch==10.1 \ - --hash=sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a \ - --hash=sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af - # via mkdocs-include-markdown-plugin -websockets==15.0.1 \ - --hash=sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2 \ - --hash=sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5 \ - --hash=sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8 \ - --hash=sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375 \ - --hash=sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597 \ - --hash=sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f \ - --hash=sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3 \ - --hash=sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4 \ - --hash=sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665 \ - --hash=sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22 \ - --hash=sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675 \ - --hash=sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4 \ - --hash=sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65 \ - --hash=sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151 \ - --hash=sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d \ - --hash=sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee \ - --hash=sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa \ - --hash=sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9 \ - --hash=sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe \ - --hash=sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561 \ - --hash=sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215 \ - --hash=sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931 \ - --hash=sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f \ - --hash=sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7 - # via uvicorn diff --git a/src/README.md b/src/README.md index 0e070feb..a244f011 100644 --- a/src/README.md +++ b/src/README.md @@ -97,6 +97,28 @@ For usage in vulnerability management scenarios consider the following popular S from ssvc.decision_tables.helpers import ascii_tree print(ascii_tree(CISACoordinate)) + #Creating an SSVC Selection for publish/export to external providers like CSAF or CVE + from datetime import datetime, timezone + from ssvc.decision_tables.cisa.cisa_coordinate_dt import LATEST as decision_table + from ssvc import selection + namespace = "ssvc" + decision_points = ["Exploitation"] + values = [["Public PoC"]] + timestamp = datetime.now() + selections = [] + + for dp in decision_table.decision_points.values(): + if dp.namespace == namespace and dp.name in decision_points: + dp_index = decision_points.index(dp.name) + selected = selection.Selection.from_decision_point(dp) + selected.values = tuple(selection.MinimalDecisionPointValue(key=val.key, + name=val.name) for val in dp.values if val.name in values[dp_index]) + selections.append(selected) + + out = selection.SelectionList(selections=selections,timestamp=timestamp) + print(out.model_dump_json(exclude_none=True, indent=4)) + + Resources --------- diff --git a/src/ssvc/api/README.md b/src/ssvc/api/README.md new file mode 100644 index 00000000..33417d04 --- /dev/null +++ b/src/ssvc/api/README.md @@ -0,0 +1,56 @@ +# SSVC API Readme + +This directory contains source code for the SSVC API. + +## Prerequisites + +- `uv` CLI tool installed. You can install it via pip: + +```shell +pip install uv +``` + +We recommend using `uv` to manage your Python environment and dependencies, +so you don't need to manually create and activate virtual environments or +worry about Python versions. + +## Running a local instance in development mode + +From the project root, run: + +```shell +uv --project=src run uvicorn ssvc.api.main:app --reload --port=7777 +``` + +> [!TIP] +> Adjust the port as needed. + +> [!NOTE] +> We're planning to move our `pyproject.toml` to the top level of the project, +> so in the future you may be able to run this command without the `--project` flag. + +This will start the FastAPI server with auto-reload enabled, allowing you to +see changes immediately. + +## Running a local instance in production mode + +From the project root, run: + +```shell +cd docker +docker-compose up api +``` + +This will start the FastAPI server in a Docker container. + +> [!NOTE] +> Docker and Docker Compose must be installed on your machine to use this method. +> Make sure to adjust the `docker-compose.yml` file if you want to change +> the port or other settings. + +> [!TIP] +> The `api` docker target copies the code into the container at build time. +> If you make changes to the code, you'll need to rebuild the Docker image +> using `docker-compose build api` before restarting the container. Or else +> use `docker-compose up --build api` to build and start in one command. + diff --git a/src/ssvc/api/main.py b/src/ssvc/api/main.py index 9b4f58fc..05a592e7 100644 --- a/src/ssvc/api/main.py +++ b/src/ssvc/api/main.py @@ -3,7 +3,6 @@ API for SSVC """ - # Copyright (c) 2025 Carnegie Mellon University. # NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE # ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. @@ -45,12 +44,12 @@ }, ) -app.include_router(router_v1) +app.include_router(router_v1, prefix="/ssvc/api/v1", tags=["SSVC API v1"]) # root should redirect to docs # at least until we have something better to show -@app.get("/", include_in_schema=False) +@app.get("/", include_in_schema=False, description="Redirect to API docs") async def redirect_root_to_docs(): return RedirectResponse(url="/docs") diff --git a/src/ssvc/api/v1/routers/examples.py b/src/ssvc/api/v1/routers/examples.py new file mode 100644 index 00000000..090ba715 --- /dev/null +++ b/src/ssvc/api/v1/routers/examples.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python +""" +SSVC API v1 Examples Router +""" + +# Copyright (c) 2025 Carnegie Mellon University. +# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE +# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. +# CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT +# NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR +# MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE +# OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE +# ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM +# PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. +# Licensed under a MIT (SEI)-style license, please see LICENSE or contact +# permission@sei.cmu.edu for full terms. +# [DISTRIBUTION STATEMENT A] This material has been approved for +# public release and unlimited distribution. Please see Copyright notice +# for non-US Government use and distribution. +# This Software includes and/or makes use of Third-Party Software each +# subject to its own license. +# DM24-0278 + +from fastapi import APIRouter + +from ssvc.decision_points.base import DecisionPoint, DecisionPointValue +from ssvc.decision_tables.base import DecisionTable +from ssvc.examples import ( + EXAMPLE_DECISION_POINT_1, + EXAMPLE_DECISION_TABLE, + EXAMPLE_MINIMAL_DECISION_POINT_VALUE, + EXAMPLE_SELECTION_1, + EXAMPLE_SELECTION_LIST, +) +from ssvc.selection import ( + MinimalDecisionPointValue, + Reference, + Selection, + SelectionList, +) + +router = APIRouter(prefix="/examples", tags=["Examples"]) + +# GET to retrieve a sample object +# POST to validate an object against the pydantic model + + +# Decision Point Values +@router.get( + "/decision-point-values", + response_model=DecisionPointValue, + response_model_exclude_none=True, + summary="Get a sample Decision Point Value", + description="Retrieve a sample Decision Point Value object.", +) +def get_example_decision_point_value() -> DecisionPointValue: + """ + Retrieve a sample Decision Point Value object. + """ + return EXAMPLE_DECISION_POINT_1.values[0] + + +@router.post( + "/decision-point-values", + response_model=DecisionPointValue, + response_model_exclude_none=True, + summary="Validate a Decision Point Value", + description="Validate a Decision Point Value object against the pydantic model.", +) +def validate_decision_point_value( + decision_point_value: DecisionPointValue, +) -> DecisionPointValue: + """ + Validate a Decision Point Value object against the pydantic model. + """ + return decision_point_value + + +# Decision Points +@router.get( + "/decision-points", + response_model=DecisionPoint, + response_model_exclude_none=True, + summary="Get a sample Decision Point", + description="Retrieve a sample Decision Point object.", +) +def get_example_decision_point() -> DecisionPoint: + """ + Retrieve a sample Decision Point object. + """ + return EXAMPLE_DECISION_POINT_1 + + +@router.post( + "/decision-points", + response_model=DecisionPoint, + response_model_exclude_none=True, + summary="Validate a Decision Point", + description="Validate a Decision Point object against the pydantic model.", +) +def validate_decision_point(decision_point: DecisionPoint) -> DecisionPoint: + """ + Validate a Decision Point object against the pydantic model. + """ + return decision_point + + +# Decision Tables +@router.get( + "/decision-tables", + response_model=DecisionTable, + response_model_exclude_none=True, + summary="Get a sample Decision Table", + description="Retrieve a sample Decision Table object.", +) +def get_example_decision_table() -> DecisionTable: + """ + Retrieve a sample Decision Table object. + """ + return EXAMPLE_DECISION_TABLE + + +@router.post( + "/decision-tables", + response_model=DecisionTable, + response_model_exclude_none=True, + summary="Validate a Decision Table", + description="Validate a Decision Table object against the pydantic model.", +) +def validate_decision_table(decision_table: DecisionTable) -> DecisionTable: + """ + Validate a Decision Table object against the pydantic model. + """ + return decision_table + + +# minimal decision point values +@router.get( + "/decision-point-values-minimal", + response_model=MinimalDecisionPointValue, + response_model_exclude_none=True, + summary="Get a minimal Decision Point Value", + description="Retrieve a minimal Decision Point Value object.", +) +def get_minimal_decision_point_value() -> MinimalDecisionPointValue: + """ + Retrieve a minimal Decision Point Value object. + """ + return EXAMPLE_MINIMAL_DECISION_POINT_VALUE + + +@router.post( + "/decision-point-values-minimal", + response_model=MinimalDecisionPointValue, + response_model_exclude_none=True, + summary="Validate a minimal Decision Point Value", + description="Validate a minimal Decision Point Value object against the pydantic model.", +) +def validate_minimal_decision_point_value( + minimal_decision_point_value: MinimalDecisionPointValue, +) -> MinimalDecisionPointValue: + """ + Validate a minimal Decision Point Value object against the pydantic model. + """ + return minimal_decision_point_value + + +# selection +@router.get( + "/selections", + response_model=Selection, + response_model_exclude_none=True, + summary="Get a sample Selection", + description="Retrieve a sample Selection object.", +) +def get_example_selection() -> Selection: + """ + Retrieve a sample Selection object. + """ + return EXAMPLE_SELECTION_1 + + +@router.post( + "/selections", + response_model=Selection, + response_model_exclude_none=True, + summary="Validate a Selection", + description="Validate a Selection object against the pydantic model.", +) +def validate_selection(selection: Selection) -> Selection: + """ + Validate a Selection object against the pydantic model. + """ + return selection + + +# Selection lists +@router.get( + "/selection-lists", + response_model=SelectionList, + response_model_exclude_none=True, + summary="Get a sample Selection List", + description="Retrieve a sample Selection List object.", +) +def get_example_selection_list() -> SelectionList: + """ + Retrieve a sample Selection List object. + """ + return EXAMPLE_SELECTION_LIST + + +@router.post( + "/selection-lists", + response_model=SelectionList, + response_model_exclude_none=True, + summary="Validate a Selection List", + description="Validate a Selection List object against the pydantic model.", +) +def validate_selection_list(selection_list: SelectionList) -> SelectionList: + """ + Validate a Selection List object against the pydantic model. + """ + return selection_list + + +# references +@router.get( + "/references", + response_model=Reference, + response_model_exclude_none=True, + summary="Get sample References", + description="Retrieve a list of sample Reference URIs.", +) +def get_example_references() -> Reference: + """ + Retrieve a list of sample Reference URIs. + """ + return EXAMPLE_SELECTION_LIST.references[0] + + +@router.post( + "/references", + response_model=Reference, + response_model_exclude_none=True, + summary="Validate a Reference", + description="Validate a Reference object against the pydantic model.", +) +def validate_reference(reference: Reference) -> Reference: + """ + Validate a Reference object against the pydantic model. + """ + return reference diff --git a/src/ssvc/api/v1/routers/v1_router.py b/src/ssvc/api/v1/routers/v1_router.py index 9031079f..52422c05 100644 --- a/src/ssvc/api/v1/routers/v1_router.py +++ b/src/ssvc/api/v1/routers/v1_router.py @@ -26,6 +26,7 @@ decision_point, decision_table, decision_tables, + examples, objects, ) from ssvc.api.v1.routers import ( @@ -36,7 +37,8 @@ versions, ) -router_v1 = APIRouter(prefix="/v1", tags=["v1"]) +router_v1 = APIRouter() +router_v1.include_router(examples.router) router_v1.include_router(decision_point.router) router_v1.include_router(decision_points.router) router_v1.include_router(decision_table.router) diff --git a/src/ssvc/examples.py b/src/ssvc/examples.py new file mode 100644 index 00000000..1f4939a6 --- /dev/null +++ b/src/ssvc/examples.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +""" +Example SSVC object instances +""" + +# Copyright (c) 2025 Carnegie Mellon University. +# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE +# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. +# CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT +# NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR +# MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE +# OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE +# ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM +# PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. +# Licensed under a MIT (SEI)-style license, please see LICENSE or contact +# permission@sei.cmu.edu for full terms. +# [DISTRIBUTION STATEMENT A] This material has been approved for +# public release and unlimited distribution. Please see Copyright notice +# for non-US Government use and distribution. +# This Software includes and/or makes use of Third-Party Software each +# subject to its own license. +# DM24-0278 + +import datetime + +from ssvc.decision_points.base import DecisionPoint, DecisionPointValue +from ssvc.decision_tables.base import DecisionTable +from ssvc.selection import ( + MinimalDecisionPointValue, + Reference, + Selection, + SelectionList, +) + +EXAMPLE_DECISION_POINT_1 = DecisionPoint( + namespace="example", + key="KEY1", + version="1.0.0", + name="Example Decision Point", + definition="This is a sample decision point for demonstration purposes. Values must be an ordered list.", + values=( + DecisionPointValue( + key="V1", + name="Value One", + definition="Value One definition.", + ), + DecisionPointValue( + key="V2", + name="Value Two", + definition="Value Two definition.", + ), + ), + registered=False, +) + +EXAMPLE_DECISION_POINT_2 = DecisionPoint( + namespace="example", + key="KEY2", + version="1.0.0", + name="Example Decision Point 2", + definition="This is another sample decision point for demonstration purposes. Values must be an ordered list.", + values=( + DecisionPointValue( + key="A", + name="Value A", + definition="Value A definition.", + ), + DecisionPointValue( + key="B", + name="Value B", + definition="Value B definition.", + ), + ), + registered=False, +) +EXAMPLE_OUTCOME_DECISION_POINT = DecisionPoint( + namespace="example", + key="OUTCOME", + version="1.0.0", + name="Example Outcome Decision Point", + definition="This is a sample outcome decision point for demonstration purposes. Values must be an ordered list.", + values=( + DecisionPointValue( + key="O1", + name="Outcome One", + definition="Outcome One definition.", + ), + DecisionPointValue( + key="O2", + name="Outcome Two", + definition="Outcome Two definition.", + ), + DecisionPointValue( + key="O3", + name="Outcome Three", + definition="Outcome Three definition.", + ), + ), + registered=False, +) + +EXAMPLE_DECISION_TABLE = DecisionTable( + namespace="example", + key="DT1", + version="1.0.0", + name="Example Decision Table", + definition="This is a sample decision table for demonstration purposes.", + decision_points={ + dp.id: dp + for dp in [ + EXAMPLE_DECISION_POINT_1, + EXAMPLE_DECISION_POINT_2, + EXAMPLE_OUTCOME_DECISION_POINT, + ] + }, + outcome=EXAMPLE_OUTCOME_DECISION_POINT.id, + registered=False, +) +EXAMPLE_SELECTION_1 = Selection.from_decision_point( + decision_point=EXAMPLE_DECISION_POINT_1 +) +EXAMPLE_SELECTION_1.values = [ + EXAMPLE_SELECTION_1.values[0], +] + + +EXAMPLE_SELECTION_2 = Selection.from_decision_point( + decision_point=EXAMPLE_DECISION_POINT_2 +) +EXAMPLE_SELECTION_LIST = SelectionList( + target_ids=["VU#9999999", "CVE-1900-0001"], + selections=[EXAMPLE_SELECTION_1, EXAMPLE_SELECTION_2], + timestamp=datetime.datetime.now(tz=datetime.timezone.utc), + references=[ + Reference(uri="https://example.com", summary="Example reference"), + ], +) + +EXAMPLE_MINIMAL_DECISION_POINT_VALUE = MinimalDecisionPointValue( + key="KEY_REQUIRED", +) diff --git a/src/ssvc/selection.py b/src/ssvc/selection.py index 752e495d..e2ac2e5f 100644 --- a/src/ssvc/selection.py +++ b/src/ssvc/selection.py @@ -30,6 +30,7 @@ ConfigDict, Field, field_validator, + model_serializer, model_validator, ) @@ -140,6 +141,30 @@ class Reference(BaseModel): uri: AnyUrl summary: str + @model_serializer(mode="wrap") + def remove_falsy_fields(self, handler): + data = handler(self) + return {k: v for k, v in data.items() if v} + + @model_validator(mode="before") + @classmethod + def set_default_summary(cls, data): + """ + Ensure that summary is set to an empty string if not provided. + + Args: + data: The input data dictionary. + + Returns: + The modified data dictionary with summary set to an empty string if it was missing. + + """ + if "summary" not in data: + data["summary"] = "" + elif not data["summary"]: + data["summary"] = "" + return data + # override schema generation to ensure that description is not required def model_json_schema(cls, **kwargs): schema = super().model_json_schema(**kwargs) @@ -238,6 +263,11 @@ class SelectionList(_SchemaVersioned, _Timestamped, BaseModel): ], ) + @model_serializer(mode="wrap") + def remove_falsy_fields(self, handler): + data = handler(self) + return {k: v for k, v in data.items() if v} + @model_validator(mode="before") def set_schema_version(cls, data): if "schemaVersion" not in data: diff --git a/src/test/api/routers/test_examples.py b/src/test/api/routers/test_examples.py new file mode 100644 index 00000000..0abec341 --- /dev/null +++ b/src/test/api/routers/test_examples.py @@ -0,0 +1,178 @@ +# Copyright (c) 2025 Carnegie Mellon University. +# NO WARRANTY. THIS CARNEGIE MELLON UNIVERSITY AND SOFTWARE +# ENGINEERING INSTITUTE MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. +# CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT +# NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR +# MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE +# OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE +# ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM +# PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT. +# Licensed under a MIT (SEI)-style license, please see LICENSE or contact +# permission@sei.cmu.edu for full terms. +# [DISTRIBUTION STATEMENT A] This material has been approved for +# public release and unlimited distribution. Please see Copyright notice +# for non-US Government use and distribution. +# This Software includes and/or makes use of Third-Party Software each +# subject to its own license. +# DM24-0278 +import datetime +import random +import unittest + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from ssvc.api.v1.routers import examples +from ssvc.decision_points.base import DecisionPoint, DecisionPointValue +from ssvc.decision_tables.base import DecisionTable +from ssvc.decision_tables.example.to_play import TOPLAY_1 +from ssvc.selection import ( + MinimalDecisionPointValue, + Reference, + Selection, + SelectionList, +) + + +class MyTestCase(unittest.TestCase): + def setUp(self): + self.app = FastAPI() + self.app.include_router(examples.router) + self.client = TestClient(self.app) + + # set up a decision table from which we can derive other objects in post-tests + self.dt = TOPLAY_1 + + def tearDown(self): + pass + + def _test_get_example(self, endpoint: str, model: type): + response = self.client.get(endpoint) + self.assertEqual(response.status_code, 200) + data = response.json() + + self.assertIsInstance(data, dict) + + try: + model.model_validate(data) + except Exception as e: + self.fail(f"Validation failed: {e}") + + def _test_post_example(self, endpoint: str, model: type, obj: object): + response = self.client.post(endpoint, json=obj.model_dump(mode="json")) + self.assertEqual( + 200, response.status_code, f"POST to {endpoint} failed: {response}" + ) + data = response.json() + + try: + model.model_validate(data) + except Exception as e: + self.fail(f"Validation failed: {e}") + + def test_get_decision_point_values(self): + self._test_get_example( + "/examples/decision-point-values", DecisionPointValue + ) + + def test_get_decision_points(self): + self._test_get_example("/examples/decision-points", DecisionPoint) + + def test_get_decision_tables(self): + self._test_get_example("/examples/decision-tables", DecisionTable) + + def test_get_minimal_decision_point_values(self): + self._test_get_example( + "/examples/decision-point-values-minimal", + MinimalDecisionPointValue, + ) + + def test_get_selections(self): + self._test_get_example("/examples/selections", Selection) + + def test_get_selection_lists(self): + self._test_get_example("/examples/selection-lists", SelectionList) + + def test_get_references(self): + self._test_get_example("/examples/references", Reference) + + def test_post_decision_points(self): + for dp in self.dt.decision_points.values(): + self._test_post_example( + "/examples/decision-points", DecisionPoint, dp + ) + # TODO test bad data + + def test_post_decision_point_values(self): + for dp in self.dt.decision_points.values(): + for dpv in dp.values: + self._test_post_example( + "/examples/decision-point-values", DecisionPointValue, dpv + ) + # TODO test bad data + + def test_post_decision_tables(self): + self._test_post_example( + "/examples/decision-tables", DecisionTable, self.dt + ) + # TODO test bad data + + def test_post_minimal_decision_point_values(self): + for dp in self.dt.decision_points.values(): + for dpv in dp.values: + mdpv = MinimalDecisionPointValue( + key=dpv.key, + ) + self._test_post_example( + "/examples/decision-point-values-minimal", + MinimalDecisionPointValue, + mdpv, + ) + # TODO test bad data + + def test_post_selections(self): + for dp in self.dt.decision_points.values(): + sel = Selection.from_decision_point(dp) + # randomly sample 1 or more values from the selection + sample_size = random.randint(1, len(sel.values)) + sel.values = random.sample(sel.values, sample_size) + self._test_post_example("/examples/selections", Selection, sel) + + # TODO test bad data + + def test_post_selection_lists(self): + sels = [] + for dp in self.dt.decision_points.values(): + sel = Selection.from_decision_point(dp) + # randomly sample 1 or more values from the selection + sample_size = random.randint(1, len(sel.values)) + sel.values = random.sample(sel.values, sample_size) + sels.append(sel) + + sel_list = SelectionList( + target_ids=["TK-421", "TK-710"], + selections=sels, + timestamp=datetime.datetime.now(), + references=[ + Reference( + uri="https://starwars.fandom.com/wiki/TK-421", + summary="Alongside TK-710, TK-421's first security assignment was to guard the Millennium Falcon in Docking Bay 327 after the Death Star captured it.", + ) + ], + ) + self._test_post_example( + "/examples/selection-lists", SelectionList, sel_list + ) + # TODO test bad data + + def test_post_references(self): + ref = Reference( + uri="http://some/reference", summary="An example reference" + ) + self._test_post_example("/examples/references", Reference, ref) + # TODO test bad data + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test/api/test_main.py b/src/test/api/test_main.py index be81a600..c5d0546b 100644 --- a/src/test/api/test_main.py +++ b/src/test/api/test_main.py @@ -42,6 +42,7 @@ def test_expected_routers(self): "namespaces", "keys", "versions", + "examples", ] routes = [r.path for r in app.routes] for expected in expected_routers: diff --git a/src/test/test_selections.py b/src/test/test_selections.py index bc6977d0..f5d2e5c0 100644 --- a/src/test/test_selections.py +++ b/src/test/test_selections.py @@ -17,9 +17,9 @@ # subject to its own license. # DM24-0278 +import json import unittest from datetime import datetime -from unittest import expectedFailure from ssvc import selection from ssvc.selection import MinimalDecisionPointValue, SelectionList @@ -219,7 +219,31 @@ def test_reference_model(self): self.assertIn(uri, str(ref.uri)) self.assertEqual(ref.summary, "Test description") - @expectedFailure + def test_model_dump_removes_empty_values(self): + """model_dump() should remove None or empty values.""" + result_clean = self.selections.model_dump(exclude_none=True) + result_bloat = self.selections.model_dump() + self.assertNotEqual(result_clean, result_bloat) + self.assertIn("selections", result_clean) + self.assertNotIn("metadata", result_clean) + + def test_model_dump_json_respects_indent(self): + """model_dump_json() should apply JSON indentation and pruning.""" + json_text = self.selections.model_dump_json(indent=4) + data = json.loads(json_text) + self.assertIn("selections", data) + self.assertNotIn("metadata", data) + self.assertIn("\n \"selections\":", json_text) + + def test_model_dump_json_excludes_none(self): + """exclude_none=True should work with post-processing.""" + json_text_clean = self.selections.model_dump_json(exclude_none=True) + json_text_bloat = self.selections.model_dump_json() + self.assertNotEqual(json_text_clean, json_text_bloat) + data = json.loads(json_text_clean) + self.assertIn("selections", data) + self.assertNotIn("metadata", data) + def test_reference_model_without_summary(self): """Test the Reference model.""" uris = [ @@ -248,6 +272,15 @@ def test_reference_model_without_summary(self): self.assertIn(uri, str(ref.uri)) + # while ref might have an empty string summary, + self.assertTrue(hasattr(ref, "summary")) + self.assertEqual("", ref.summary) + # the json export should not include it + json_data = ref.model_dump_json(exclude_none=True) + data = json.loads(json_data) + self.assertIn("uri", data) + self.assertNotIn("summary", data) + def test_selection_list_validators(self): """Test SelectionList validators.""" # Test schema version is set automatically @@ -339,6 +372,58 @@ def test_selection_list_optional_fields(self): self.assertEqual(len(sel_list.references), 1) self.assertEqual(sel_list.decision_point_resources[0].uri, ref.uri) + def test_missing_lists_are_empty_after_init(self): + # if decision_point_resources is not included, it should still validate. + sel_list_no_dpr = SelectionList( + selections=[self.s1, self.s2], + timestamp=datetime.now(), + ) + for attribute in [ + "decision_point_resources", + "references", + "target_ids", + ]: + self.assertTrue( + hasattr(sel_list_no_dpr, attribute), + f"Attribute {attribute} is missing", + ) + _value = getattr(sel_list_no_dpr, attribute) + self.assertIsInstance(_value, list) + self.assertEqual(0, len(_value)) + + # but they should not appear in the model dump to JSON + dumped = sel_list_no_dpr.model_dump() + self.assertNotIn(attribute, dumped) + + def test_validation_when_empty_lists_provided(self): + sel_list_no_dpr = SelectionList( + selections=[self.s1, self.s2], + timestamp=datetime.now(), + ) + json_data = sel_list_no_dpr.model_dump_json(exclude_none=True) + + data = json.loads(json_data) + + check_attrs = [ + "decision_point_resources", + "references", + "target_ids", + ] + + for attr in check_attrs: + self.assertNotIn(attr, data) + + new_obj = SelectionList.model_validate(data) + + for attr in check_attrs: + self.assertTrue( + hasattr(new_obj, attr), + f"Attribute {attr} is missing after re-validation", + ) + _value = getattr(new_obj, attr) + self.assertIsInstance(_value, list) + self.assertEqual(0, len(_value)) + def test_model_json_schema_customization(self): """Test that JSON schema is properly customized.""" schema = SelectionList.model_json_schema() @@ -381,6 +466,16 @@ def test_selection_list_minimum_selections(self): timestamp=datetime.now(), ) + def test_model_dump_removes_required_field(self): + """Test if a selections is dumped and breaks when items removed""" + s = SelectionList( + selections=[self.s1], + timestamp=datetime.now(), + ) + dumped = s.model_dump() + with self.assertRaises(Exception): + del dumped['values'] + if __name__ == "__main__": unittest.main() diff --git a/src/uv.lock b/uv.lock similarity index 98% rename from src/uv.lock rename to uv.lock index c142f9eb..039975c6 100644 --- a/src/uv.lock +++ b/uv.lock @@ -70,6 +70,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/eb/f4151e0c7377a6e08a38108609ba5cede57986802757848688aeedd1b9e8/beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a", size = 105113, upload-time = "2025-08-24T14:06:14.884Z" }, ] +[[package]] +name = "black" +version = "25.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012, upload-time = "2025-09-19T00:33:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421, upload-time = "2025-09-19T00:35:25.937Z" }, + { url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619, upload-time = "2025-09-19T00:30:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481, upload-time = "2025-09-19T00:31:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/48/99/3acfea65f5e79f45472c45f87ec13037b506522719cd9d4ac86484ff51ac/black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175", size = 1742165, upload-time = "2025-09-19T00:34:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/3a/18/799285282c8236a79f25d590f0222dbd6850e14b060dfaa3e720241fd772/black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f", size = 1581259, upload-time = "2025-09-19T00:32:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ce/883ec4b6303acdeca93ee06b7622f1fa383c6b3765294824165d49b1a86b/black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831", size = 1655583, upload-time = "2025-09-19T00:30:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/21/17/5c253aa80a0639ccc427a5c7144534b661505ae2b5a10b77ebe13fa25334/black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357", size = 1343428, upload-time = "2025-09-19T00:32:13.839Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" }, +] + [[package]] name = "bracex" version = "2.6" @@ -106,6 +131,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "black" }, { name = "linkchecker" }, { name = "pytest" }, ] @@ -135,6 +161,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "black", specifier = ">=25.9.0" }, { name = "linkchecker", specifier = ">=10.6.0" }, { name = "pytest", specifier = ">=8.4.1" }, ] @@ -757,6 +784,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/ac/b1fcc937f4ecd372f3e857162dea67c45c1e2eedbac80447be516e3372bb/mkdocstrings_python-1.17.0-py3-none-any.whl", hash = "sha256:49903fa355dfecc5ad0b891e78ff5d25d30ffd00846952801bbe8331e123d4b0", size = 124778, upload-time = "2025-08-14T21:18:12.821Z" }, ] +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "networkx" version = "3.4.2" @@ -1148,6 +1184,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] +[[package]] +name = "pytokens" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/5f/e959a442435e24f6fb5a01aec6c657079ceaca1b3baf18561c3728d681da/pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044", size = 12171, upload-time = "2025-02-19T14:51:22.001Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e5/63bed382f6a7a5ba70e7e132b8b7b8abbcf4888ffa6be4877698dcfbed7d/pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b", size = 12046, upload-time = "2025-02-19T14:51:18.694Z" }, +] + [[package]] name = "pytz" version = "2025.2"