Initial public upload from Projekte/CodingAgent (part 4)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/INSTALLER +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/METADATA +241 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/RECORD +119 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/REQUESTED +0 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/WHEEL +4 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/entry_points.txt +2 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/licenses/LICENSE +21 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/shared/tool_name_validation.py +129 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/shared/version.py +3 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/types.py +1999 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/__init__.py +38 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/decoders.py +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/exceptions.py +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/multipart.py +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/INSTALLER +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/METADATA +112 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/RECORD +29 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/REQUESTED +0 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/WHEEL +4 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE +3 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE.APACHE +177 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE.BSD +23 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/__init__.py +15 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_elffile.py +108 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_manylinux.py +262 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_musllinux.py +85 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_parser.py +393 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_structures.py +33 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_tokenizer.py +193 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/dependency_groups.py +302 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/direct_url.py +325 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/errors.py +94 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/licenses/__init__.py +186 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/licenses/_spdx.py +799 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/markers.py +492 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/metadata.py +964 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/py.typed +0 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/pylock.py +905 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/requirements.py +129 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/specifiers.py +1943 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/tags.py +932 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/utils.py +296 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/version.py +1231 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/INSTALLER +1 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/METADATA +152 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/RECORD +16 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/REQUESTED +0 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/WHEEL +5 -0
- hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE +21 -0
.gitattributes
CHANGED
|
@@ -38,3 +38,4 @@ hai-mcp-pr7/.venv/bin/python3 filter=lfs diff=lfs merge=lfs -text
|
|
| 38 |
hai-mcp-pr7/.venv/bin/python3.13 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
hai-mcp-pr7/.venv/lib/python3.13/site-packages/_cffi_backend.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 40 |
hai-mcp-pr7/.venv/lib/python3.13/site-packages/cryptography/hazmat/bindings/_rust.abi3.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 38 |
hai-mcp-pr7/.venv/bin/python3.13 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
hai-mcp-pr7/.venv/lib/python3.13/site-packages/_cffi_backend.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 40 |
hai-mcp-pr7/.venv/lib/python3.13/site-packages/cryptography/hazmat/bindings/_rust.abi3.so filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: mcp
|
| 3 |
+
Version: 1.28.1
|
| 4 |
+
Summary: Model Context Protocol SDK
|
| 5 |
+
Project-URL: Homepage, https://modelcontextprotocol.io
|
| 6 |
+
Project-URL: Documentation, https://py.sdk.modelcontextprotocol.io/
|
| 7 |
+
Project-URL: Repository, https://github.com/modelcontextprotocol/python-sdk
|
| 8 |
+
Project-URL: Issues, https://github.com/modelcontextprotocol/python-sdk/issues
|
| 9 |
+
Author: Anthropic, PBC.
|
| 10 |
+
Maintainer-email: David Soria Parra <davidsp@anthropic.com>, Justin Spahr-Summers <justin@anthropic.com>
|
| 11 |
+
License: MIT
|
| 12 |
+
License-File: LICENSE
|
| 13 |
+
Keywords: automation,git,llm,mcp
|
| 14 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 15 |
+
Classifier: Intended Audience :: Developers
|
| 16 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 17 |
+
Classifier: Programming Language :: Python :: 3
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 23 |
+
Requires-Python: >=3.10
|
| 24 |
+
Requires-Dist: anyio>=4.5
|
| 25 |
+
Requires-Dist: httpx-sse>=0.4
|
| 26 |
+
Requires-Dist: httpx<1.0.0,>=0.27.1
|
| 27 |
+
Requires-Dist: jsonschema>=4.20.0
|
| 28 |
+
Requires-Dist: pydantic-settings>=2.5.2
|
| 29 |
+
Requires-Dist: pydantic<3.0.0,>=2.11.0; python_version < '3.14'
|
| 30 |
+
Requires-Dist: pydantic<3.0.0,>=2.12.0; python_version >= '3.14'
|
| 31 |
+
Requires-Dist: pyjwt[crypto]>=2.10.1
|
| 32 |
+
Requires-Dist: python-multipart>=0.0.9
|
| 33 |
+
Requires-Dist: pywin32>=310; sys_platform == 'win32' and python_version < '3.14'
|
| 34 |
+
Requires-Dist: pywin32>=311; sys_platform == 'win32' and python_version >= '3.14'
|
| 35 |
+
Requires-Dist: sse-starlette>=1.6.1
|
| 36 |
+
Requires-Dist: starlette>=0.27; python_version < '3.14'
|
| 37 |
+
Requires-Dist: starlette>=0.48.0; python_version >= '3.14'
|
| 38 |
+
Requires-Dist: typing-extensions>=4.9.0
|
| 39 |
+
Requires-Dist: typing-inspection>=0.4.1
|
| 40 |
+
Requires-Dist: uvicorn>=0.31.1; sys_platform != 'emscripten'
|
| 41 |
+
Provides-Extra: cli
|
| 42 |
+
Requires-Dist: python-dotenv>=1.0.0; extra == 'cli'
|
| 43 |
+
Requires-Dist: typer>=0.16.0; extra == 'cli'
|
| 44 |
+
Provides-Extra: rich
|
| 45 |
+
Requires-Dist: rich>=13.9.4; extra == 'rich'
|
| 46 |
+
Provides-Extra: ws
|
| 47 |
+
Requires-Dist: websockets>=15.0.1; extra == 'ws'
|
| 48 |
+
Description-Content-Type: text/markdown
|
| 49 |
+
|
| 50 |
+
# MCP Python SDK
|
| 51 |
+
|
| 52 |
+
<div align="center">
|
| 53 |
+
|
| 54 |
+
<strong>Python implementation of the Model Context Protocol (MCP)</strong>
|
| 55 |
+
|
| 56 |
+
[![PyPI][pypi-badge]][pypi-url]
|
| 57 |
+
[![MIT licensed][mit-badge]][mit-url]
|
| 58 |
+
[![Python Version][python-badge]][python-url]
|
| 59 |
+
[![Documentation][docs-badge]][docs-url]
|
| 60 |
+
[![Protocol][protocol-badge]][protocol-url]
|
| 61 |
+
[![Specification][spec-badge]][spec-url]
|
| 62 |
+
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
> **This documents v1.x, the stable release line of the MCP Python SDK. v2 is in alpha.**
|
| 66 |
+
>
|
| 67 |
+
> v2 pre-releases are published to PyPI as `2.0.0aN`. Installers never select a pre-release unless you opt in (for example `pip install mcp==2.0.0a1`), so v1.x users are unaffected. **If your package depends on `mcp`, add a `<2` upper bound to your version constraint (for example `mcp>=1.27,<2`) before the stable v2 release lands.** See the [v2 documentation](https://github.com/modelcontextprotocol/python-sdk/blob/main/README.v2.md) and the [migration guide](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md) for what's changing.
|
| 68 |
+
>
|
| 69 |
+
> v1.x remains recommended for production use. It is in maintenance mode and continues to receive critical bug fixes and security patches.
|
| 70 |
+
|
| 71 |
+
<!-- omit in toc -->
|
| 72 |
+
## Table of Contents
|
| 73 |
+
|
| 74 |
+
- [MCP Python SDK](#mcp-python-sdk)
|
| 75 |
+
- [Overview](#overview)
|
| 76 |
+
- [Installation](#installation)
|
| 77 |
+
- [Quickstart](#quickstart)
|
| 78 |
+
- [What is MCP?](#what-is-mcp)
|
| 79 |
+
- [Documentation](#documentation)
|
| 80 |
+
- [Contributing](#contributing)
|
| 81 |
+
- [License](#license)
|
| 82 |
+
|
| 83 |
+
[pypi-badge]: https://img.shields.io/pypi/v/mcp.svg
|
| 84 |
+
[pypi-url]: https://pypi.org/project/mcp/
|
| 85 |
+
[mit-badge]: https://img.shields.io/pypi/l/mcp.svg
|
| 86 |
+
[mit-url]: https://github.com/modelcontextprotocol/python-sdk/blob/main/LICENSE
|
| 87 |
+
[python-badge]: https://img.shields.io/pypi/pyversions/mcp.svg
|
| 88 |
+
[python-url]: https://www.python.org/downloads/
|
| 89 |
+
[docs-badge]: https://img.shields.io/badge/docs-python--sdk-blue.svg
|
| 90 |
+
[docs-url]: https://modelcontextprotocol.github.io/python-sdk/
|
| 91 |
+
[protocol-badge]: https://img.shields.io/badge/protocol-modelcontextprotocol.io-blue.svg
|
| 92 |
+
[protocol-url]: https://modelcontextprotocol.io
|
| 93 |
+
[spec-badge]: https://img.shields.io/badge/spec-spec.modelcontextprotocol.io-blue.svg
|
| 94 |
+
[spec-url]: https://modelcontextprotocol.io/specification/latest
|
| 95 |
+
|
| 96 |
+
## Overview
|
| 97 |
+
|
| 98 |
+
The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Python SDK implements the full MCP specification, making it easy to:
|
| 99 |
+
|
| 100 |
+
- Build MCP clients that can connect to any MCP server
|
| 101 |
+
- Create MCP servers that expose resources, prompts and tools
|
| 102 |
+
- Use standard transports like stdio, SSE, and Streamable HTTP
|
| 103 |
+
- Handle all MCP protocol messages and lifecycle events
|
| 104 |
+
|
| 105 |
+
## Installation
|
| 106 |
+
|
| 107 |
+
### Adding MCP to your python project
|
| 108 |
+
|
| 109 |
+
We recommend using [uv](https://docs.astral.sh/uv/) to manage your Python projects.
|
| 110 |
+
|
| 111 |
+
If you haven't created a uv-managed project yet, create one:
|
| 112 |
+
|
| 113 |
+
```bash
|
| 114 |
+
uv init mcp-server-demo
|
| 115 |
+
cd mcp-server-demo
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Then add MCP to your project dependencies:
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
uv add "mcp[cli]"
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
Alternatively, for projects using pip for dependencies:
|
| 125 |
+
|
| 126 |
+
```bash
|
| 127 |
+
pip install "mcp[cli]"
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
### Running the standalone MCP development tools
|
| 131 |
+
|
| 132 |
+
To run the mcp command with uv:
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
uv run mcp
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
## Quickstart
|
| 139 |
+
|
| 140 |
+
Let's create a simple MCP server that exposes a calculator tool and some data:
|
| 141 |
+
|
| 142 |
+
<!-- snippet-source examples/snippets/servers/fastmcp_quickstart.py -->
|
| 143 |
+
```python
|
| 144 |
+
"""
|
| 145 |
+
FastMCP quickstart example.
|
| 146 |
+
|
| 147 |
+
Run from the repository root:
|
| 148 |
+
uv run examples/snippets/servers/fastmcp_quickstart.py
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
from mcp.server.fastmcp import FastMCP
|
| 152 |
+
|
| 153 |
+
# Create an MCP server
|
| 154 |
+
mcp = FastMCP("Demo", json_response=True)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
# Add an addition tool
|
| 158 |
+
@mcp.tool()
|
| 159 |
+
def add(a: int, b: int) -> int:
|
| 160 |
+
"""Add two numbers"""
|
| 161 |
+
return a + b
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# Add a dynamic greeting resource
|
| 165 |
+
@mcp.resource("greeting://{name}")
|
| 166 |
+
def get_greeting(name: str) -> str:
|
| 167 |
+
"""Get a personalized greeting"""
|
| 168 |
+
return f"Hello, {name}!"
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
# Add a prompt
|
| 172 |
+
@mcp.prompt()
|
| 173 |
+
def greet_user(name: str, style: str = "friendly") -> str:
|
| 174 |
+
"""Generate a greeting prompt"""
|
| 175 |
+
styles = {
|
| 176 |
+
"friendly": "Please write a warm, friendly greeting",
|
| 177 |
+
"formal": "Please write a formal, professional greeting",
|
| 178 |
+
"casual": "Please write a casual, relaxed greeting",
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
return f"{styles.get(style, styles['friendly'])} for someone named {name}."
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
# Run with streamable HTTP transport
|
| 185 |
+
if __name__ == "__main__":
|
| 186 |
+
mcp.run(transport="streamable-http")
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
_Full example: [examples/snippets/servers/fastmcp_quickstart.py](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/examples/snippets/servers/fastmcp_quickstart.py)_
|
| 190 |
+
<!-- /snippet-source -->
|
| 191 |
+
|
| 192 |
+
You can install this server in [Claude Code](https://docs.claude.com/en/docs/claude-code/mcp) and interact with it right away. First, run the server:
|
| 193 |
+
|
| 194 |
+
```bash
|
| 195 |
+
uv run --with mcp examples/snippets/servers/fastmcp_quickstart.py
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
Then add it to Claude Code:
|
| 199 |
+
|
| 200 |
+
```bash
|
| 201 |
+
claude mcp add --transport http my-server http://localhost:8000/mcp
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
Alternatively, you can test it with the MCP Inspector. Start the server as above, then in a separate terminal:
|
| 205 |
+
|
| 206 |
+
```bash
|
| 207 |
+
npx -y @modelcontextprotocol/inspector
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
In the inspector UI, connect to `http://localhost:8000/mcp`.
|
| 211 |
+
|
| 212 |
+
## What is MCP?
|
| 213 |
+
|
| 214 |
+
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) lets you build servers that expose data and functionality to LLM applications in a secure, standardized way. Think of it like a web API, but specifically designed for LLM interactions. MCP servers can:
|
| 215 |
+
|
| 216 |
+
- Expose data through **Resources** (think of these sort of like GET endpoints; they are used to load information into the LLM's context)
|
| 217 |
+
- Provide functionality through **Tools** (sort of like POST endpoints; they are used to execute code or otherwise produce a side effect)
|
| 218 |
+
- Define interaction patterns through **Prompts** (reusable templates for LLM interactions)
|
| 219 |
+
- And more!
|
| 220 |
+
|
| 221 |
+
## Documentation
|
| 222 |
+
|
| 223 |
+
- [Building Servers](docs/server.md) -- tools, resources, prompts, logging, completions, sampling, elicitation, transports, ASGI mounting
|
| 224 |
+
- [Writing Clients](docs/client.md) -- connecting to servers, using tools/resources/prompts, display utilities
|
| 225 |
+
- [Authorization](docs/authorization.md) -- OAuth 2.1, token verification, client authentication
|
| 226 |
+
- [Low-Level Server](docs/low-level-server.md) -- direct handler registration for advanced use cases
|
| 227 |
+
- [Protocol Features](docs/protocol.md) -- MCP primitives, server capabilities
|
| 228 |
+
- [Testing](docs/testing.md) -- in-memory transport testing with pytest
|
| 229 |
+
- [API Reference](https://modelcontextprotocol.github.io/python-sdk/api/)
|
| 230 |
+
- [Experimental Features (Tasks)](https://modelcontextprotocol.github.io/python-sdk/experimental/tasks/)
|
| 231 |
+
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
|
| 232 |
+
- [Model Context Protocol specification](https://modelcontextprotocol.io/specification/latest)
|
| 233 |
+
- [Officially supported servers](https://github.com/modelcontextprotocol/servers)
|
| 234 |
+
|
| 235 |
+
## Contributing
|
| 236 |
+
|
| 237 |
+
We are passionate about supporting contributors of all levels of experience and would love to see you get involved in the project. See the [contributing guide](CONTRIBUTING.md) to get started.
|
| 238 |
+
|
| 239 |
+
## License
|
| 240 |
+
|
| 241 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
../../../bin/mcp,sha256=nmQiXjC95fuk-R6W-lcT_XJKaUAGWy7TlLxW6lPfggA,334
|
| 2 |
+
mcp-1.28.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 3 |
+
mcp-1.28.1.dist-info/METADATA,sha256=1BigS3PrZSDYAf5jg7UBKDtm4h_C82v9qN7G5C46h3w,9448
|
| 4 |
+
mcp-1.28.1.dist-info/RECORD,,
|
| 5 |
+
mcp-1.28.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 6 |
+
mcp-1.28.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
| 7 |
+
mcp-1.28.1.dist-info/entry_points.txt,sha256=gnGBbo3yGF0YJNXfZpgkAg1-CQtKxnzxrlA53c8viYA,42
|
| 8 |
+
mcp-1.28.1.dist-info/licenses/LICENSE,sha256=XhPbvB0SD8KgPOzefJFCSuLX3hG2PVje0vRDHiYe5Q0,1071
|
| 9 |
+
mcp/__init__.py,sha256=9mrheVmhQg7bZ2JNzXnaIIYFQ-HVE7fVyG7AJQz4r3I,3224
|
| 10 |
+
mcp/cli/__init__.py,sha256=NJrJIUQmjc6FtSxvGNt1zeXnSLj8-Mli86laODTtQzE,107
|
| 11 |
+
mcp/cli/claude.py,sha256=MKUuZGrBmMSslD2weLIxU7RdQXZl2OvuMMq-_-EAlgY,5086
|
| 12 |
+
mcp/cli/cli.py,sha256=w6mWBmcy4y7nxLQDa4qSbxKV0BWeJqU_SFZf3qdOubs,15179
|
| 13 |
+
mcp/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 14 |
+
mcp/client/__main__.py,sha256=_aXYJ3xJiQGURxEEfvV-4MAqxRJwIFptFaLHyJgfvRU,2588
|
| 15 |
+
mcp/client/auth/__init__.py,sha256=hvlPclaDk3EkHr_nrt6tx37iv_rQ8SQgosM0OhJSeQ4,488
|
| 16 |
+
mcp/client/auth/exceptions.py,sha256=uUDHX_P0S3R_tt7oAS8ALQrUgOiV2Dgc0QQAdaaBcW8,264
|
| 17 |
+
mcp/client/auth/extensions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 18 |
+
mcp/client/auth/extensions/client_credentials.py,sha256=XvCarBQnB4DREuYEVygZbe0P9Wj7z8dSx_3SqajMKzQ,20897
|
| 19 |
+
mcp/client/auth/oauth2.py,sha256=X1w0eA4_3en9XVcicckZbse9J4KiWAX2aPRUqVIT0hQ,28394
|
| 20 |
+
mcp/client/auth/utils.py,sha256=Mm443kj9pHY09mUYflmr89W8jexZalvDS96b_31tUIE,12069
|
| 21 |
+
mcp/client/experimental/__init__.py,sha256=r923QPMrHQbLQYA_F48MLy2Y8ODdPWJhx8SuzhXcaEA,219
|
| 22 |
+
mcp/client/experimental/task_handlers.py,sha256=yDrCoiCha5iBBVpjqZ66uk3ZoDiQznqU29xKGY4Dh_Q,10791
|
| 23 |
+
mcp/client/experimental/tasks.py,sha256=LLEbfmIwCNk0PCpPSnhK6U0dcaqxENbdzLQXeGsqVjI,7055
|
| 24 |
+
mcp/client/session.py,sha256=kvgSrxeYP1o2tHA7BlIWzEklL8WdvQS3Uyhvp4WAsHo,24633
|
| 25 |
+
mcp/client/session_group.py,sha256=wXt4sZI-WRXdy5PPv-hNwRhMuzEhKMkQf3bFvaOITEc,17423
|
| 26 |
+
mcp/client/sse.py,sha256=mUIAMK1Q5MIJ2HfvRp-R30V6nUpi-I0fd9_UtLeRI9g,8224
|
| 27 |
+
mcp/client/stdio/__init__.py,sha256=ojInz-HjRW8Wq0ExFIajmc9Uf8xrZFOZv5vzptxs9lQ,9403
|
| 28 |
+
mcp/client/streamable_http.py,sha256=jrAcPU2onG5z1JSpYCnbzy_gXQv__nYHGx5Amv22L1s,28324
|
| 29 |
+
mcp/client/websocket.py,sha256=IxO2BPOQJrsVernQBkxcpzbPmwDSEC-Tb2imYOgzqxg,3911
|
| 30 |
+
mcp/os/__init__.py,sha256=uqaGIfCF4I0IPeSsEEZMNwZuDF91U0WKAIbDcKzPRLo,43
|
| 31 |
+
mcp/os/posix/__init__.py,sha256=l_2j8jhhgw69s_-eH4dCDk4QYxNlxKGIpaOCvcbv1Qk,40
|
| 32 |
+
mcp/os/posix/utilities.py,sha256=mTEYV74M9z9s2deXWctpUGTS1MNtkOr7IYCXU3mxlNI,1930
|
| 33 |
+
mcp/os/win32/__init__.py,sha256=7xyI7KWpZhDS3omGEBcu3200Q-DjIsS7c-a-vrn28Y4,42
|
| 34 |
+
mcp/os/win32/utilities.py,sha256=M5j9NF9QhgCHqQeHYpc9z0ksFsNy1F43vYqQl3LXq-Y,10808
|
| 35 |
+
mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 36 |
+
mcp/server/__init__.py,sha256=jeMjAkvChhKLEy1fpeKJ4d_bT1vnnmuGm11otQ7ncBg,202
|
| 37 |
+
mcp/server/__main__.py,sha256=KakUSquF7R44Ugwg3N5VQCJsPnsEVrbwk-UnRHerVww,1328
|
| 38 |
+
mcp/server/auth/__init__.py,sha256=xmxBzGAYvejOEzxqvZxiRkootIdlpp5WrCNiPKvuKdk,51
|
| 39 |
+
mcp/server/auth/errors.py,sha256=8YjlejJPl-qMjZrbJTDzMPBOAOITn0Da0pC5QWn9QdE,224
|
| 40 |
+
mcp/server/auth/handlers/__init__.py,sha256=T2ueQWVpLDbMIAiOcROyumxRwqa8Po5lA9zd_Cbt2Uc,58
|
| 41 |
+
mcp/server/auth/handlers/authorize.py,sha256=fAz-L-5Qv6KeHjpoBOAUE6sYgTuG0ydPA7y2xjb4pIM,9728
|
| 42 |
+
mcp/server/auth/handlers/metadata.py,sha256=mWPkrs1DfG-Qzq1nZU2HzwxSwk8K_vXe-ZpEDZ0k_QA,853
|
| 43 |
+
mcp/server/auth/handlers/register.py,sha256=CtH9c03oZtJgUoBVr-VgrAoJ6WXBw2RGNk5n70eBlPc,5928
|
| 44 |
+
mcp/server/auth/handlers/revoke.py,sha256=DIVjn09k7qTkAXrkTsRBVlR-XM9rsmTJzKlXyNdJqDE,3113
|
| 45 |
+
mcp/server/auth/handlers/token.py,sha256=yt5kUvB8zhBgONpYZJOQTFyoJZBhC0UKPUE3GQMkots,10223
|
| 46 |
+
mcp/server/auth/json_response.py,sha256=EMv3qAH62vRuw8XBHdFU3kYp0S8Jd6DjRGfwc7Jc9XU,377
|
| 47 |
+
mcp/server/auth/middleware/__init__.py,sha256=XhRUi_w-ie_QG7XT5EfqNG-NPZvXtJtgHSB_tPVYNIw,42
|
| 48 |
+
mcp/server/auth/middleware/auth_context.py,sha256=0hYDUr2GL0VqTVQtCCZCDop2JwGox_IuD0XBZQbtpT0,1696
|
| 49 |
+
mcp/server/auth/middleware/bearer_auth.py,sha256=kEURIfQ8DW9E1WM5iX7F8CL3feibmn0ljaG09tby-PQ,5276
|
| 50 |
+
mcp/server/auth/middleware/client_auth.py,sha256=lIIGufkj8rwkNVjh63tPH1UOY5h4Bc08zuvGmubUz40,4723
|
| 51 |
+
mcp/server/auth/provider.py,sha256=MsSXtkvI8-zerDM3ZAXDl7060vjPREY3aMtvK1RzoAc,10199
|
| 52 |
+
mcp/server/auth/routes.py,sha256=j4B5b2Pw2VJnCOq3bW2t3mRvgxJ3oI5wzQn0z3AlZ50,9131
|
| 53 |
+
mcp/server/auth/settings.py,sha256=Nj8tNCXANHMCmX6nxCa-GUrA9knJ9f8zCuMxMDFN4D4,1031
|
| 54 |
+
mcp/server/elicitation.py,sha256=euqjotipBszsirdZurr2fbKemRYwR7-zlZlijwMdSNY,6917
|
| 55 |
+
mcp/server/experimental/__init__.py,sha256=XT0dhG_cYzo9sStYMrmRPSOIZwBLd3BxdYQIgq11zzI,440
|
| 56 |
+
mcp/server/experimental/request_context.py,sha256=jqcv7aES0S3jgzXSMPwnGGRiers2O1Yt209aC-UPBxc,10387
|
| 57 |
+
mcp/server/experimental/session_features.py,sha256=NyIH2S2Y6cNrW_X4j_2DMV4TPAEwTDn3PLH6eWPyHeQ,8301
|
| 58 |
+
mcp/server/experimental/task_context.py,sha256=pFo4Tip6lqg9-qiKRJV_TcHMxS0O8msjQLtnQOMhh7k,23159
|
| 59 |
+
mcp/server/experimental/task_result_handler.py,sha256=5B2BkualH0AGMDVp9_GKuZzHHwAkr5lKDcuO3xudGck,8790
|
| 60 |
+
mcp/server/experimental/task_scope.py,sha256=yjNe3J7vaRpbIAyvlSqwsPnoRqGS4SnoAm3tSE60ICE,2894
|
| 61 |
+
mcp/server/experimental/task_support.py,sha256=y7l9in0JFLm7aL0fuvF5nuCFHeM4-2HfU-DEoaARY3o,4538
|
| 62 |
+
mcp/server/fastmcp/__init__.py,sha256=4EnleAYdPF_hgPexfq2zxGRtU_Fd3ZrjXegKckuj-K8,297
|
| 63 |
+
mcp/server/fastmcp/exceptions.py,sha256=q9djUDmpwmGEWcHI8q4UzJBtf7s7UtgL--OB7OaGzyQ,435
|
| 64 |
+
mcp/server/fastmcp/prompts/__init__.py,sha256=4BsMxoYolpoxg74xkkkzCFL8vvdkLVJ5cIPNs1ND1Jo,99
|
| 65 |
+
mcp/server/fastmcp/prompts/base.py,sha256=s5-zpCjfd-LLbqMoa2oJnzUoVUGW4EVhi8ufvWSjHns,7088
|
| 66 |
+
mcp/server/fastmcp/prompts/manager.py,sha256=IXIv__muOQhXgipCmKWJ4cyPjjawWkjmeahe6G29oyU,1835
|
| 67 |
+
mcp/server/fastmcp/resources/__init__.py,sha256=e4S369jBoJt07ez9_ZaJefzGfz4kr9nGwG4KPMzMHc8,464
|
| 68 |
+
mcp/server/fastmcp/resources/base.py,sha256=aw7d46dfMGCz9fYNtShxCJ9PlnNGmoev4JfPpstC8xI,1852
|
| 69 |
+
mcp/server/fastmcp/resources/resource_manager.py,sha256=f_yS7f4-hs-RryiLHlxXmf68bHd4cZnIPkmXbwzhaVg,4007
|
| 70 |
+
mcp/server/fastmcp/resources/templates.py,sha256=dLcTfoEV6nu8CkDq7WsNI1qmwGFan4mMewvYUroLPPA,4947
|
| 71 |
+
mcp/server/fastmcp/resources/types.py,sha256=g6bfXFNGGfoOGiq37TMvG87kxXrgjSgV6UzLS4M6Lz4,7214
|
| 72 |
+
mcp/server/fastmcp/server.py,sha256=b-7RpcESrPHPPzXoh0Y7VJIMaVSEvP7qKfhrb0wuASc,52923
|
| 73 |
+
mcp/server/fastmcp/tools/__init__.py,sha256=ZboxhyMJDl87Svjov8YwNYwNZi55P9VhmpTjBZLesnk,96
|
| 74 |
+
mcp/server/fastmcp/tools/base.py,sha256=h2JrpiTTMhj_t1QhQNyIhFPjMTD97-rciQnUe8YAylQ,4740
|
| 75 |
+
mcp/server/fastmcp/tools/tool_manager.py,sha256=rNjJ6_ug6HZEJuYIaN7qqTGgEqiCTha6T0eeSZG9KnY,2972
|
| 76 |
+
mcp/server/fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
| 77 |
+
mcp/server/fastmcp/utilities/context_injection.py,sha256=5Gh_mn4xaEDQINCJ-jmHygBKnt9F-ESJ2hhw66LcGk8,2048
|
| 78 |
+
mcp/server/fastmcp/utilities/func_metadata.py,sha256=cvcvHckZW5EoC4TCro20ZujCicowaeGN-o3-sN9Z3rI,22370
|
| 79 |
+
mcp/server/fastmcp/utilities/logging.py,sha256=e8kBHAYHB00gntMTaWMN6uT4kwiiOdL14qDXEZU0x-k,1063
|
| 80 |
+
mcp/server/fastmcp/utilities/types.py,sha256=xXRshurGRIjVnr1lVPwcZomSSN4_wvxpNb2jS6RUCw4,3546
|
| 81 |
+
mcp/server/lowlevel/__init__.py,sha256=mS7slrVoWzHMYqDHDIJ7oRZxwPZG_I63UVQiJN311YY,93
|
| 82 |
+
mcp/server/lowlevel/experimental.py,sha256=KyTRGodCcZw-gn-sxnPFsap9jsim5flB576JLXpbPqA,13312
|
| 83 |
+
mcp/server/lowlevel/func_inspection.py,sha256=uLEpY5IHgWicIfbYbaI4aDYfUgMRxuFdTu20duvH2pw,2466
|
| 84 |
+
mcp/server/lowlevel/helper_types.py,sha256=LVC0N6JMPynVs4fMJSfBmqKIigHNLsRNhBvbU06rg6A,251
|
| 85 |
+
mcp/server/lowlevel/server.py,sha256=Ho3zPcHICEiRyrqpSmyf-0bqBUEmQz4PhZbSkLeczCQ,35263
|
| 86 |
+
mcp/server/models.py,sha256=w-xEPy_qGds2_bEpejN6iaWeh0UvMtKqwlXzPN_ChZ0,422
|
| 87 |
+
mcp/server/session.py,sha256=SswRc5RgVTorliZg8FrmOJ6gALvf26WUXOT7Hh7KRZg,28148
|
| 88 |
+
mcp/server/sse.py,sha256=6V0DtA3baJaNXMGwo4bDeBl_VEEHCRI_SFVk8F5PvXc,12326
|
| 89 |
+
mcp/server/stdio.py,sha256=FGlkS_ya_DojCTP-L7rYhG7F8twT9jkoJrywYOjJQUE,3356
|
| 90 |
+
mcp/server/streamable_http.py,sha256=sYT4WpnJvTqsyCMvbM0swky_05ZOyyOrLJVl2ao4siU,47909
|
| 91 |
+
mcp/server/streamable_http_manager.py,sha256=X8JlICN5YHFk13EbQ9zuUc8Ie5jGCk7VVVBFokPzRFg,15595
|
| 92 |
+
mcp/server/transport_security.py,sha256=MljT0UTKuLRyu881Um6BuF6gj_Z93vslYWGA_hITVfo,4952
|
| 93 |
+
mcp/server/validation.py,sha256=owYWMCEg5KLesnXmNbyaTifJyJKJYp79FszXgLPsG7c,3633
|
| 94 |
+
mcp/server/websocket.py,sha256=asNejq9ykZususyWB1q3RVX6p4iesahdL8o3bSuhtZM,3531
|
| 95 |
+
mcp/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 96 |
+
mcp/shared/_httpx_utils.py,sha256=bfvypq5zsbvL4dgtNfX-lp8R_FqmzDtWEK61K7Y-Sos,2906
|
| 97 |
+
mcp/shared/auth.py,sha256=SRxs5E58Jkso6pZlTy-sDFXxDCi2pzYB_8gGbzz25mQ,7045
|
| 98 |
+
mcp/shared/auth_utils.py,sha256=aX1l1S3pDlE_9r0Fv25I76huJp1wqB25iUROb25xuuc,2881
|
| 99 |
+
mcp/shared/context.py,sha256=QMRHwXCIJsvf4U3vcgqSAXaaUEMKhfj_9XlCI-kUknE,1188
|
| 100 |
+
mcp/shared/exceptions.py,sha256=glHYWFVmdInhHRnlwrpr5w_iALLM0OlNZAM3djNBDXg,2433
|
| 101 |
+
mcp/shared/experimental/__init__.py,sha256=BhhD176Lv_NmLZ3nK6n3zVyCbCxEj5VMwPuqjm3fqcs,209
|
| 102 |
+
mcp/shared/experimental/tasks/__init__.py,sha256=jYCm-mbvvEblzcSWEvQMTlbRD3iUJss-PGF4sMI9OMc,433
|
| 103 |
+
mcp/shared/experimental/tasks/capabilities.py,sha256=wkO_u4698o5yYtV8RPCRj0oaKWfQLMk7l05-ICdjGmo,3571
|
| 104 |
+
mcp/shared/experimental/tasks/context.py,sha256=an3XBnocKUwdlKX8hR71wowzDixBKwsUe9uCBXrsq1w,2875
|
| 105 |
+
mcp/shared/experimental/tasks/helpers.py,sha256=OF7obHXpVoQmrqft-ZcEXOA9bY8xHvnMfd2YGz3xeLo,5517
|
| 106 |
+
mcp/shared/experimental/tasks/in_memory_task_store.py,sha256=cmCPo59XOQPe-kF5uG1Yd-6nHZDaOhrEZJZc35v3I8U,7558
|
| 107 |
+
mcp/shared/experimental/tasks/message_queue.py,sha256=vf5Dmwb-XkpytbK6TixouFWj8ci0wxeN2STpj7IUoiw,7436
|
| 108 |
+
mcp/shared/experimental/tasks/polling.py,sha256=6BCbo7hYGG19bG_RJUYvnYLJ65WHWbWJ9gwri7Aiw80,1369
|
| 109 |
+
mcp/shared/experimental/tasks/resolver.py,sha256=5VvPexlwcL1TZq2j_7fAdMziYlbHDNHk1UW4l1hVH1M,1801
|
| 110 |
+
mcp/shared/experimental/tasks/store.py,sha256=552Kbp7UqLoIruPZnrIkZFu5m0iiQz0quNuHOKI-Yts,3808
|
| 111 |
+
mcp/shared/memory.py,sha256=DtPFbfalB14Nm5hZS5zZLAwrIkqwZFVcBUwFcBAU1TM,3951
|
| 112 |
+
mcp/shared/message.py,sha256=K7wy_fCaPo7p0r8_2Pd223Ab0Q9DMABuCitK3xq0cQI,1550
|
| 113 |
+
mcp/shared/metadata_utils.py,sha256=9Tp9b0PkfjBhm2SlwHnod1W4Bf6vZB0p5J2E7Sv25U4,1712
|
| 114 |
+
mcp/shared/progress.py,sha256=Ys4V_DTQhYsug6KS6Bb3E4T0CDyOOaIecIXAIMFs1tU,1756
|
| 115 |
+
mcp/shared/response_router.py,sha256=en9M764wGHNkWl6dn9Ki7amIRCclHaKsfbnTfpL9KR0,2043
|
| 116 |
+
mcp/shared/session.py,sha256=RzwVHtUexhdGbmO1JcQqDzHbmXwoEr54R33v9A4-m6A,24224
|
| 117 |
+
mcp/shared/tool_name_validation.py,sha256=mTEvgzsMskayyilLaIkP7lIaA4je8cnkj9xYIc6htgo,4531
|
| 118 |
+
mcp/shared/version.py,sha256=GKwM5WHAwd2Dmrq7TyrAHEGWhTO_9UKJJUmC791JcMk,156
|
| 119 |
+
mcp/types.py,sha256=aCQQnNHquvQ8XauCmoMBWxuI7JrhSXZeaBSvryP5veU,64908
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/REQUESTED
ADDED
|
File without changes
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: hatchling 1.30.1
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
mcp = mcp.cli:app [cli]
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 Anthropic, PBC
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/shared/tool_name_validation.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tool name validation utilities according to SEP-986.
|
| 2 |
+
|
| 3 |
+
Tool names SHOULD be between 1 and 128 characters in length (inclusive).
|
| 4 |
+
Tool names are case-sensitive.
|
| 5 |
+
Allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z),
|
| 6 |
+
digits (0-9), underscore (_), dash (-), and dot (.).
|
| 7 |
+
Tool names SHOULD NOT contain spaces, commas, or other special characters.
|
| 8 |
+
|
| 9 |
+
See: https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import logging
|
| 15 |
+
import re
|
| 16 |
+
from dataclasses import dataclass, field
|
| 17 |
+
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
# Regular expression for valid tool names according to SEP-986 specification
|
| 21 |
+
TOOL_NAME_REGEX = re.compile(r"^[A-Za-z0-9._-]{1,128}$")
|
| 22 |
+
|
| 23 |
+
# SEP reference URL for warning messages
|
| 24 |
+
SEP_986_URL = "https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class ToolNameValidationResult:
|
| 29 |
+
"""Result of tool name validation.
|
| 30 |
+
|
| 31 |
+
Attributes:
|
| 32 |
+
is_valid: Whether the tool name conforms to SEP-986 requirements.
|
| 33 |
+
warnings: List of warning messages for non-conforming aspects.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
is_valid: bool
|
| 37 |
+
warnings: list[str] = field(default_factory=lambda: [])
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def validate_tool_name(name: str) -> ToolNameValidationResult:
|
| 41 |
+
"""Validate a tool name according to the SEP-986 specification.
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
name: The tool name to validate.
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
ToolNameValidationResult containing validation status and any warnings.
|
| 48 |
+
"""
|
| 49 |
+
warnings: list[str] = []
|
| 50 |
+
|
| 51 |
+
# Check for empty name
|
| 52 |
+
if not name:
|
| 53 |
+
return ToolNameValidationResult(
|
| 54 |
+
is_valid=False,
|
| 55 |
+
warnings=["Tool name cannot be empty"],
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
# Check length
|
| 59 |
+
if len(name) > 128:
|
| 60 |
+
return ToolNameValidationResult(
|
| 61 |
+
is_valid=False,
|
| 62 |
+
warnings=[f"Tool name exceeds maximum length of 128 characters (current: {len(name)})"],
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
# Check for problematic patterns (warnings, not validation failures)
|
| 66 |
+
if " " in name:
|
| 67 |
+
warnings.append("Tool name contains spaces, which may cause parsing issues")
|
| 68 |
+
|
| 69 |
+
if "," in name:
|
| 70 |
+
warnings.append("Tool name contains commas, which may cause parsing issues")
|
| 71 |
+
|
| 72 |
+
# Check for potentially confusing leading/trailing characters
|
| 73 |
+
if name.startswith("-") or name.endswith("-"):
|
| 74 |
+
warnings.append("Tool name starts or ends with a dash, which may cause parsing issues in some contexts")
|
| 75 |
+
|
| 76 |
+
if name.startswith(".") or name.endswith("."):
|
| 77 |
+
warnings.append("Tool name starts or ends with a dot, which may cause parsing issues in some contexts")
|
| 78 |
+
|
| 79 |
+
# Check for invalid characters
|
| 80 |
+
if not TOOL_NAME_REGEX.match(name):
|
| 81 |
+
# Find all invalid characters (unique, preserving order)
|
| 82 |
+
invalid_chars: list[str] = []
|
| 83 |
+
seen: set[str] = set()
|
| 84 |
+
for char in name:
|
| 85 |
+
if not re.match(r"[A-Za-z0-9._-]", char) and char not in seen:
|
| 86 |
+
invalid_chars.append(char)
|
| 87 |
+
seen.add(char)
|
| 88 |
+
|
| 89 |
+
warnings.append(f"Tool name contains invalid characters: {', '.join(repr(c) for c in invalid_chars)}")
|
| 90 |
+
warnings.append("Allowed characters are: A-Z, a-z, 0-9, underscore (_), dash (-), and dot (.)")
|
| 91 |
+
|
| 92 |
+
return ToolNameValidationResult(is_valid=False, warnings=warnings)
|
| 93 |
+
|
| 94 |
+
return ToolNameValidationResult(is_valid=True, warnings=warnings)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def issue_tool_name_warning(name: str, warnings: list[str]) -> None:
|
| 98 |
+
"""Log warnings for non-conforming tool names.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
name: The tool name that triggered the warnings.
|
| 102 |
+
warnings: List of warning messages to log.
|
| 103 |
+
"""
|
| 104 |
+
if not warnings:
|
| 105 |
+
return
|
| 106 |
+
|
| 107 |
+
logger.warning(f'Tool name validation warning for "{name}":')
|
| 108 |
+
for warning in warnings:
|
| 109 |
+
logger.warning(f" - {warning}")
|
| 110 |
+
logger.warning("Tool registration will proceed, but this may cause compatibility issues.")
|
| 111 |
+
logger.warning("Consider updating the tool name to conform to the MCP tool naming standard.")
|
| 112 |
+
logger.warning(f"See SEP-986 ({SEP_986_URL}) for more details.")
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def validate_and_warn_tool_name(name: str) -> bool:
|
| 116 |
+
"""Validate a tool name and issue warnings for non-conforming names.
|
| 117 |
+
|
| 118 |
+
This is the primary entry point for tool name validation. It validates
|
| 119 |
+
the name and logs any warnings via the logging module.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
name: The tool name to validate.
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
True if the name is valid, False otherwise.
|
| 126 |
+
"""
|
| 127 |
+
result = validate_tool_name(name)
|
| 128 |
+
issue_tool_name_warning(name, result.warnings)
|
| 129 |
+
return result.is_valid
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/shared/version.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from mcp.types import LATEST_PROTOCOL_VERSION
|
| 2 |
+
|
| 3 |
+
SUPPORTED_PROTOCOL_VERSIONS: list[str] = ["2024-11-05", "2025-03-26", "2025-06-18", LATEST_PROTOCOL_VERSION]
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/types.py
ADDED
|
@@ -0,0 +1,1999 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Callable
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
|
| 4 |
+
|
| 5 |
+
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
|
| 6 |
+
from pydantic.networks import AnyUrl, UrlConstraints
|
| 7 |
+
from typing_extensions import deprecated
|
| 8 |
+
|
| 9 |
+
"""
|
| 10 |
+
Model Context Protocol bindings for Python
|
| 11 |
+
|
| 12 |
+
These bindings were generated from https://github.com/modelcontextprotocol/specification,
|
| 13 |
+
using Claude, with a prompt something like the following:
|
| 14 |
+
|
| 15 |
+
Generate idiomatic Python bindings for this schema for MCP, or the "Model Context
|
| 16 |
+
Protocol." The schema is defined in TypeScript, but there's also a JSON Schema version
|
| 17 |
+
for reference.
|
| 18 |
+
|
| 19 |
+
* For the bindings, let's use Pydantic V2 models.
|
| 20 |
+
* Each model should allow extra fields everywhere, by specifying `model_config =
|
| 21 |
+
ConfigDict(extra='allow')`. Do this in every case, instead of a custom base class.
|
| 22 |
+
* Union types should be represented with a Pydantic `RootModel`.
|
| 23 |
+
* Define additional model classes instead of using dictionaries. Do this even if they're
|
| 24 |
+
not separate types in the schema.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
LATEST_PROTOCOL_VERSION = "2025-11-25"
|
| 28 |
+
|
| 29 |
+
"""
|
| 30 |
+
The default negotiated version of the Model Context Protocol when no version is specified.
|
| 31 |
+
We need this to satisfy the MCP specification, which requires the server to assume a
|
| 32 |
+
specific version if none is provided by the client. See section "Protocol Version Header" at
|
| 33 |
+
https://modelcontextprotocol.io/specification
|
| 34 |
+
"""
|
| 35 |
+
DEFAULT_NEGOTIATED_VERSION = "2025-03-26"
|
| 36 |
+
|
| 37 |
+
ProgressToken = str | int
|
| 38 |
+
Cursor = str
|
| 39 |
+
Role = Literal["user", "assistant"]
|
| 40 |
+
RequestId = Annotated[int, Field(strict=True)] | str
|
| 41 |
+
AnyFunction: TypeAlias = Callable[..., Any]
|
| 42 |
+
|
| 43 |
+
TaskExecutionMode = Literal["forbidden", "optional", "required"]
|
| 44 |
+
TASK_FORBIDDEN: Final[Literal["forbidden"]] = "forbidden"
|
| 45 |
+
TASK_OPTIONAL: Final[Literal["optional"]] = "optional"
|
| 46 |
+
TASK_REQUIRED: Final[Literal["required"]] = "required"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class TaskMetadata(BaseModel):
|
| 50 |
+
"""
|
| 51 |
+
Metadata for augmenting a request with task execution.
|
| 52 |
+
Include this in the `task` field of the request parameters.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
model_config = ConfigDict(extra="allow")
|
| 56 |
+
|
| 57 |
+
ttl: Annotated[int, Field(strict=True)] | None = None
|
| 58 |
+
"""Requested duration in milliseconds to retain task from creation."""
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class RequestParams(BaseModel):
|
| 62 |
+
class Meta(BaseModel):
|
| 63 |
+
progressToken: ProgressToken | None = None
|
| 64 |
+
"""
|
| 65 |
+
If specified, the caller requests out-of-band progress notifications for
|
| 66 |
+
this request (as represented by notifications/progress). The value of this
|
| 67 |
+
parameter is an opaque token that will be attached to any subsequent
|
| 68 |
+
notifications. The receiver is not obligated to provide these notifications.
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
model_config = ConfigDict(extra="allow")
|
| 72 |
+
|
| 73 |
+
task: TaskMetadata | None = None
|
| 74 |
+
"""
|
| 75 |
+
If specified, the caller is requesting task-augmented execution for this request.
|
| 76 |
+
The request will return a CreateTaskResult immediately, and the actual result can be
|
| 77 |
+
retrieved later via tasks/result.
|
| 78 |
+
|
| 79 |
+
Task augmentation is subject to capability negotiation - receivers MUST declare support
|
| 80 |
+
for task augmentation of specific request types in their capabilities.
|
| 81 |
+
"""
|
| 82 |
+
|
| 83 |
+
meta: Meta | None = Field(alias="_meta", default=None)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class PaginatedRequestParams(RequestParams):
|
| 87 |
+
cursor: Cursor | None = None
|
| 88 |
+
"""
|
| 89 |
+
An opaque token representing the current pagination position.
|
| 90 |
+
If provided, the server should return results starting after this cursor.
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class NotificationParams(BaseModel):
|
| 95 |
+
class Meta(BaseModel):
|
| 96 |
+
model_config = ConfigDict(extra="allow")
|
| 97 |
+
|
| 98 |
+
meta: Meta | None = Field(alias="_meta", default=None)
|
| 99 |
+
"""
|
| 100 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 101 |
+
for notes on _meta usage.
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
RequestParamsT = TypeVar("RequestParamsT", bound=RequestParams | dict[str, Any] | None)
|
| 106 |
+
NotificationParamsT = TypeVar("NotificationParamsT", bound=NotificationParams | dict[str, Any] | None)
|
| 107 |
+
MethodT = TypeVar("MethodT", bound=str)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class Request(BaseModel, Generic[RequestParamsT, MethodT]):
|
| 111 |
+
"""Base class for JSON-RPC requests."""
|
| 112 |
+
|
| 113 |
+
method: MethodT
|
| 114 |
+
params: RequestParamsT
|
| 115 |
+
model_config = ConfigDict(extra="allow")
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[MethodT]):
|
| 119 |
+
"""Base class for paginated requests,
|
| 120 |
+
matching the schema's PaginatedRequest interface."""
|
| 121 |
+
|
| 122 |
+
params: PaginatedRequestParams | None = None
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class Notification(BaseModel, Generic[NotificationParamsT, MethodT]):
|
| 126 |
+
"""Base class for JSON-RPC notifications."""
|
| 127 |
+
|
| 128 |
+
method: MethodT
|
| 129 |
+
params: NotificationParamsT
|
| 130 |
+
model_config = ConfigDict(extra="allow")
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class Result(BaseModel):
|
| 134 |
+
"""Base class for JSON-RPC results."""
|
| 135 |
+
|
| 136 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 137 |
+
"""
|
| 138 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 139 |
+
for notes on _meta usage.
|
| 140 |
+
"""
|
| 141 |
+
model_config = ConfigDict(extra="allow")
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class PaginatedResult(Result):
|
| 145 |
+
nextCursor: Cursor | None = None
|
| 146 |
+
"""
|
| 147 |
+
An opaque token representing the pagination position after the last returned result.
|
| 148 |
+
If present, there may be more results available.
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class JSONRPCRequest(Request[dict[str, Any] | None, str]):
|
| 153 |
+
"""A request that expects a response."""
|
| 154 |
+
|
| 155 |
+
jsonrpc: Literal["2.0"]
|
| 156 |
+
id: RequestId
|
| 157 |
+
method: str
|
| 158 |
+
params: dict[str, Any] | None = None
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class JSONRPCNotification(Notification[dict[str, Any] | None, str]):
|
| 162 |
+
"""A notification which does not expect a response."""
|
| 163 |
+
|
| 164 |
+
jsonrpc: Literal["2.0"]
|
| 165 |
+
params: dict[str, Any] | None = None
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
class JSONRPCResponse(BaseModel):
|
| 169 |
+
"""A successful (non-error) response to a request."""
|
| 170 |
+
|
| 171 |
+
jsonrpc: Literal["2.0"]
|
| 172 |
+
id: RequestId
|
| 173 |
+
result: dict[str, Any]
|
| 174 |
+
model_config = ConfigDict(extra="allow")
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
# MCP-specific error codes in the range [-32000, -32099]
|
| 178 |
+
URL_ELICITATION_REQUIRED = -32042
|
| 179 |
+
"""Error code indicating that a URL mode elicitation is required before the request can be processed."""
|
| 180 |
+
|
| 181 |
+
# SDK error codes
|
| 182 |
+
CONNECTION_CLOSED = -32000
|
| 183 |
+
# REQUEST_TIMEOUT = -32001 # the typescript sdk uses this
|
| 184 |
+
|
| 185 |
+
# Standard JSON-RPC error codes
|
| 186 |
+
PARSE_ERROR = -32700
|
| 187 |
+
INVALID_REQUEST = -32600
|
| 188 |
+
METHOD_NOT_FOUND = -32601
|
| 189 |
+
INVALID_PARAMS = -32602
|
| 190 |
+
INTERNAL_ERROR = -32603
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class ErrorData(BaseModel):
|
| 194 |
+
"""Error information for JSON-RPC error responses."""
|
| 195 |
+
|
| 196 |
+
code: int
|
| 197 |
+
"""The error type that occurred."""
|
| 198 |
+
|
| 199 |
+
message: str
|
| 200 |
+
"""
|
| 201 |
+
A short description of the error. The message SHOULD be limited to a concise single
|
| 202 |
+
sentence.
|
| 203 |
+
"""
|
| 204 |
+
|
| 205 |
+
data: Any | None = None
|
| 206 |
+
"""
|
| 207 |
+
Additional information about the error. The value of this member is defined by the
|
| 208 |
+
sender (e.g. detailed error information, nested errors etc.).
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
model_config = ConfigDict(extra="allow")
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
class JSONRPCError(BaseModel):
|
| 215 |
+
"""A response to a request that indicates an error occurred."""
|
| 216 |
+
|
| 217 |
+
jsonrpc: Literal["2.0"]
|
| 218 |
+
id: str | int
|
| 219 |
+
error: ErrorData
|
| 220 |
+
model_config = ConfigDict(extra="allow")
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
class JSONRPCMessage(RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError]):
|
| 224 |
+
pass
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
class EmptyResult(Result):
|
| 228 |
+
"""A response that indicates success but carries no data."""
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
class BaseMetadata(BaseModel):
|
| 232 |
+
"""Base class for entities with name and optional title fields."""
|
| 233 |
+
|
| 234 |
+
name: str
|
| 235 |
+
"""The programmatic name of the entity."""
|
| 236 |
+
|
| 237 |
+
title: str | None = None
|
| 238 |
+
"""
|
| 239 |
+
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
|
| 240 |
+
even by those unfamiliar with domain-specific terminology.
|
| 241 |
+
|
| 242 |
+
If not provided, the name should be used for display (except for Tool,
|
| 243 |
+
where `annotations.title` should be given precedence over using `name`,
|
| 244 |
+
if present).
|
| 245 |
+
"""
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class Icon(BaseModel):
|
| 249 |
+
"""An icon for display in user interfaces."""
|
| 250 |
+
|
| 251 |
+
src: str
|
| 252 |
+
"""URL or data URI for the icon."""
|
| 253 |
+
|
| 254 |
+
mimeType: str | None = None
|
| 255 |
+
"""Optional MIME type for the icon."""
|
| 256 |
+
|
| 257 |
+
sizes: list[str] | None = None
|
| 258 |
+
"""Optional list of strings specifying icon dimensions (e.g., ["48x48", "96x96"])."""
|
| 259 |
+
|
| 260 |
+
model_config = ConfigDict(extra="allow")
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
class Implementation(BaseMetadata):
|
| 264 |
+
"""Describes the name and version of an MCP implementation."""
|
| 265 |
+
|
| 266 |
+
version: str
|
| 267 |
+
|
| 268 |
+
websiteUrl: str | None = None
|
| 269 |
+
"""An optional URL of the website for this implementation."""
|
| 270 |
+
|
| 271 |
+
icons: list[Icon] | None = None
|
| 272 |
+
"""An optional list of icons for this implementation."""
|
| 273 |
+
|
| 274 |
+
model_config = ConfigDict(extra="allow")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
class RootsCapability(BaseModel):
|
| 278 |
+
"""Capability for root operations."""
|
| 279 |
+
|
| 280 |
+
listChanged: bool | None = None
|
| 281 |
+
"""Whether the client supports notifications for changes to the roots list."""
|
| 282 |
+
model_config = ConfigDict(extra="allow")
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
class SamplingContextCapability(BaseModel):
|
| 286 |
+
"""
|
| 287 |
+
Capability for context inclusion during sampling.
|
| 288 |
+
|
| 289 |
+
Indicates support for non-'none' values in the includeContext parameter.
|
| 290 |
+
SOFT-DEPRECATED: New implementations should use tools parameter instead.
|
| 291 |
+
"""
|
| 292 |
+
|
| 293 |
+
model_config = ConfigDict(extra="allow")
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
class SamplingToolsCapability(BaseModel):
|
| 297 |
+
"""
|
| 298 |
+
Capability indicating support for tool calling during sampling.
|
| 299 |
+
|
| 300 |
+
When present in ClientCapabilities.sampling, indicates that the client
|
| 301 |
+
supports the tools and toolChoice parameters in sampling requests.
|
| 302 |
+
"""
|
| 303 |
+
|
| 304 |
+
model_config = ConfigDict(extra="allow")
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
class FormElicitationCapability(BaseModel):
|
| 308 |
+
"""Capability for form mode elicitation."""
|
| 309 |
+
|
| 310 |
+
model_config = ConfigDict(extra="allow")
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
class UrlElicitationCapability(BaseModel):
|
| 314 |
+
"""Capability for URL mode elicitation."""
|
| 315 |
+
|
| 316 |
+
model_config = ConfigDict(extra="allow")
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
class ElicitationCapability(BaseModel):
|
| 320 |
+
"""Capability for elicitation operations.
|
| 321 |
+
|
| 322 |
+
Clients must support at least one mode (form or url).
|
| 323 |
+
"""
|
| 324 |
+
|
| 325 |
+
form: FormElicitationCapability | None = None
|
| 326 |
+
"""Present if the client supports form mode elicitation."""
|
| 327 |
+
|
| 328 |
+
url: UrlElicitationCapability | None = None
|
| 329 |
+
"""Present if the client supports URL mode elicitation."""
|
| 330 |
+
|
| 331 |
+
model_config = ConfigDict(extra="allow")
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
class SamplingCapability(BaseModel):
|
| 335 |
+
"""
|
| 336 |
+
Sampling capability structure, allowing fine-grained capability advertisement.
|
| 337 |
+
"""
|
| 338 |
+
|
| 339 |
+
context: SamplingContextCapability | None = None
|
| 340 |
+
"""
|
| 341 |
+
Present if the client supports non-'none' values for includeContext parameter.
|
| 342 |
+
SOFT-DEPRECATED: New implementations should use tools parameter instead.
|
| 343 |
+
"""
|
| 344 |
+
tools: SamplingToolsCapability | None = None
|
| 345 |
+
"""
|
| 346 |
+
Present if the client supports tools and toolChoice parameters in sampling requests.
|
| 347 |
+
Presence indicates full tool calling support during sampling.
|
| 348 |
+
"""
|
| 349 |
+
model_config = ConfigDict(extra="allow")
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
class TasksListCapability(BaseModel):
|
| 353 |
+
"""Capability for tasks listing operations."""
|
| 354 |
+
|
| 355 |
+
model_config = ConfigDict(extra="allow")
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
class TasksCancelCapability(BaseModel):
|
| 359 |
+
"""Capability for tasks cancel operations."""
|
| 360 |
+
|
| 361 |
+
model_config = ConfigDict(extra="allow")
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
class TasksCreateMessageCapability(BaseModel):
|
| 365 |
+
"""Capability for tasks create messages."""
|
| 366 |
+
|
| 367 |
+
model_config = ConfigDict(extra="allow")
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
class TasksSamplingCapability(BaseModel):
|
| 371 |
+
"""Capability for tasks sampling operations."""
|
| 372 |
+
|
| 373 |
+
model_config = ConfigDict(extra="allow")
|
| 374 |
+
|
| 375 |
+
createMessage: TasksCreateMessageCapability | None = None
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
class TasksCreateElicitationCapability(BaseModel):
|
| 379 |
+
"""Capability for tasks create elicitation operations."""
|
| 380 |
+
|
| 381 |
+
model_config = ConfigDict(extra="allow")
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
class TasksElicitationCapability(BaseModel):
|
| 385 |
+
"""Capability for tasks elicitation operations."""
|
| 386 |
+
|
| 387 |
+
model_config = ConfigDict(extra="allow")
|
| 388 |
+
|
| 389 |
+
create: TasksCreateElicitationCapability | None = None
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
class ClientTasksRequestsCapability(BaseModel):
|
| 393 |
+
"""Capability for tasks requests operations."""
|
| 394 |
+
|
| 395 |
+
model_config = ConfigDict(extra="allow")
|
| 396 |
+
|
| 397 |
+
sampling: TasksSamplingCapability | None = None
|
| 398 |
+
|
| 399 |
+
elicitation: TasksElicitationCapability | None = None
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
class ClientTasksCapability(BaseModel):
|
| 403 |
+
"""Capability for client tasks operations."""
|
| 404 |
+
|
| 405 |
+
model_config = ConfigDict(extra="allow")
|
| 406 |
+
|
| 407 |
+
list: TasksListCapability | None = None
|
| 408 |
+
"""Whether this client supports tasks/list."""
|
| 409 |
+
|
| 410 |
+
cancel: TasksCancelCapability | None = None
|
| 411 |
+
"""Whether this client supports tasks/cancel."""
|
| 412 |
+
|
| 413 |
+
requests: ClientTasksRequestsCapability | None = None
|
| 414 |
+
"""Specifies which request types can be augmented with tasks."""
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
class ClientCapabilities(BaseModel):
|
| 418 |
+
"""Capabilities a client may support."""
|
| 419 |
+
|
| 420 |
+
experimental: dict[str, dict[str, Any]] | None = None
|
| 421 |
+
"""Experimental, non-standard capabilities that the client supports."""
|
| 422 |
+
sampling: SamplingCapability | None = None
|
| 423 |
+
"""
|
| 424 |
+
Present if the client supports sampling from an LLM.
|
| 425 |
+
Can contain fine-grained capabilities like context and tools support.
|
| 426 |
+
"""
|
| 427 |
+
elicitation: ElicitationCapability | None = None
|
| 428 |
+
"""Present if the client supports elicitation from the user."""
|
| 429 |
+
roots: RootsCapability | None = None
|
| 430 |
+
"""Present if the client supports listing roots."""
|
| 431 |
+
tasks: ClientTasksCapability | None = None
|
| 432 |
+
"""Present if the client supports task-augmented requests."""
|
| 433 |
+
|
| 434 |
+
model_config = ConfigDict(extra="allow")
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
class PromptsCapability(BaseModel):
|
| 438 |
+
"""Capability for prompts operations."""
|
| 439 |
+
|
| 440 |
+
listChanged: bool | None = None
|
| 441 |
+
"""Whether this server supports notifications for changes to the prompt list."""
|
| 442 |
+
model_config = ConfigDict(extra="allow")
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
class ResourcesCapability(BaseModel):
|
| 446 |
+
"""Capability for resources operations."""
|
| 447 |
+
|
| 448 |
+
subscribe: bool | None = None
|
| 449 |
+
"""Whether this server supports subscribing to resource updates."""
|
| 450 |
+
listChanged: bool | None = None
|
| 451 |
+
"""Whether this server supports notifications for changes to the resource list."""
|
| 452 |
+
model_config = ConfigDict(extra="allow")
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
class ToolsCapability(BaseModel):
|
| 456 |
+
"""Capability for tools operations."""
|
| 457 |
+
|
| 458 |
+
listChanged: bool | None = None
|
| 459 |
+
"""Whether this server supports notifications for changes to the tool list."""
|
| 460 |
+
model_config = ConfigDict(extra="allow")
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
class LoggingCapability(BaseModel):
|
| 464 |
+
"""Capability for logging operations."""
|
| 465 |
+
|
| 466 |
+
model_config = ConfigDict(extra="allow")
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
class CompletionsCapability(BaseModel):
|
| 470 |
+
"""Capability for completions operations."""
|
| 471 |
+
|
| 472 |
+
model_config = ConfigDict(extra="allow")
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
class TasksCallCapability(BaseModel):
|
| 476 |
+
"""Capability for tasks call operations."""
|
| 477 |
+
|
| 478 |
+
model_config = ConfigDict(extra="allow")
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
class TasksToolsCapability(BaseModel):
|
| 482 |
+
"""Capability for tasks tools operations."""
|
| 483 |
+
|
| 484 |
+
model_config = ConfigDict(extra="allow")
|
| 485 |
+
call: TasksCallCapability | None = None
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
class ServerTasksRequestsCapability(BaseModel):
|
| 489 |
+
"""Capability for tasks requests operations."""
|
| 490 |
+
|
| 491 |
+
model_config = ConfigDict(extra="allow")
|
| 492 |
+
|
| 493 |
+
tools: TasksToolsCapability | None = None
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
class ServerTasksCapability(BaseModel):
|
| 497 |
+
"""Capability for server tasks operations."""
|
| 498 |
+
|
| 499 |
+
model_config = ConfigDict(extra="allow")
|
| 500 |
+
|
| 501 |
+
list: TasksListCapability | None = None
|
| 502 |
+
cancel: TasksCancelCapability | None = None
|
| 503 |
+
requests: ServerTasksRequestsCapability | None = None
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
class ServerCapabilities(BaseModel):
|
| 507 |
+
"""Capabilities that a server may support."""
|
| 508 |
+
|
| 509 |
+
experimental: dict[str, dict[str, Any]] | None = None
|
| 510 |
+
"""Experimental, non-standard capabilities that the server supports."""
|
| 511 |
+
logging: LoggingCapability | None = None
|
| 512 |
+
"""Present if the server supports sending log messages to the client."""
|
| 513 |
+
prompts: PromptsCapability | None = None
|
| 514 |
+
"""Present if the server offers any prompt templates."""
|
| 515 |
+
resources: ResourcesCapability | None = None
|
| 516 |
+
"""Present if the server offers any resources to read."""
|
| 517 |
+
tools: ToolsCapability | None = None
|
| 518 |
+
"""Present if the server offers any tools to call."""
|
| 519 |
+
completions: CompletionsCapability | None = None
|
| 520 |
+
"""Present if the server offers autocompletion suggestions for prompts and resources."""
|
| 521 |
+
tasks: ServerTasksCapability | None = None
|
| 522 |
+
"""Present if the server supports task-augmented requests."""
|
| 523 |
+
model_config = ConfigDict(extra="allow")
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"]
|
| 527 |
+
|
| 528 |
+
# Task status constants
|
| 529 |
+
TASK_STATUS_WORKING: Final[Literal["working"]] = "working"
|
| 530 |
+
TASK_STATUS_INPUT_REQUIRED: Final[Literal["input_required"]] = "input_required"
|
| 531 |
+
TASK_STATUS_COMPLETED: Final[Literal["completed"]] = "completed"
|
| 532 |
+
TASK_STATUS_FAILED: Final[Literal["failed"]] = "failed"
|
| 533 |
+
TASK_STATUS_CANCELLED: Final[Literal["cancelled"]] = "cancelled"
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
class RelatedTaskMetadata(BaseModel):
|
| 537 |
+
"""
|
| 538 |
+
Metadata for associating messages with a task.
|
| 539 |
+
|
| 540 |
+
Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.
|
| 541 |
+
"""
|
| 542 |
+
|
| 543 |
+
model_config = ConfigDict(extra="allow")
|
| 544 |
+
taskId: str
|
| 545 |
+
"""The task identifier this message is associated with."""
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
class Task(BaseModel):
|
| 549 |
+
"""Data associated with a task."""
|
| 550 |
+
|
| 551 |
+
model_config = ConfigDict(extra="allow")
|
| 552 |
+
|
| 553 |
+
taskId: str
|
| 554 |
+
"""The task identifier."""
|
| 555 |
+
|
| 556 |
+
status: TaskStatus
|
| 557 |
+
"""Current task state."""
|
| 558 |
+
|
| 559 |
+
statusMessage: str | None = None
|
| 560 |
+
"""
|
| 561 |
+
Optional human-readable message describing the current task state.
|
| 562 |
+
This can provide context for any status, including:
|
| 563 |
+
- Reasons for "cancelled" status
|
| 564 |
+
- Summaries for "completed" status
|
| 565 |
+
- Diagnostic information for "failed" status (e.g., error details, what went wrong)
|
| 566 |
+
"""
|
| 567 |
+
|
| 568 |
+
createdAt: datetime # Pydantic will enforce ISO 8601 and re-serialize as a string later
|
| 569 |
+
"""ISO 8601 timestamp when the task was created."""
|
| 570 |
+
|
| 571 |
+
lastUpdatedAt: datetime
|
| 572 |
+
"""ISO 8601 timestamp when the task was last updated."""
|
| 573 |
+
|
| 574 |
+
ttl: Annotated[int, Field(strict=True)] | None
|
| 575 |
+
"""Actual retention duration from creation in milliseconds, null for unlimited."""
|
| 576 |
+
|
| 577 |
+
pollInterval: Annotated[int, Field(strict=True)] | None = None
|
| 578 |
+
"""Suggested polling interval in milliseconds."""
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
class CreateTaskResult(Result):
|
| 582 |
+
"""A response to a task-augmented request."""
|
| 583 |
+
|
| 584 |
+
task: Task
|
| 585 |
+
|
| 586 |
+
|
| 587 |
+
class GetTaskRequestParams(RequestParams):
|
| 588 |
+
model_config = ConfigDict(extra="allow")
|
| 589 |
+
taskId: str
|
| 590 |
+
"""The task identifier to query."""
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]):
|
| 594 |
+
"""A request to retrieve the state of a task."""
|
| 595 |
+
|
| 596 |
+
method: Literal["tasks/get"] = "tasks/get"
|
| 597 |
+
|
| 598 |
+
params: GetTaskRequestParams
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
class GetTaskResult(Result, Task):
|
| 602 |
+
"""The response to a tasks/get request."""
|
| 603 |
+
|
| 604 |
+
|
| 605 |
+
class GetTaskPayloadRequestParams(RequestParams):
|
| 606 |
+
model_config = ConfigDict(extra="allow")
|
| 607 |
+
|
| 608 |
+
taskId: str
|
| 609 |
+
"""The task identifier to retrieve results for."""
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]):
|
| 613 |
+
"""A request to retrieve the result of a completed task."""
|
| 614 |
+
|
| 615 |
+
method: Literal["tasks/result"] = "tasks/result"
|
| 616 |
+
params: GetTaskPayloadRequestParams
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
class GetTaskPayloadResult(Result):
|
| 620 |
+
"""
|
| 621 |
+
The response to a tasks/result request.
|
| 622 |
+
The structure matches the result type of the original request.
|
| 623 |
+
For example, a tools/call task would return the CallToolResult structure.
|
| 624 |
+
"""
|
| 625 |
+
|
| 626 |
+
|
| 627 |
+
class CancelTaskRequestParams(RequestParams):
|
| 628 |
+
model_config = ConfigDict(extra="allow")
|
| 629 |
+
|
| 630 |
+
taskId: str
|
| 631 |
+
"""The task identifier to cancel."""
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]):
|
| 635 |
+
"""A request to cancel a task."""
|
| 636 |
+
|
| 637 |
+
method: Literal["tasks/cancel"] = "tasks/cancel"
|
| 638 |
+
params: CancelTaskRequestParams
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
class CancelTaskResult(Result, Task):
|
| 642 |
+
"""The response to a tasks/cancel request."""
|
| 643 |
+
|
| 644 |
+
|
| 645 |
+
class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]):
|
| 646 |
+
"""A request to retrieve a list of tasks."""
|
| 647 |
+
|
| 648 |
+
method: Literal["tasks/list"] = "tasks/list"
|
| 649 |
+
|
| 650 |
+
|
| 651 |
+
class ListTasksResult(PaginatedResult):
|
| 652 |
+
"""The response to a tasks/list request."""
|
| 653 |
+
|
| 654 |
+
tasks: list[Task]
|
| 655 |
+
|
| 656 |
+
|
| 657 |
+
class TaskStatusNotificationParams(NotificationParams, Task):
|
| 658 |
+
"""Parameters for a `notifications/tasks/status` notification."""
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]):
|
| 662 |
+
"""
|
| 663 |
+
An optional notification from the receiver to the requestor, informing them that a task's status has changed.
|
| 664 |
+
Receivers are not required to send these notifications
|
| 665 |
+
"""
|
| 666 |
+
|
| 667 |
+
method: Literal["notifications/tasks/status"] = "notifications/tasks/status"
|
| 668 |
+
params: TaskStatusNotificationParams
|
| 669 |
+
|
| 670 |
+
|
| 671 |
+
class InitializeRequestParams(RequestParams):
|
| 672 |
+
"""Parameters for the initialize request."""
|
| 673 |
+
|
| 674 |
+
protocolVersion: str | int
|
| 675 |
+
"""The latest version of the Model Context Protocol that the client supports."""
|
| 676 |
+
capabilities: ClientCapabilities
|
| 677 |
+
clientInfo: Implementation
|
| 678 |
+
model_config = ConfigDict(extra="allow")
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]):
|
| 682 |
+
"""
|
| 683 |
+
This request is sent from the client to the server when it first connects, asking it
|
| 684 |
+
to begin initialization.
|
| 685 |
+
"""
|
| 686 |
+
|
| 687 |
+
method: Literal["initialize"] = "initialize"
|
| 688 |
+
params: InitializeRequestParams
|
| 689 |
+
|
| 690 |
+
|
| 691 |
+
class InitializeResult(Result):
|
| 692 |
+
"""After receiving an initialize request from the client, the server sends this."""
|
| 693 |
+
|
| 694 |
+
protocolVersion: str | int
|
| 695 |
+
"""The version of the Model Context Protocol that the server wants to use."""
|
| 696 |
+
capabilities: ServerCapabilities
|
| 697 |
+
serverInfo: Implementation
|
| 698 |
+
instructions: str | None = None
|
| 699 |
+
"""Instructions describing how to use the server and its features."""
|
| 700 |
+
|
| 701 |
+
|
| 702 |
+
class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]):
|
| 703 |
+
"""
|
| 704 |
+
This notification is sent from the client to the server after initialization has
|
| 705 |
+
finished.
|
| 706 |
+
"""
|
| 707 |
+
|
| 708 |
+
method: Literal["notifications/initialized"] = "notifications/initialized"
|
| 709 |
+
params: NotificationParams | None = None
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
class PingRequest(Request[RequestParams | None, Literal["ping"]]):
|
| 713 |
+
"""
|
| 714 |
+
A ping, issued by either the server or the client, to check that the other party is
|
| 715 |
+
still alive.
|
| 716 |
+
"""
|
| 717 |
+
|
| 718 |
+
method: Literal["ping"] = "ping"
|
| 719 |
+
params: RequestParams | None = None
|
| 720 |
+
|
| 721 |
+
|
| 722 |
+
class ProgressNotificationParams(NotificationParams):
|
| 723 |
+
"""Parameters for progress notifications."""
|
| 724 |
+
|
| 725 |
+
progressToken: ProgressToken
|
| 726 |
+
"""
|
| 727 |
+
The progress token which was given in the initial request, used to associate this
|
| 728 |
+
notification with the request that is proceeding.
|
| 729 |
+
"""
|
| 730 |
+
progress: float
|
| 731 |
+
"""
|
| 732 |
+
The progress thus far. This should increase every time progress is made, even if the
|
| 733 |
+
total is unknown.
|
| 734 |
+
"""
|
| 735 |
+
total: float | None = None
|
| 736 |
+
"""Total number of items to process (or total progress required), if known."""
|
| 737 |
+
message: str | None = None
|
| 738 |
+
"""
|
| 739 |
+
Message related to progress. This should provide relevant human readable
|
| 740 |
+
progress information.
|
| 741 |
+
"""
|
| 742 |
+
model_config = ConfigDict(extra="allow")
|
| 743 |
+
|
| 744 |
+
|
| 745 |
+
class ProgressNotification(Notification[ProgressNotificationParams, Literal["notifications/progress"]]):
|
| 746 |
+
"""
|
| 747 |
+
An out-of-band notification used to inform the receiver of a progress update for a
|
| 748 |
+
long-running request.
|
| 749 |
+
"""
|
| 750 |
+
|
| 751 |
+
method: Literal["notifications/progress"] = "notifications/progress"
|
| 752 |
+
params: ProgressNotificationParams
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
class ListResourcesRequest(PaginatedRequest[Literal["resources/list"]]):
|
| 756 |
+
"""Sent from the client to request a list of resources the server has."""
|
| 757 |
+
|
| 758 |
+
method: Literal["resources/list"] = "resources/list"
|
| 759 |
+
|
| 760 |
+
|
| 761 |
+
class Annotations(BaseModel):
|
| 762 |
+
audience: list[Role] | None = None
|
| 763 |
+
priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None
|
| 764 |
+
model_config = ConfigDict(extra="allow")
|
| 765 |
+
|
| 766 |
+
|
| 767 |
+
class Resource(BaseMetadata):
|
| 768 |
+
"""A known resource that the server is capable of reading."""
|
| 769 |
+
|
| 770 |
+
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
| 771 |
+
"""The URI of this resource."""
|
| 772 |
+
description: str | None = None
|
| 773 |
+
"""A description of what this resource represents."""
|
| 774 |
+
mimeType: str | None = None
|
| 775 |
+
"""The MIME type of this resource, if known."""
|
| 776 |
+
size: int | None = None
|
| 777 |
+
"""
|
| 778 |
+
The size of the raw resource content, in bytes (i.e., before base64 encoding
|
| 779 |
+
or any tokenization), if known.
|
| 780 |
+
|
| 781 |
+
This can be used by Hosts to display file sizes and estimate context window usage.
|
| 782 |
+
"""
|
| 783 |
+
icons: list[Icon] | None = None
|
| 784 |
+
"""An optional list of icons for this resource."""
|
| 785 |
+
annotations: Annotations | None = None
|
| 786 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 787 |
+
"""
|
| 788 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 789 |
+
for notes on _meta usage.
|
| 790 |
+
"""
|
| 791 |
+
model_config = ConfigDict(extra="allow")
|
| 792 |
+
|
| 793 |
+
|
| 794 |
+
class ResourceTemplate(BaseMetadata):
|
| 795 |
+
"""A template description for resources available on the server."""
|
| 796 |
+
|
| 797 |
+
uriTemplate: str
|
| 798 |
+
"""
|
| 799 |
+
A URI template (according to RFC 6570) that can be used to construct resource
|
| 800 |
+
URIs.
|
| 801 |
+
"""
|
| 802 |
+
description: str | None = None
|
| 803 |
+
"""A human-readable description of what this template is for."""
|
| 804 |
+
mimeType: str | None = None
|
| 805 |
+
"""
|
| 806 |
+
The MIME type for all resources that match this template. This should only be
|
| 807 |
+
included if all resources matching this template have the same type.
|
| 808 |
+
"""
|
| 809 |
+
icons: list[Icon] | None = None
|
| 810 |
+
"""An optional list of icons for this resource template."""
|
| 811 |
+
annotations: Annotations | None = None
|
| 812 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 813 |
+
"""
|
| 814 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 815 |
+
for notes on _meta usage.
|
| 816 |
+
"""
|
| 817 |
+
model_config = ConfigDict(extra="allow")
|
| 818 |
+
|
| 819 |
+
|
| 820 |
+
class ListResourcesResult(PaginatedResult):
|
| 821 |
+
"""The server's response to a resources/list request from the client."""
|
| 822 |
+
|
| 823 |
+
resources: list[Resource]
|
| 824 |
+
|
| 825 |
+
|
| 826 |
+
class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates/list"]]):
|
| 827 |
+
"""Sent from the client to request a list of resource templates the server has."""
|
| 828 |
+
|
| 829 |
+
method: Literal["resources/templates/list"] = "resources/templates/list"
|
| 830 |
+
|
| 831 |
+
|
| 832 |
+
class ListResourceTemplatesResult(PaginatedResult):
|
| 833 |
+
"""The server's response to a resources/templates/list request from the client."""
|
| 834 |
+
|
| 835 |
+
resourceTemplates: list[ResourceTemplate]
|
| 836 |
+
|
| 837 |
+
|
| 838 |
+
class ReadResourceRequestParams(RequestParams):
|
| 839 |
+
"""Parameters for reading a resource."""
|
| 840 |
+
|
| 841 |
+
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
| 842 |
+
"""
|
| 843 |
+
The URI of the resource to read. The URI can use any protocol; it is up to the
|
| 844 |
+
server how to interpret it.
|
| 845 |
+
"""
|
| 846 |
+
model_config = ConfigDict(extra="allow")
|
| 847 |
+
|
| 848 |
+
|
| 849 |
+
class ReadResourceRequest(Request[ReadResourceRequestParams, Literal["resources/read"]]):
|
| 850 |
+
"""Sent from the client to the server, to read a specific resource URI."""
|
| 851 |
+
|
| 852 |
+
method: Literal["resources/read"] = "resources/read"
|
| 853 |
+
params: ReadResourceRequestParams
|
| 854 |
+
|
| 855 |
+
|
| 856 |
+
class ResourceContents(BaseModel):
|
| 857 |
+
"""The contents of a specific resource or sub-resource."""
|
| 858 |
+
|
| 859 |
+
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
| 860 |
+
"""The URI of this resource."""
|
| 861 |
+
mimeType: str | None = None
|
| 862 |
+
"""The MIME type of this resource, if known."""
|
| 863 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 864 |
+
"""
|
| 865 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 866 |
+
for notes on _meta usage.
|
| 867 |
+
"""
|
| 868 |
+
model_config = ConfigDict(extra="allow")
|
| 869 |
+
|
| 870 |
+
|
| 871 |
+
class TextResourceContents(ResourceContents):
|
| 872 |
+
"""Text contents of a resource."""
|
| 873 |
+
|
| 874 |
+
text: str
|
| 875 |
+
"""
|
| 876 |
+
The text of the item. This must only be set if the item can actually be represented
|
| 877 |
+
as text (not binary data).
|
| 878 |
+
"""
|
| 879 |
+
|
| 880 |
+
|
| 881 |
+
class BlobResourceContents(ResourceContents):
|
| 882 |
+
"""Binary contents of a resource."""
|
| 883 |
+
|
| 884 |
+
blob: str
|
| 885 |
+
"""A base64-encoded string representing the binary data of the item."""
|
| 886 |
+
|
| 887 |
+
|
| 888 |
+
class ReadResourceResult(Result):
|
| 889 |
+
"""The server's response to a resources/read request from the client."""
|
| 890 |
+
|
| 891 |
+
contents: list[TextResourceContents | BlobResourceContents]
|
| 892 |
+
|
| 893 |
+
|
| 894 |
+
class ResourceListChangedNotification(
|
| 895 |
+
Notification[NotificationParams | None, Literal["notifications/resources/list_changed"]]
|
| 896 |
+
):
|
| 897 |
+
"""
|
| 898 |
+
An optional notification from the server to the client, informing it that the list
|
| 899 |
+
of resources it can read from has changed.
|
| 900 |
+
"""
|
| 901 |
+
|
| 902 |
+
method: Literal["notifications/resources/list_changed"] = "notifications/resources/list_changed"
|
| 903 |
+
params: NotificationParams | None = None
|
| 904 |
+
|
| 905 |
+
|
| 906 |
+
class SubscribeRequestParams(RequestParams):
|
| 907 |
+
"""Parameters for subscribing to a resource."""
|
| 908 |
+
|
| 909 |
+
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
| 910 |
+
"""
|
| 911 |
+
The URI of the resource to subscribe to. The URI can use any protocol; it is up to
|
| 912 |
+
the server how to interpret it.
|
| 913 |
+
"""
|
| 914 |
+
model_config = ConfigDict(extra="allow")
|
| 915 |
+
|
| 916 |
+
|
| 917 |
+
class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscribe"]]):
|
| 918 |
+
"""
|
| 919 |
+
Sent from the client to request resources/updated notifications from the server
|
| 920 |
+
whenever a particular resource changes.
|
| 921 |
+
"""
|
| 922 |
+
|
| 923 |
+
method: Literal["resources/subscribe"] = "resources/subscribe"
|
| 924 |
+
params: SubscribeRequestParams
|
| 925 |
+
|
| 926 |
+
|
| 927 |
+
class UnsubscribeRequestParams(RequestParams):
|
| 928 |
+
"""Parameters for unsubscribing from a resource."""
|
| 929 |
+
|
| 930 |
+
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
| 931 |
+
"""The URI of the resource to unsubscribe from."""
|
| 932 |
+
model_config = ConfigDict(extra="allow")
|
| 933 |
+
|
| 934 |
+
|
| 935 |
+
class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]):
|
| 936 |
+
"""
|
| 937 |
+
Sent from the client to request cancellation of resources/updated notifications from
|
| 938 |
+
the server.
|
| 939 |
+
"""
|
| 940 |
+
|
| 941 |
+
method: Literal["resources/unsubscribe"] = "resources/unsubscribe"
|
| 942 |
+
params: UnsubscribeRequestParams
|
| 943 |
+
|
| 944 |
+
|
| 945 |
+
class ResourceUpdatedNotificationParams(NotificationParams):
|
| 946 |
+
"""Parameters for resource update notifications."""
|
| 947 |
+
|
| 948 |
+
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)]
|
| 949 |
+
"""
|
| 950 |
+
The URI of the resource that has been updated. This might be a sub-resource of the
|
| 951 |
+
one that the client actually subscribed to.
|
| 952 |
+
"""
|
| 953 |
+
model_config = ConfigDict(extra="allow")
|
| 954 |
+
|
| 955 |
+
|
| 956 |
+
class ResourceUpdatedNotification(
|
| 957 |
+
Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]]
|
| 958 |
+
):
|
| 959 |
+
"""
|
| 960 |
+
A notification from the server to the client, informing it that a resource has
|
| 961 |
+
changed and may need to be read again.
|
| 962 |
+
"""
|
| 963 |
+
|
| 964 |
+
method: Literal["notifications/resources/updated"] = "notifications/resources/updated"
|
| 965 |
+
params: ResourceUpdatedNotificationParams
|
| 966 |
+
|
| 967 |
+
|
| 968 |
+
class ListPromptsRequest(PaginatedRequest[Literal["prompts/list"]]):
|
| 969 |
+
"""Sent from the client to request a list of prompts and prompt templates."""
|
| 970 |
+
|
| 971 |
+
method: Literal["prompts/list"] = "prompts/list"
|
| 972 |
+
|
| 973 |
+
|
| 974 |
+
class PromptArgument(BaseModel):
|
| 975 |
+
"""An argument for a prompt template."""
|
| 976 |
+
|
| 977 |
+
name: str
|
| 978 |
+
"""The name of the argument."""
|
| 979 |
+
description: str | None = None
|
| 980 |
+
"""A human-readable description of the argument."""
|
| 981 |
+
required: bool | None = None
|
| 982 |
+
"""Whether this argument must be provided."""
|
| 983 |
+
model_config = ConfigDict(extra="allow")
|
| 984 |
+
|
| 985 |
+
|
| 986 |
+
class Prompt(BaseMetadata):
|
| 987 |
+
"""A prompt or prompt template that the server offers."""
|
| 988 |
+
|
| 989 |
+
description: str | None = None
|
| 990 |
+
"""An optional description of what this prompt provides."""
|
| 991 |
+
arguments: list[PromptArgument] | None = None
|
| 992 |
+
"""A list of arguments to use for templating the prompt."""
|
| 993 |
+
icons: list[Icon] | None = None
|
| 994 |
+
"""An optional list of icons for this prompt."""
|
| 995 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 996 |
+
"""
|
| 997 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 998 |
+
for notes on _meta usage.
|
| 999 |
+
"""
|
| 1000 |
+
model_config = ConfigDict(extra="allow")
|
| 1001 |
+
|
| 1002 |
+
|
| 1003 |
+
class ListPromptsResult(PaginatedResult):
|
| 1004 |
+
"""The server's response to a prompts/list request from the client."""
|
| 1005 |
+
|
| 1006 |
+
prompts: list[Prompt]
|
| 1007 |
+
|
| 1008 |
+
|
| 1009 |
+
class GetPromptRequestParams(RequestParams):
|
| 1010 |
+
"""Parameters for getting a prompt."""
|
| 1011 |
+
|
| 1012 |
+
name: str
|
| 1013 |
+
"""The name of the prompt or prompt template."""
|
| 1014 |
+
arguments: dict[str, str] | None = None
|
| 1015 |
+
"""Arguments to use for templating the prompt."""
|
| 1016 |
+
model_config = ConfigDict(extra="allow")
|
| 1017 |
+
|
| 1018 |
+
|
| 1019 |
+
class GetPromptRequest(Request[GetPromptRequestParams, Literal["prompts/get"]]):
|
| 1020 |
+
"""Used by the client to get a prompt provided by the server."""
|
| 1021 |
+
|
| 1022 |
+
method: Literal["prompts/get"] = "prompts/get"
|
| 1023 |
+
params: GetPromptRequestParams
|
| 1024 |
+
|
| 1025 |
+
|
| 1026 |
+
class TextContent(BaseModel):
|
| 1027 |
+
"""Text content for a message."""
|
| 1028 |
+
|
| 1029 |
+
type: Literal["text"]
|
| 1030 |
+
text: str
|
| 1031 |
+
"""The text content of the message."""
|
| 1032 |
+
annotations: Annotations | None = None
|
| 1033 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1034 |
+
"""
|
| 1035 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1036 |
+
for notes on _meta usage.
|
| 1037 |
+
"""
|
| 1038 |
+
model_config = ConfigDict(extra="allow")
|
| 1039 |
+
|
| 1040 |
+
|
| 1041 |
+
class ImageContent(BaseModel):
|
| 1042 |
+
"""Image content for a message."""
|
| 1043 |
+
|
| 1044 |
+
type: Literal["image"]
|
| 1045 |
+
data: str
|
| 1046 |
+
"""The base64-encoded image data."""
|
| 1047 |
+
mimeType: str
|
| 1048 |
+
"""
|
| 1049 |
+
The MIME type of the image. Different providers may support different
|
| 1050 |
+
image types.
|
| 1051 |
+
"""
|
| 1052 |
+
annotations: Annotations | None = None
|
| 1053 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1054 |
+
"""
|
| 1055 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1056 |
+
for notes on _meta usage.
|
| 1057 |
+
"""
|
| 1058 |
+
model_config = ConfigDict(extra="allow")
|
| 1059 |
+
|
| 1060 |
+
|
| 1061 |
+
class AudioContent(BaseModel):
|
| 1062 |
+
"""Audio content for a message."""
|
| 1063 |
+
|
| 1064 |
+
type: Literal["audio"]
|
| 1065 |
+
data: str
|
| 1066 |
+
"""The base64-encoded audio data."""
|
| 1067 |
+
mimeType: str
|
| 1068 |
+
"""
|
| 1069 |
+
The MIME type of the audio. Different providers may support different
|
| 1070 |
+
audio types.
|
| 1071 |
+
"""
|
| 1072 |
+
annotations: Annotations | None = None
|
| 1073 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1074 |
+
"""
|
| 1075 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1076 |
+
for notes on _meta usage.
|
| 1077 |
+
"""
|
| 1078 |
+
model_config = ConfigDict(extra="allow")
|
| 1079 |
+
|
| 1080 |
+
|
| 1081 |
+
class ToolUseContent(BaseModel):
|
| 1082 |
+
"""
|
| 1083 |
+
Content representing an assistant's request to invoke a tool.
|
| 1084 |
+
|
| 1085 |
+
This content type appears in assistant messages when the LLM wants to call a tool
|
| 1086 |
+
during sampling. The server should execute the tool and return a ToolResultContent
|
| 1087 |
+
in the next user message.
|
| 1088 |
+
"""
|
| 1089 |
+
|
| 1090 |
+
type: Literal["tool_use"]
|
| 1091 |
+
"""Discriminator for tool use content."""
|
| 1092 |
+
|
| 1093 |
+
name: str
|
| 1094 |
+
"""The name of the tool to invoke. Must match a tool name from the request's tools array."""
|
| 1095 |
+
|
| 1096 |
+
id: str
|
| 1097 |
+
"""Unique identifier for this tool call, used to correlate with ToolResultContent."""
|
| 1098 |
+
|
| 1099 |
+
input: dict[str, Any]
|
| 1100 |
+
"""Arguments to pass to the tool. Must conform to the tool's inputSchema."""
|
| 1101 |
+
|
| 1102 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1103 |
+
"""
|
| 1104 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1105 |
+
for notes on _meta usage.
|
| 1106 |
+
"""
|
| 1107 |
+
model_config = ConfigDict(extra="allow")
|
| 1108 |
+
|
| 1109 |
+
|
| 1110 |
+
class ToolResultContent(BaseModel):
|
| 1111 |
+
"""
|
| 1112 |
+
Content representing the result of a tool execution.
|
| 1113 |
+
|
| 1114 |
+
This content type appears in user messages as a response to a ToolUseContent
|
| 1115 |
+
from the assistant. It contains the output of executing the requested tool.
|
| 1116 |
+
"""
|
| 1117 |
+
|
| 1118 |
+
type: Literal["tool_result"]
|
| 1119 |
+
"""Discriminator for tool result content."""
|
| 1120 |
+
|
| 1121 |
+
toolUseId: str
|
| 1122 |
+
"""The unique identifier that corresponds to the tool call's id field."""
|
| 1123 |
+
|
| 1124 |
+
content: list["ContentBlock"] = []
|
| 1125 |
+
"""
|
| 1126 |
+
A list of content objects representing the tool result.
|
| 1127 |
+
Defaults to empty list if not provided.
|
| 1128 |
+
"""
|
| 1129 |
+
|
| 1130 |
+
structuredContent: dict[str, Any] | None = None
|
| 1131 |
+
"""
|
| 1132 |
+
Optional structured tool output that matches the tool's outputSchema (if defined).
|
| 1133 |
+
"""
|
| 1134 |
+
|
| 1135 |
+
isError: bool | None = None
|
| 1136 |
+
"""Whether the tool execution resulted in an error."""
|
| 1137 |
+
|
| 1138 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1139 |
+
"""
|
| 1140 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1141 |
+
for notes on _meta usage.
|
| 1142 |
+
"""
|
| 1143 |
+
model_config = ConfigDict(extra="allow")
|
| 1144 |
+
|
| 1145 |
+
|
| 1146 |
+
SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent
|
| 1147 |
+
"""Content block types allowed in sampling messages."""
|
| 1148 |
+
|
| 1149 |
+
SamplingContent: TypeAlias = TextContent | ImageContent | AudioContent
|
| 1150 |
+
"""Basic content types for sampling responses (without tool use).
|
| 1151 |
+
Used for backwards-compatible CreateMessageResult when tools are not used."""
|
| 1152 |
+
|
| 1153 |
+
|
| 1154 |
+
class SamplingMessage(BaseModel):
|
| 1155 |
+
"""Describes a message issued to or received from an LLM API."""
|
| 1156 |
+
|
| 1157 |
+
role: Role
|
| 1158 |
+
content: SamplingMessageContentBlock | list[SamplingMessageContentBlock]
|
| 1159 |
+
"""
|
| 1160 |
+
Message content. Can be a single content block or an array of content blocks
|
| 1161 |
+
for multi-modal messages and tool interactions.
|
| 1162 |
+
"""
|
| 1163 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1164 |
+
"""
|
| 1165 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1166 |
+
for notes on _meta usage.
|
| 1167 |
+
"""
|
| 1168 |
+
model_config = ConfigDict(extra="allow")
|
| 1169 |
+
|
| 1170 |
+
@property
|
| 1171 |
+
def content_as_list(self) -> list[SamplingMessageContentBlock]:
|
| 1172 |
+
"""Returns the content as a list of content blocks, regardless of whether
|
| 1173 |
+
it was originally a single block or a list."""
|
| 1174 |
+
return self.content if isinstance(self.content, list) else [self.content]
|
| 1175 |
+
|
| 1176 |
+
|
| 1177 |
+
class EmbeddedResource(BaseModel):
|
| 1178 |
+
"""
|
| 1179 |
+
The contents of a resource, embedded into a prompt or tool call result.
|
| 1180 |
+
|
| 1181 |
+
It is up to the client how best to render embedded resources for the benefit
|
| 1182 |
+
of the LLM and/or the user.
|
| 1183 |
+
"""
|
| 1184 |
+
|
| 1185 |
+
type: Literal["resource"]
|
| 1186 |
+
resource: TextResourceContents | BlobResourceContents
|
| 1187 |
+
annotations: Annotations | None = None
|
| 1188 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1189 |
+
"""
|
| 1190 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1191 |
+
for notes on _meta usage.
|
| 1192 |
+
"""
|
| 1193 |
+
model_config = ConfigDict(extra="allow")
|
| 1194 |
+
|
| 1195 |
+
|
| 1196 |
+
class ResourceLink(Resource):
|
| 1197 |
+
"""
|
| 1198 |
+
A resource that the server is capable of reading, included in a prompt or tool call result.
|
| 1199 |
+
|
| 1200 |
+
Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.
|
| 1201 |
+
"""
|
| 1202 |
+
|
| 1203 |
+
type: Literal["resource_link"]
|
| 1204 |
+
|
| 1205 |
+
|
| 1206 |
+
ContentBlock = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource
|
| 1207 |
+
"""A content block that can be used in prompts and tool results."""
|
| 1208 |
+
|
| 1209 |
+
Content: TypeAlias = ContentBlock
|
| 1210 |
+
# """DEPRECATED: Content is deprecated, you should use ContentBlock directly."""
|
| 1211 |
+
|
| 1212 |
+
|
| 1213 |
+
class PromptMessage(BaseModel):
|
| 1214 |
+
"""Describes a message returned as part of a prompt."""
|
| 1215 |
+
|
| 1216 |
+
role: Role
|
| 1217 |
+
content: ContentBlock
|
| 1218 |
+
model_config = ConfigDict(extra="allow")
|
| 1219 |
+
|
| 1220 |
+
|
| 1221 |
+
class GetPromptResult(Result):
|
| 1222 |
+
"""The server's response to a prompts/get request from the client."""
|
| 1223 |
+
|
| 1224 |
+
description: str | None = None
|
| 1225 |
+
"""An optional description for the prompt."""
|
| 1226 |
+
messages: list[PromptMessage]
|
| 1227 |
+
|
| 1228 |
+
|
| 1229 |
+
class PromptListChangedNotification(
|
| 1230 |
+
Notification[NotificationParams | None, Literal["notifications/prompts/list_changed"]]
|
| 1231 |
+
):
|
| 1232 |
+
"""
|
| 1233 |
+
An optional notification from the server to the client, informing it that the list
|
| 1234 |
+
of prompts it offers has changed.
|
| 1235 |
+
"""
|
| 1236 |
+
|
| 1237 |
+
method: Literal["notifications/prompts/list_changed"] = "notifications/prompts/list_changed"
|
| 1238 |
+
params: NotificationParams | None = None
|
| 1239 |
+
|
| 1240 |
+
|
| 1241 |
+
class ListToolsRequest(PaginatedRequest[Literal["tools/list"]]):
|
| 1242 |
+
"""Sent from the client to request a list of tools the server has."""
|
| 1243 |
+
|
| 1244 |
+
method: Literal["tools/list"] = "tools/list"
|
| 1245 |
+
|
| 1246 |
+
|
| 1247 |
+
class ToolAnnotations(BaseModel):
|
| 1248 |
+
"""
|
| 1249 |
+
Additional properties describing a Tool to clients.
|
| 1250 |
+
|
| 1251 |
+
NOTE: all properties in ToolAnnotations are **hints**.
|
| 1252 |
+
They are not guaranteed to provide a faithful description of
|
| 1253 |
+
tool behavior (including descriptive properties like `title`).
|
| 1254 |
+
|
| 1255 |
+
Clients should never make tool use decisions based on ToolAnnotations
|
| 1256 |
+
received from untrusted servers.
|
| 1257 |
+
"""
|
| 1258 |
+
|
| 1259 |
+
title: str | None = None
|
| 1260 |
+
"""A human-readable title for the tool."""
|
| 1261 |
+
|
| 1262 |
+
readOnlyHint: bool | None = None
|
| 1263 |
+
"""
|
| 1264 |
+
If true, the tool does not modify its environment.
|
| 1265 |
+
Default: false
|
| 1266 |
+
"""
|
| 1267 |
+
|
| 1268 |
+
destructiveHint: bool | None = None
|
| 1269 |
+
"""
|
| 1270 |
+
If true, the tool may perform destructive updates to its environment.
|
| 1271 |
+
If false, the tool performs only additive updates.
|
| 1272 |
+
(This property is meaningful only when `readOnlyHint == false`)
|
| 1273 |
+
Default: true
|
| 1274 |
+
"""
|
| 1275 |
+
|
| 1276 |
+
idempotentHint: bool | None = None
|
| 1277 |
+
"""
|
| 1278 |
+
If true, calling the tool repeatedly with the same arguments
|
| 1279 |
+
will have no additional effect on the its environment.
|
| 1280 |
+
(This property is meaningful only when `readOnlyHint == false`)
|
| 1281 |
+
Default: false
|
| 1282 |
+
"""
|
| 1283 |
+
|
| 1284 |
+
openWorldHint: bool | None = None
|
| 1285 |
+
"""
|
| 1286 |
+
If true, this tool may interact with an "open world" of external
|
| 1287 |
+
entities. If false, the tool's domain of interaction is closed.
|
| 1288 |
+
For example, the world of a web search tool is open, whereas that
|
| 1289 |
+
of a memory tool is not.
|
| 1290 |
+
Default: true
|
| 1291 |
+
"""
|
| 1292 |
+
|
| 1293 |
+
model_config = ConfigDict(extra="allow")
|
| 1294 |
+
|
| 1295 |
+
|
| 1296 |
+
class ToolExecution(BaseModel):
|
| 1297 |
+
"""Execution-related properties for a tool."""
|
| 1298 |
+
|
| 1299 |
+
model_config = ConfigDict(extra="allow")
|
| 1300 |
+
|
| 1301 |
+
taskSupport: TaskExecutionMode | None = None
|
| 1302 |
+
"""
|
| 1303 |
+
Indicates whether this tool supports task-augmented execution.
|
| 1304 |
+
This allows clients to handle long-running operations through polling
|
| 1305 |
+
the task system.
|
| 1306 |
+
|
| 1307 |
+
- "forbidden": Tool does not support task-augmented execution (default when absent)
|
| 1308 |
+
- "optional": Tool may support task-augmented execution
|
| 1309 |
+
- "required": Tool requires task-augmented execution
|
| 1310 |
+
|
| 1311 |
+
Default: "forbidden"
|
| 1312 |
+
"""
|
| 1313 |
+
|
| 1314 |
+
|
| 1315 |
+
class Tool(BaseMetadata):
|
| 1316 |
+
"""Definition for a tool the client can call."""
|
| 1317 |
+
|
| 1318 |
+
description: str | None = None
|
| 1319 |
+
"""A human-readable description of the tool."""
|
| 1320 |
+
inputSchema: dict[str, Any]
|
| 1321 |
+
"""A JSON Schema object defining the expected parameters for the tool."""
|
| 1322 |
+
outputSchema: dict[str, Any] | None = None
|
| 1323 |
+
"""
|
| 1324 |
+
An optional JSON Schema object defining the structure of the tool's output
|
| 1325 |
+
returned in the structuredContent field of a CallToolResult.
|
| 1326 |
+
"""
|
| 1327 |
+
icons: list[Icon] | None = None
|
| 1328 |
+
"""An optional list of icons for this tool."""
|
| 1329 |
+
annotations: ToolAnnotations | None = None
|
| 1330 |
+
"""Optional additional tool information."""
|
| 1331 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1332 |
+
"""
|
| 1333 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1334 |
+
for notes on _meta usage.
|
| 1335 |
+
"""
|
| 1336 |
+
|
| 1337 |
+
execution: ToolExecution | None = None
|
| 1338 |
+
|
| 1339 |
+
model_config = ConfigDict(extra="allow")
|
| 1340 |
+
|
| 1341 |
+
|
| 1342 |
+
class ListToolsResult(PaginatedResult):
|
| 1343 |
+
"""The server's response to a tools/list request from the client."""
|
| 1344 |
+
|
| 1345 |
+
tools: list[Tool]
|
| 1346 |
+
|
| 1347 |
+
|
| 1348 |
+
class CallToolRequestParams(RequestParams):
|
| 1349 |
+
"""Parameters for calling a tool."""
|
| 1350 |
+
|
| 1351 |
+
name: str
|
| 1352 |
+
arguments: dict[str, Any] | None = None
|
| 1353 |
+
model_config = ConfigDict(extra="allow")
|
| 1354 |
+
|
| 1355 |
+
|
| 1356 |
+
class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]):
|
| 1357 |
+
"""Used by the client to invoke a tool provided by the server."""
|
| 1358 |
+
|
| 1359 |
+
method: Literal["tools/call"] = "tools/call"
|
| 1360 |
+
params: CallToolRequestParams
|
| 1361 |
+
|
| 1362 |
+
|
| 1363 |
+
class CallToolResult(Result):
|
| 1364 |
+
"""The server's response to a tool call."""
|
| 1365 |
+
|
| 1366 |
+
content: list[ContentBlock]
|
| 1367 |
+
structuredContent: dict[str, Any] | None = None
|
| 1368 |
+
"""An optional JSON object that represents the structured result of the tool call."""
|
| 1369 |
+
isError: bool = False
|
| 1370 |
+
|
| 1371 |
+
|
| 1372 |
+
class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]):
|
| 1373 |
+
"""
|
| 1374 |
+
An optional notification from the server to the client, informing it that the list
|
| 1375 |
+
of tools it offers has changed.
|
| 1376 |
+
"""
|
| 1377 |
+
|
| 1378 |
+
method: Literal["notifications/tools/list_changed"] = "notifications/tools/list_changed"
|
| 1379 |
+
params: NotificationParams | None = None
|
| 1380 |
+
|
| 1381 |
+
|
| 1382 |
+
LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]
|
| 1383 |
+
|
| 1384 |
+
|
| 1385 |
+
class SetLevelRequestParams(RequestParams):
|
| 1386 |
+
"""Parameters for setting the logging level."""
|
| 1387 |
+
|
| 1388 |
+
level: LoggingLevel
|
| 1389 |
+
"""The level of logging that the client wants to receive from the server."""
|
| 1390 |
+
model_config = ConfigDict(extra="allow")
|
| 1391 |
+
|
| 1392 |
+
|
| 1393 |
+
class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"]]):
|
| 1394 |
+
"""A request from the client to the server, to enable or adjust logging."""
|
| 1395 |
+
|
| 1396 |
+
method: Literal["logging/setLevel"] = "logging/setLevel"
|
| 1397 |
+
params: SetLevelRequestParams
|
| 1398 |
+
|
| 1399 |
+
|
| 1400 |
+
class LoggingMessageNotificationParams(NotificationParams):
|
| 1401 |
+
"""Parameters for logging message notifications."""
|
| 1402 |
+
|
| 1403 |
+
level: LoggingLevel
|
| 1404 |
+
"""The severity of this log message."""
|
| 1405 |
+
logger: str | None = None
|
| 1406 |
+
"""An optional name of the logger issuing this message."""
|
| 1407 |
+
data: Any
|
| 1408 |
+
"""
|
| 1409 |
+
The data to be logged, such as a string message or an object. Any JSON serializable
|
| 1410 |
+
type is allowed here.
|
| 1411 |
+
"""
|
| 1412 |
+
model_config = ConfigDict(extra="allow")
|
| 1413 |
+
|
| 1414 |
+
|
| 1415 |
+
class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]):
|
| 1416 |
+
"""Notification of a log message passed from server to client."""
|
| 1417 |
+
|
| 1418 |
+
method: Literal["notifications/message"] = "notifications/message"
|
| 1419 |
+
params: LoggingMessageNotificationParams
|
| 1420 |
+
|
| 1421 |
+
|
| 1422 |
+
IncludeContext = Literal["none", "thisServer", "allServers"]
|
| 1423 |
+
|
| 1424 |
+
|
| 1425 |
+
class ModelHint(BaseModel):
|
| 1426 |
+
"""Hints to use for model selection."""
|
| 1427 |
+
|
| 1428 |
+
name: str | None = None
|
| 1429 |
+
"""A hint for a model name."""
|
| 1430 |
+
|
| 1431 |
+
model_config = ConfigDict(extra="allow")
|
| 1432 |
+
|
| 1433 |
+
|
| 1434 |
+
class ModelPreferences(BaseModel):
|
| 1435 |
+
"""
|
| 1436 |
+
The server's preferences for model selection, requested by the client during
|
| 1437 |
+
sampling.
|
| 1438 |
+
|
| 1439 |
+
Because LLMs can vary along multiple dimensions, choosing the "best" model is
|
| 1440 |
+
rarely straightforward. Different models excel in different areas—some are
|
| 1441 |
+
faster but less capable, others are more capable but more expensive, and so
|
| 1442 |
+
on. This interface allows servers to express their priorities across multiple
|
| 1443 |
+
dimensions to help clients make an appropriate selection for their use case.
|
| 1444 |
+
|
| 1445 |
+
These preferences are always advisory. The client MAY ignore them. It is also
|
| 1446 |
+
up to the client to decide how to interpret these preferences and how to
|
| 1447 |
+
balance them against other considerations.
|
| 1448 |
+
"""
|
| 1449 |
+
|
| 1450 |
+
hints: list[ModelHint] | None = None
|
| 1451 |
+
"""
|
| 1452 |
+
Optional hints to use for model selection.
|
| 1453 |
+
|
| 1454 |
+
If multiple hints are specified, the client MUST evaluate them in order
|
| 1455 |
+
(such that the first match is taken).
|
| 1456 |
+
|
| 1457 |
+
The client SHOULD prioritize these hints over the numeric priorities, but
|
| 1458 |
+
MAY still use the priorities to select from ambiguous matches.
|
| 1459 |
+
"""
|
| 1460 |
+
|
| 1461 |
+
costPriority: float | None = None
|
| 1462 |
+
"""
|
| 1463 |
+
How much to prioritize cost when selecting a model. A value of 0 means cost
|
| 1464 |
+
is not important, while a value of 1 means cost is the most important
|
| 1465 |
+
factor.
|
| 1466 |
+
"""
|
| 1467 |
+
|
| 1468 |
+
speedPriority: float | None = None
|
| 1469 |
+
"""
|
| 1470 |
+
How much to prioritize sampling speed (latency) when selecting a model. A
|
| 1471 |
+
value of 0 means speed is not important, while a value of 1 means speed is
|
| 1472 |
+
the most important factor.
|
| 1473 |
+
"""
|
| 1474 |
+
|
| 1475 |
+
intelligencePriority: float | None = None
|
| 1476 |
+
"""
|
| 1477 |
+
How much to prioritize intelligence and capabilities when selecting a
|
| 1478 |
+
model. A value of 0 means intelligence is not important, while a value of 1
|
| 1479 |
+
means intelligence is the most important factor.
|
| 1480 |
+
"""
|
| 1481 |
+
|
| 1482 |
+
model_config = ConfigDict(extra="allow")
|
| 1483 |
+
|
| 1484 |
+
|
| 1485 |
+
class ToolChoice(BaseModel):
|
| 1486 |
+
"""
|
| 1487 |
+
Controls tool usage behavior during sampling.
|
| 1488 |
+
|
| 1489 |
+
Allows the server to specify whether and how the LLM should use tools
|
| 1490 |
+
in its response.
|
| 1491 |
+
"""
|
| 1492 |
+
|
| 1493 |
+
mode: Literal["auto", "required", "none"] | None = None
|
| 1494 |
+
"""
|
| 1495 |
+
Controls when tools are used:
|
| 1496 |
+
- "auto": Model decides whether to use tools (default)
|
| 1497 |
+
- "required": Model MUST use at least one tool before completing
|
| 1498 |
+
- "none": Model should not use tools
|
| 1499 |
+
"""
|
| 1500 |
+
|
| 1501 |
+
model_config = ConfigDict(extra="allow")
|
| 1502 |
+
|
| 1503 |
+
|
| 1504 |
+
class CreateMessageRequestParams(RequestParams):
|
| 1505 |
+
"""Parameters for creating a message."""
|
| 1506 |
+
|
| 1507 |
+
messages: list[SamplingMessage]
|
| 1508 |
+
modelPreferences: ModelPreferences | None = None
|
| 1509 |
+
"""
|
| 1510 |
+
The server's preferences for which model to select. The client MAY ignore
|
| 1511 |
+
these preferences.
|
| 1512 |
+
"""
|
| 1513 |
+
systemPrompt: str | None = None
|
| 1514 |
+
"""An optional system prompt the server wants to use for sampling."""
|
| 1515 |
+
includeContext: IncludeContext | None = None
|
| 1516 |
+
"""
|
| 1517 |
+
A request to include context from one or more MCP servers (including the caller), to
|
| 1518 |
+
be attached to the prompt.
|
| 1519 |
+
"""
|
| 1520 |
+
temperature: float | None = None
|
| 1521 |
+
maxTokens: int
|
| 1522 |
+
"""The maximum number of tokens to sample, as requested by the server."""
|
| 1523 |
+
stopSequences: list[str] | None = None
|
| 1524 |
+
metadata: dict[str, Any] | None = None
|
| 1525 |
+
"""Optional metadata to pass through to the LLM provider."""
|
| 1526 |
+
tools: list["Tool"] | None = None
|
| 1527 |
+
"""
|
| 1528 |
+
Tool definitions for the LLM to use during sampling.
|
| 1529 |
+
Requires clientCapabilities.sampling.tools to be present.
|
| 1530 |
+
"""
|
| 1531 |
+
toolChoice: ToolChoice | None = None
|
| 1532 |
+
"""
|
| 1533 |
+
Controls tool usage behavior.
|
| 1534 |
+
Requires clientCapabilities.sampling.tools and the tools parameter to be present.
|
| 1535 |
+
"""
|
| 1536 |
+
model_config = ConfigDict(extra="allow")
|
| 1537 |
+
|
| 1538 |
+
|
| 1539 |
+
class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]):
|
| 1540 |
+
"""A request from the server to sample an LLM via the client."""
|
| 1541 |
+
|
| 1542 |
+
method: Literal["sampling/createMessage"] = "sampling/createMessage"
|
| 1543 |
+
params: CreateMessageRequestParams
|
| 1544 |
+
|
| 1545 |
+
|
| 1546 |
+
StopReason = Literal["endTurn", "stopSequence", "maxTokens", "toolUse"] | str
|
| 1547 |
+
|
| 1548 |
+
|
| 1549 |
+
class CreateMessageResult(Result):
|
| 1550 |
+
"""The client's response to a sampling/create_message request from the server.
|
| 1551 |
+
|
| 1552 |
+
This is the backwards-compatible version that returns single content (no arrays).
|
| 1553 |
+
Used when the request does not include tools.
|
| 1554 |
+
"""
|
| 1555 |
+
|
| 1556 |
+
role: Role
|
| 1557 |
+
"""The role of the message sender (typically 'assistant' for LLM responses)."""
|
| 1558 |
+
content: SamplingContent
|
| 1559 |
+
"""Response content. Single content block (text, image, or audio)."""
|
| 1560 |
+
model: str
|
| 1561 |
+
"""The name of the model that generated the message."""
|
| 1562 |
+
stopReason: StopReason | None = None
|
| 1563 |
+
"""The reason why sampling stopped, if known."""
|
| 1564 |
+
|
| 1565 |
+
|
| 1566 |
+
class CreateMessageResultWithTools(Result):
|
| 1567 |
+
"""The client's response to a sampling/create_message request when tools were provided.
|
| 1568 |
+
|
| 1569 |
+
This version supports array content for tool use flows.
|
| 1570 |
+
"""
|
| 1571 |
+
|
| 1572 |
+
role: Role
|
| 1573 |
+
"""The role of the message sender (typically 'assistant' for LLM responses)."""
|
| 1574 |
+
content: SamplingMessageContentBlock | list[SamplingMessageContentBlock]
|
| 1575 |
+
"""
|
| 1576 |
+
Response content. May be a single content block or an array.
|
| 1577 |
+
May include ToolUseContent if stopReason is 'toolUse'.
|
| 1578 |
+
"""
|
| 1579 |
+
model: str
|
| 1580 |
+
"""The name of the model that generated the message."""
|
| 1581 |
+
stopReason: StopReason | None = None
|
| 1582 |
+
"""
|
| 1583 |
+
The reason why sampling stopped, if known.
|
| 1584 |
+
'toolUse' indicates the model wants to use a tool.
|
| 1585 |
+
"""
|
| 1586 |
+
|
| 1587 |
+
@property
|
| 1588 |
+
def content_as_list(self) -> list[SamplingMessageContentBlock]:
|
| 1589 |
+
"""Returns the content as a list of content blocks, regardless of whether
|
| 1590 |
+
it was originally a single block or a list."""
|
| 1591 |
+
return self.content if isinstance(self.content, list) else [self.content]
|
| 1592 |
+
|
| 1593 |
+
|
| 1594 |
+
class ResourceTemplateReference(BaseModel):
|
| 1595 |
+
"""A reference to a resource or resource template definition."""
|
| 1596 |
+
|
| 1597 |
+
type: Literal["ref/resource"]
|
| 1598 |
+
uri: str
|
| 1599 |
+
"""The URI or URI template of the resource."""
|
| 1600 |
+
model_config = ConfigDict(extra="allow")
|
| 1601 |
+
|
| 1602 |
+
|
| 1603 |
+
@deprecated("`ResourceReference` is deprecated, you should use `ResourceTemplateReference`.")
|
| 1604 |
+
class ResourceReference(ResourceTemplateReference):
|
| 1605 |
+
pass
|
| 1606 |
+
|
| 1607 |
+
|
| 1608 |
+
class PromptReference(BaseModel):
|
| 1609 |
+
"""Identifies a prompt."""
|
| 1610 |
+
|
| 1611 |
+
type: Literal["ref/prompt"]
|
| 1612 |
+
name: str
|
| 1613 |
+
"""The name of the prompt or prompt template"""
|
| 1614 |
+
model_config = ConfigDict(extra="allow")
|
| 1615 |
+
|
| 1616 |
+
|
| 1617 |
+
class CompletionArgument(BaseModel):
|
| 1618 |
+
"""The argument's information for completion requests."""
|
| 1619 |
+
|
| 1620 |
+
name: str
|
| 1621 |
+
"""The name of the argument"""
|
| 1622 |
+
value: str
|
| 1623 |
+
"""The value of the argument to use for completion matching."""
|
| 1624 |
+
model_config = ConfigDict(extra="allow")
|
| 1625 |
+
|
| 1626 |
+
|
| 1627 |
+
class CompletionContext(BaseModel):
|
| 1628 |
+
"""Additional, optional context for completions."""
|
| 1629 |
+
|
| 1630 |
+
arguments: dict[str, str] | None = None
|
| 1631 |
+
"""Previously-resolved variables in a URI template or prompt."""
|
| 1632 |
+
model_config = ConfigDict(extra="allow")
|
| 1633 |
+
|
| 1634 |
+
|
| 1635 |
+
class CompleteRequestParams(RequestParams):
|
| 1636 |
+
"""Parameters for completion requests."""
|
| 1637 |
+
|
| 1638 |
+
ref: ResourceTemplateReference | PromptReference
|
| 1639 |
+
argument: CompletionArgument
|
| 1640 |
+
context: CompletionContext | None = None
|
| 1641 |
+
"""Additional, optional context for completions"""
|
| 1642 |
+
model_config = ConfigDict(extra="allow")
|
| 1643 |
+
|
| 1644 |
+
|
| 1645 |
+
class CompleteRequest(Request[CompleteRequestParams, Literal["completion/complete"]]):
|
| 1646 |
+
"""A request from the client to the server, to ask for completion options."""
|
| 1647 |
+
|
| 1648 |
+
method: Literal["completion/complete"] = "completion/complete"
|
| 1649 |
+
params: CompleteRequestParams
|
| 1650 |
+
|
| 1651 |
+
|
| 1652 |
+
class Completion(BaseModel):
|
| 1653 |
+
"""Completion information."""
|
| 1654 |
+
|
| 1655 |
+
values: list[str]
|
| 1656 |
+
"""An array of completion values. Must not exceed 100 items."""
|
| 1657 |
+
total: int | None = None
|
| 1658 |
+
"""
|
| 1659 |
+
The total number of completion options available. This can exceed the number of
|
| 1660 |
+
values actually sent in the response.
|
| 1661 |
+
"""
|
| 1662 |
+
hasMore: bool | None = None
|
| 1663 |
+
"""
|
| 1664 |
+
Indicates whether there are additional completion options beyond those provided in
|
| 1665 |
+
the current response, even if the exact total is unknown.
|
| 1666 |
+
"""
|
| 1667 |
+
model_config = ConfigDict(extra="allow")
|
| 1668 |
+
|
| 1669 |
+
|
| 1670 |
+
class CompleteResult(Result):
|
| 1671 |
+
"""The server's response to a completion/complete request"""
|
| 1672 |
+
|
| 1673 |
+
completion: Completion
|
| 1674 |
+
|
| 1675 |
+
|
| 1676 |
+
class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]):
|
| 1677 |
+
"""
|
| 1678 |
+
Sent from the server to request a list of root URIs from the client. Roots allow
|
| 1679 |
+
servers to ask for specific directories or files to operate on. A common example
|
| 1680 |
+
for roots is providing a set of repositories or directories a server should operate
|
| 1681 |
+
on.
|
| 1682 |
+
|
| 1683 |
+
This request is typically used when the server needs to understand the file system
|
| 1684 |
+
structure or access specific locations that the client has permission to read from.
|
| 1685 |
+
"""
|
| 1686 |
+
|
| 1687 |
+
method: Literal["roots/list"] = "roots/list"
|
| 1688 |
+
params: RequestParams | None = None
|
| 1689 |
+
|
| 1690 |
+
|
| 1691 |
+
class Root(BaseModel):
|
| 1692 |
+
"""Represents a root directory or file that the server can operate on."""
|
| 1693 |
+
|
| 1694 |
+
uri: FileUrl
|
| 1695 |
+
"""
|
| 1696 |
+
The URI identifying the root. This *must* start with file:// for now.
|
| 1697 |
+
This restriction may be relaxed in future versions of the protocol to allow
|
| 1698 |
+
other URI schemes.
|
| 1699 |
+
"""
|
| 1700 |
+
name: str | None = None
|
| 1701 |
+
"""
|
| 1702 |
+
An optional name for the root. This can be used to provide a human-readable
|
| 1703 |
+
identifier for the root, which may be useful for display purposes or for
|
| 1704 |
+
referencing the root in other parts of the application.
|
| 1705 |
+
"""
|
| 1706 |
+
meta: dict[str, Any] | None = Field(alias="_meta", default=None)
|
| 1707 |
+
"""
|
| 1708 |
+
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
|
| 1709 |
+
for notes on _meta usage.
|
| 1710 |
+
"""
|
| 1711 |
+
model_config = ConfigDict(extra="allow")
|
| 1712 |
+
|
| 1713 |
+
|
| 1714 |
+
class ListRootsResult(Result):
|
| 1715 |
+
"""
|
| 1716 |
+
The client's response to a roots/list request from the server.
|
| 1717 |
+
This result contains an array of Root objects, each representing a root directory
|
| 1718 |
+
or file that the server can operate on.
|
| 1719 |
+
"""
|
| 1720 |
+
|
| 1721 |
+
roots: list[Root]
|
| 1722 |
+
|
| 1723 |
+
|
| 1724 |
+
class RootsListChangedNotification(
|
| 1725 |
+
Notification[NotificationParams | None, Literal["notifications/roots/list_changed"]]
|
| 1726 |
+
):
|
| 1727 |
+
"""
|
| 1728 |
+
A notification from the client to the server, informing it that the list of
|
| 1729 |
+
roots has changed.
|
| 1730 |
+
|
| 1731 |
+
This notification should be sent whenever the client adds, removes, or
|
| 1732 |
+
modifies any root. The server should then request an updated list of roots
|
| 1733 |
+
using the ListRootsRequest.
|
| 1734 |
+
"""
|
| 1735 |
+
|
| 1736 |
+
method: Literal["notifications/roots/list_changed"] = "notifications/roots/list_changed"
|
| 1737 |
+
params: NotificationParams | None = None
|
| 1738 |
+
|
| 1739 |
+
|
| 1740 |
+
class CancelledNotificationParams(NotificationParams):
|
| 1741 |
+
"""Parameters for cancellation notifications."""
|
| 1742 |
+
|
| 1743 |
+
requestId: RequestId | None = None
|
| 1744 |
+
"""
|
| 1745 |
+
The ID of the request to cancel.
|
| 1746 |
+
|
| 1747 |
+
This MUST correspond to the ID of a request previously issued in the same direction.
|
| 1748 |
+
This MUST be provided for cancelling non-task requests.
|
| 1749 |
+
This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead).
|
| 1750 |
+
"""
|
| 1751 |
+
reason: str | None = None
|
| 1752 |
+
"""An optional string describing the reason for the cancellation."""
|
| 1753 |
+
|
| 1754 |
+
model_config = ConfigDict(extra="allow")
|
| 1755 |
+
|
| 1756 |
+
|
| 1757 |
+
class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]):
|
| 1758 |
+
"""
|
| 1759 |
+
This notification can be sent by either side to indicate that it is canceling a
|
| 1760 |
+
previously-issued request.
|
| 1761 |
+
"""
|
| 1762 |
+
|
| 1763 |
+
method: Literal["notifications/cancelled"] = "notifications/cancelled"
|
| 1764 |
+
params: CancelledNotificationParams
|
| 1765 |
+
|
| 1766 |
+
|
| 1767 |
+
class ElicitCompleteNotificationParams(NotificationParams):
|
| 1768 |
+
"""Parameters for elicitation completion notifications."""
|
| 1769 |
+
|
| 1770 |
+
elicitationId: str
|
| 1771 |
+
"""The unique identifier of the elicitation that was completed."""
|
| 1772 |
+
|
| 1773 |
+
model_config = ConfigDict(extra="allow")
|
| 1774 |
+
|
| 1775 |
+
|
| 1776 |
+
class ElicitCompleteNotification(
|
| 1777 |
+
Notification[ElicitCompleteNotificationParams, Literal["notifications/elicitation/complete"]]
|
| 1778 |
+
):
|
| 1779 |
+
"""
|
| 1780 |
+
A notification from the server to the client, informing it that a URL mode
|
| 1781 |
+
elicitation has been completed.
|
| 1782 |
+
|
| 1783 |
+
Clients MAY use the notification to automatically retry requests that received a
|
| 1784 |
+
URLElicitationRequiredError, update the user interface, or otherwise continue
|
| 1785 |
+
an interaction. However, because delivery of the notification is not guaranteed,
|
| 1786 |
+
clients must not wait indefinitely for a notification from the server.
|
| 1787 |
+
"""
|
| 1788 |
+
|
| 1789 |
+
method: Literal["notifications/elicitation/complete"] = "notifications/elicitation/complete"
|
| 1790 |
+
params: ElicitCompleteNotificationParams
|
| 1791 |
+
|
| 1792 |
+
|
| 1793 |
+
ClientRequestType: TypeAlias = (
|
| 1794 |
+
PingRequest
|
| 1795 |
+
| InitializeRequest
|
| 1796 |
+
| CompleteRequest
|
| 1797 |
+
| SetLevelRequest
|
| 1798 |
+
| GetPromptRequest
|
| 1799 |
+
| ListPromptsRequest
|
| 1800 |
+
| ListResourcesRequest
|
| 1801 |
+
| ListResourceTemplatesRequest
|
| 1802 |
+
| ReadResourceRequest
|
| 1803 |
+
| SubscribeRequest
|
| 1804 |
+
| UnsubscribeRequest
|
| 1805 |
+
| CallToolRequest
|
| 1806 |
+
| ListToolsRequest
|
| 1807 |
+
| GetTaskRequest
|
| 1808 |
+
| GetTaskPayloadRequest
|
| 1809 |
+
| ListTasksRequest
|
| 1810 |
+
| CancelTaskRequest
|
| 1811 |
+
)
|
| 1812 |
+
|
| 1813 |
+
|
| 1814 |
+
class ClientRequest(RootModel[ClientRequestType]):
|
| 1815 |
+
pass
|
| 1816 |
+
|
| 1817 |
+
|
| 1818 |
+
ClientNotificationType: TypeAlias = (
|
| 1819 |
+
CancelledNotification
|
| 1820 |
+
| ProgressNotification
|
| 1821 |
+
| InitializedNotification
|
| 1822 |
+
| RootsListChangedNotification
|
| 1823 |
+
| TaskStatusNotification
|
| 1824 |
+
)
|
| 1825 |
+
|
| 1826 |
+
|
| 1827 |
+
class ClientNotification(RootModel[ClientNotificationType]):
|
| 1828 |
+
pass
|
| 1829 |
+
|
| 1830 |
+
|
| 1831 |
+
# Type for elicitation schema - a JSON Schema dict
|
| 1832 |
+
ElicitRequestedSchema: TypeAlias = dict[str, Any]
|
| 1833 |
+
"""Schema for elicitation requests."""
|
| 1834 |
+
|
| 1835 |
+
|
| 1836 |
+
class ElicitRequestFormParams(RequestParams):
|
| 1837 |
+
"""Parameters for form mode elicitation requests.
|
| 1838 |
+
|
| 1839 |
+
Form mode collects non-sensitive information from the user via an in-band form
|
| 1840 |
+
rendered by the client.
|
| 1841 |
+
"""
|
| 1842 |
+
|
| 1843 |
+
mode: Literal["form"] = "form"
|
| 1844 |
+
"""The elicitation mode (always "form" for this type)."""
|
| 1845 |
+
|
| 1846 |
+
message: str
|
| 1847 |
+
"""The message to present to the user describing what information is being requested."""
|
| 1848 |
+
|
| 1849 |
+
requestedSchema: ElicitRequestedSchema
|
| 1850 |
+
"""
|
| 1851 |
+
A restricted subset of JSON Schema defining the structure of expected response.
|
| 1852 |
+
Only top-level properties are allowed, without nesting.
|
| 1853 |
+
"""
|
| 1854 |
+
|
| 1855 |
+
model_config = ConfigDict(extra="allow")
|
| 1856 |
+
|
| 1857 |
+
|
| 1858 |
+
class ElicitRequestURLParams(RequestParams):
|
| 1859 |
+
"""Parameters for URL mode elicitation requests.
|
| 1860 |
+
|
| 1861 |
+
URL mode directs users to external URLs for sensitive out-of-band interactions
|
| 1862 |
+
like OAuth flows, credential collection, or payment processing.
|
| 1863 |
+
"""
|
| 1864 |
+
|
| 1865 |
+
mode: Literal["url"] = "url"
|
| 1866 |
+
"""The elicitation mode (always "url" for this type)."""
|
| 1867 |
+
|
| 1868 |
+
message: str
|
| 1869 |
+
"""The message to present to the user explaining why the interaction is needed."""
|
| 1870 |
+
|
| 1871 |
+
url: str
|
| 1872 |
+
"""The URL that the user should navigate to."""
|
| 1873 |
+
|
| 1874 |
+
elicitationId: str
|
| 1875 |
+
"""
|
| 1876 |
+
The ID of the elicitation, which must be unique within the context of the server.
|
| 1877 |
+
The client MUST treat this ID as an opaque value.
|
| 1878 |
+
"""
|
| 1879 |
+
|
| 1880 |
+
model_config = ConfigDict(extra="allow")
|
| 1881 |
+
|
| 1882 |
+
|
| 1883 |
+
# Union type for elicitation request parameters
|
| 1884 |
+
ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams
|
| 1885 |
+
"""Parameters for elicitation requests - either form or URL mode."""
|
| 1886 |
+
|
| 1887 |
+
|
| 1888 |
+
class ElicitRequest(Request[ElicitRequestParams, Literal["elicitation/create"]]):
|
| 1889 |
+
"""A request from the server to elicit information from the client."""
|
| 1890 |
+
|
| 1891 |
+
method: Literal["elicitation/create"] = "elicitation/create"
|
| 1892 |
+
params: ElicitRequestParams
|
| 1893 |
+
|
| 1894 |
+
|
| 1895 |
+
class ElicitResult(Result):
|
| 1896 |
+
"""The client's response to an elicitation request."""
|
| 1897 |
+
|
| 1898 |
+
action: Literal["accept", "decline", "cancel"]
|
| 1899 |
+
"""
|
| 1900 |
+
The user action in response to the elicitation.
|
| 1901 |
+
- "accept": User submitted the form/confirmed the action (or consented to URL navigation)
|
| 1902 |
+
- "decline": User explicitly declined the action
|
| 1903 |
+
- "cancel": User dismissed without making an explicit choice
|
| 1904 |
+
"""
|
| 1905 |
+
|
| 1906 |
+
content: dict[str, str | int | float | bool | list[str] | None] | None = None
|
| 1907 |
+
"""
|
| 1908 |
+
The submitted form data, only present when action is "accept" in form mode.
|
| 1909 |
+
Contains values matching the requested schema. Values can be strings, integers,
|
| 1910 |
+
booleans, or arrays of strings.
|
| 1911 |
+
For URL mode, this field is omitted.
|
| 1912 |
+
"""
|
| 1913 |
+
|
| 1914 |
+
|
| 1915 |
+
class ElicitationRequiredErrorData(BaseModel):
|
| 1916 |
+
"""Error data for URLElicitationRequiredError.
|
| 1917 |
+
|
| 1918 |
+
Servers return this when a request cannot be processed until one or more
|
| 1919 |
+
URL mode elicitations are completed.
|
| 1920 |
+
"""
|
| 1921 |
+
|
| 1922 |
+
elicitations: list[ElicitRequestURLParams]
|
| 1923 |
+
"""List of URL mode elicitations that must be completed."""
|
| 1924 |
+
|
| 1925 |
+
model_config = ConfigDict(extra="allow")
|
| 1926 |
+
|
| 1927 |
+
|
| 1928 |
+
ClientResultType: TypeAlias = (
|
| 1929 |
+
EmptyResult
|
| 1930 |
+
| CreateMessageResult
|
| 1931 |
+
| CreateMessageResultWithTools
|
| 1932 |
+
| ListRootsResult
|
| 1933 |
+
| ElicitResult
|
| 1934 |
+
| GetTaskResult
|
| 1935 |
+
| GetTaskPayloadResult
|
| 1936 |
+
| ListTasksResult
|
| 1937 |
+
| CancelTaskResult
|
| 1938 |
+
| CreateTaskResult
|
| 1939 |
+
)
|
| 1940 |
+
|
| 1941 |
+
|
| 1942 |
+
class ClientResult(RootModel[ClientResultType]):
|
| 1943 |
+
pass
|
| 1944 |
+
|
| 1945 |
+
|
| 1946 |
+
ServerRequestType: TypeAlias = (
|
| 1947 |
+
PingRequest
|
| 1948 |
+
| CreateMessageRequest
|
| 1949 |
+
| ListRootsRequest
|
| 1950 |
+
| ElicitRequest
|
| 1951 |
+
| GetTaskRequest
|
| 1952 |
+
| GetTaskPayloadRequest
|
| 1953 |
+
| ListTasksRequest
|
| 1954 |
+
| CancelTaskRequest
|
| 1955 |
+
)
|
| 1956 |
+
|
| 1957 |
+
|
| 1958 |
+
class ServerRequest(RootModel[ServerRequestType]):
|
| 1959 |
+
pass
|
| 1960 |
+
|
| 1961 |
+
|
| 1962 |
+
ServerNotificationType: TypeAlias = (
|
| 1963 |
+
CancelledNotification
|
| 1964 |
+
| ProgressNotification
|
| 1965 |
+
| LoggingMessageNotification
|
| 1966 |
+
| ResourceUpdatedNotification
|
| 1967 |
+
| ResourceListChangedNotification
|
| 1968 |
+
| ToolListChangedNotification
|
| 1969 |
+
| PromptListChangedNotification
|
| 1970 |
+
| ElicitCompleteNotification
|
| 1971 |
+
| TaskStatusNotification
|
| 1972 |
+
)
|
| 1973 |
+
|
| 1974 |
+
|
| 1975 |
+
class ServerNotification(RootModel[ServerNotificationType]):
|
| 1976 |
+
pass
|
| 1977 |
+
|
| 1978 |
+
|
| 1979 |
+
ServerResultType: TypeAlias = (
|
| 1980 |
+
EmptyResult
|
| 1981 |
+
| InitializeResult
|
| 1982 |
+
| CompleteResult
|
| 1983 |
+
| GetPromptResult
|
| 1984 |
+
| ListPromptsResult
|
| 1985 |
+
| ListResourcesResult
|
| 1986 |
+
| ListResourceTemplatesResult
|
| 1987 |
+
| ReadResourceResult
|
| 1988 |
+
| CallToolResult
|
| 1989 |
+
| ListToolsResult
|
| 1990 |
+
| GetTaskResult
|
| 1991 |
+
| GetTaskPayloadResult
|
| 1992 |
+
| ListTasksResult
|
| 1993 |
+
| CancelTaskResult
|
| 1994 |
+
| CreateTaskResult
|
| 1995 |
+
)
|
| 1996 |
+
|
| 1997 |
+
|
| 1998 |
+
class ServerResult(RootModel[ServerResultType]):
|
| 1999 |
+
pass
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2012, Andrew Dunham
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# https://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
# This only works if using a file system, other loaders not implemented.
|
| 16 |
+
|
| 17 |
+
import importlib.util
|
| 18 |
+
import sys
|
| 19 |
+
import warnings
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
for p in sys.path:
|
| 23 |
+
file_path = Path(p, "multipart.py")
|
| 24 |
+
try:
|
| 25 |
+
if file_path.is_file():
|
| 26 |
+
spec = importlib.util.spec_from_file_location("multipart", file_path)
|
| 27 |
+
assert spec is not None, f"{file_path} found but not loadable!"
|
| 28 |
+
module = importlib.util.module_from_spec(spec)
|
| 29 |
+
sys.modules["multipart"] = module
|
| 30 |
+
assert spec.loader is not None, f"{file_path} must be loadable!"
|
| 31 |
+
spec.loader.exec_module(module)
|
| 32 |
+
break
|
| 33 |
+
except PermissionError:
|
| 34 |
+
pass
|
| 35 |
+
else:
|
| 36 |
+
warnings.warn("Please use `import python_multipart` instead.", PendingDeprecationWarning, stacklevel=2)
|
| 37 |
+
from python_multipart import *
|
| 38 |
+
from python_multipart import __all__, __version__
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/decoders.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from python_multipart.decoders import *
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/exceptions.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from python_multipart.exceptions import *
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/multipart.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from python_multipart.multipart import *
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/METADATA
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: packaging
|
| 3 |
+
Version: 26.2
|
| 4 |
+
Summary: Core utilities for Python packages
|
| 5 |
+
Author-email: Donald Stufft <donald@stufft.io>
|
| 6 |
+
Requires-Python: >=3.8
|
| 7 |
+
Description-Content-Type: text/x-rst
|
| 8 |
+
License-Expression: Apache-2.0 OR BSD-2-Clause
|
| 9 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 10 |
+
Classifier: Intended Audience :: Developers
|
| 11 |
+
Classifier: Programming Language :: Python
|
| 12 |
+
Classifier: Programming Language :: Python :: 3
|
| 13 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 14 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 15 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 16 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 21 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 22 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 23 |
+
Classifier: Programming Language :: Python :: Free Threading :: 4 - Resilient
|
| 24 |
+
Classifier: Typing :: Typed
|
| 25 |
+
License-File: LICENSE
|
| 26 |
+
License-File: LICENSE.APACHE
|
| 27 |
+
License-File: LICENSE.BSD
|
| 28 |
+
Project-URL: Documentation, https://packaging.pypa.io/
|
| 29 |
+
Project-URL: Source, https://github.com/pypa/packaging
|
| 30 |
+
|
| 31 |
+
packaging
|
| 32 |
+
=========
|
| 33 |
+
|
| 34 |
+
.. start-intro
|
| 35 |
+
|
| 36 |
+
Reusable core utilities for various Python Packaging
|
| 37 |
+
`interoperability specifications <https://packaging.python.org/specifications/>`_.
|
| 38 |
+
|
| 39 |
+
This library provides utilities that implement the interoperability
|
| 40 |
+
specifications which have clearly one correct behaviour (eg: :pep:`440`)
|
| 41 |
+
or benefit greatly from having a single shared implementation (eg: :pep:`425`).
|
| 42 |
+
|
| 43 |
+
.. end-intro
|
| 44 |
+
|
| 45 |
+
The ``packaging`` project includes the following: version handling, specifiers,
|
| 46 |
+
markers, requirements, tags, metadata, lockfiles, utilities.
|
| 47 |
+
|
| 48 |
+
Documentation
|
| 49 |
+
-------------
|
| 50 |
+
|
| 51 |
+
The `documentation`_ provides information and the API for the following:
|
| 52 |
+
|
| 53 |
+
- Version Handling
|
| 54 |
+
- Specifiers
|
| 55 |
+
- Markers
|
| 56 |
+
- Licenses
|
| 57 |
+
- Requirements
|
| 58 |
+
- Metadata
|
| 59 |
+
- Tags
|
| 60 |
+
- Lockfiles (pylock)
|
| 61 |
+
- Direct URL helpers
|
| 62 |
+
- Dependency groups
|
| 63 |
+
- Errors
|
| 64 |
+
- Utilities
|
| 65 |
+
|
| 66 |
+
Installation
|
| 67 |
+
------------
|
| 68 |
+
|
| 69 |
+
Use ``pip`` to install these utilities::
|
| 70 |
+
|
| 71 |
+
pip install packaging
|
| 72 |
+
|
| 73 |
+
The ``packaging`` library uses calendar-based versioning (``YY.N``).
|
| 74 |
+
|
| 75 |
+
Discussion
|
| 76 |
+
----------
|
| 77 |
+
|
| 78 |
+
If you run into bugs, you can file them in our `issue tracker`_.
|
| 79 |
+
|
| 80 |
+
You can also join discussions on `GitHub Discussions`_ to ask questions or get involved.
|
| 81 |
+
|
| 82 |
+
.. _`documentation`: https://packaging.pypa.io/
|
| 83 |
+
.. _`issue tracker`: https://github.com/pypa/packaging/issues
|
| 84 |
+
.. _`GitHub Discussions`: https://github.com/pypa/packaging/discussions
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
Code of Conduct
|
| 88 |
+
---------------
|
| 89 |
+
|
| 90 |
+
Everyone interacting in the packaging project's codebases, issue trackers, chat
|
| 91 |
+
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
|
| 92 |
+
|
| 93 |
+
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
|
| 94 |
+
|
| 95 |
+
Contributing
|
| 96 |
+
------------
|
| 97 |
+
|
| 98 |
+
The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as
|
| 99 |
+
well as how to report a potential security issue. The documentation for this
|
| 100 |
+
project also covers information about `project development`_ and `security`_.
|
| 101 |
+
|
| 102 |
+
.. _`project development`: https://packaging.pypa.io/en/latest/development/
|
| 103 |
+
.. _`security`: https://packaging.pypa.io/en/latest/security/
|
| 104 |
+
|
| 105 |
+
Project History
|
| 106 |
+
---------------
|
| 107 |
+
|
| 108 |
+
Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for
|
| 109 |
+
recent changes and project history.
|
| 110 |
+
|
| 111 |
+
.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/
|
| 112 |
+
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/RECORD
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
packaging-26.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
packaging-26.2.dist-info/METADATA,sha256=T5y815M0FaR5P3dnyYoralEsgj_IHIczeBVwXyMOyr8,3543
|
| 3 |
+
packaging-26.2.dist-info/RECORD,,
|
| 4 |
+
packaging-26.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 5 |
+
packaging-26.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
| 6 |
+
packaging-26.2.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
| 7 |
+
packaging-26.2.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
| 8 |
+
packaging-26.2.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
| 9 |
+
packaging/__init__.py,sha256=QhMEdPu2XogrJzV3S0KWS6t7l0I9k8EeDRJl4fnw87s,494
|
| 10 |
+
packaging/_elffile.py,sha256=-sKkptYqzYw2-x3QByJa5mB4rfPWu1pxkZHRx1WAFCY,3211
|
| 11 |
+
packaging/_manylinux.py,sha256=Hf6nB0cOrayEs96-p3oIXAgGnFquv20DO5l-o2_Xnv0,9559
|
| 12 |
+
packaging/_musllinux.py,sha256=Z6swjH3MA7XS3qXnmMN7QPhqP3fnoYI0eQ18e9-HgAE,2707
|
| 13 |
+
packaging/_parser.py,sha256=Kf2nsDw4c54X82pY8ba4F02Bve6OygGMAjL-Begqcew,11698
|
| 14 |
+
packaging/_structures.py,sha256=60jRbF78p8z5MKnNd6cAprgOadCJHV0DlmUmRBqFZcs,1109
|
| 15 |
+
packaging/_tokenizer.py,sha256=tFU2Wr-ZZJdAbkXLEJo7qUQDJaIkfft9DqaifiEND7A,5391
|
| 16 |
+
packaging/dependency_groups.py,sha256=XZIAVFK9uHG4RCGprmJn3VInUWMesxha_kytJuMO9eY,10218
|
| 17 |
+
packaging/direct_url.py,sha256=eKmbDiPP1sLV4Mj_kCSZqqknrIyVO9Sr7JpF8KCjp4U,10917
|
| 18 |
+
packaging/errors.py,sha256=6hfEYXAf8v_IF65-lFadJOMIieBP2xIKtyEXjG1nGIs,2680
|
| 19 |
+
packaging/licenses/__init__.py,sha256=_Jx0XRiD_58palsWnyLrLuh59ZpGCPIPXLKdZo9OJvQ,7293
|
| 20 |
+
packaging/licenses/_spdx.py,sha256=WW7DXiyg68up_YND_wpRYlr1SHhiV4FfJLQffghhMxQ,51122
|
| 21 |
+
packaging/markers.py,sha256=8fDIUhAF6YMnCNB5FSiwh9pEIusiFzAF73J-0OB8bTk,17055
|
| 22 |
+
packaging/metadata.py,sha256=crAh0E3GVGVqPlu6EdRFsaG-Y6UYznTUqjuGKRGPv6c,38770
|
| 23 |
+
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 24 |
+
packaging/pylock.py,sha256=G_1gncTmDbRLY1jo4VDI9Uw-b5IErh_Q9V_BbVJTmD8,33890
|
| 25 |
+
packaging/requirements.py,sha256=dd1c9aa1gp5NI6btF6UFRQjPn1nxQXnE_T34yDDTEpc,4383
|
| 26 |
+
packaging/specifiers.py,sha256=Mfp8avQg0lVot17to9lVKBtZD1FsWBTItoGwFUZ3wtg,71514
|
| 27 |
+
packaging/tags.py,sha256=NQ1weo69_Sjte3xBZ1I_G63CIgCmaN0C24mz-z3hGYo,34224
|
| 28 |
+
packaging/utils.py,sha256=M7-JMKic2sP1YtV_8aW7eVGB-x3ADuKCiSrsVeCd2Uo,9848
|
| 29 |
+
packaging/version.py,sha256=Y1aTtxe3sn2xOMa5BdI85-AcHuybbanOVkEvvSRRC8I,38369
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/REQUESTED
ADDED
|
File without changes
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: flit 3.12.0
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This software is made available under the terms of *either* of the licenses
|
| 2 |
+
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
| 3 |
+
under the terms of *both* these licenses.
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE.APACHE
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE.BSD
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) Donald Stufft and individual contributors.
|
| 2 |
+
All rights reserved.
|
| 3 |
+
|
| 4 |
+
Redistribution and use in source and binary forms, with or without
|
| 5 |
+
modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
1. Redistributions of source code must retain the above copyright notice,
|
| 8 |
+
this list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
2. Redistributions in binary form must reproduce the above copyright
|
| 11 |
+
notice, this list of conditions and the following disclaimer in the
|
| 12 |
+
documentation and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 15 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 16 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 17 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 18 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 19 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 20 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 21 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 22 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 23 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
__title__ = "packaging"
|
| 6 |
+
__summary__ = "Core utilities for Python packages"
|
| 7 |
+
__uri__ = "https://github.com/pypa/packaging"
|
| 8 |
+
|
| 9 |
+
__version__ = "26.2"
|
| 10 |
+
|
| 11 |
+
__author__ = "Donald Stufft and individual contributors"
|
| 12 |
+
__email__ = "donald@stufft.io"
|
| 13 |
+
|
| 14 |
+
__license__ = "BSD-2-Clause or Apache-2.0"
|
| 15 |
+
__copyright__ = f"2014 {__author__}"
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_elffile.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ELF file parser.
|
| 3 |
+
|
| 4 |
+
This provides a class ``ELFFile`` that parses an ELF executable in a similar
|
| 5 |
+
interface to ``ZipFile``. Only the read interface is implemented.
|
| 6 |
+
|
| 7 |
+
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import enum
|
| 13 |
+
import os
|
| 14 |
+
import struct
|
| 15 |
+
from typing import IO
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ELFInvalid(ValueError):
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class EIClass(enum.IntEnum):
|
| 23 |
+
C32 = 1
|
| 24 |
+
C64 = 2
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class EIData(enum.IntEnum):
|
| 28 |
+
Lsb = 1
|
| 29 |
+
Msb = 2
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class EMachine(enum.IntEnum):
|
| 33 |
+
I386 = 3
|
| 34 |
+
S390 = 22
|
| 35 |
+
Arm = 40
|
| 36 |
+
X8664 = 62
|
| 37 |
+
AArc64 = 183
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class ELFFile:
|
| 41 |
+
"""
|
| 42 |
+
Representation of an ELF executable.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
def __init__(self, f: IO[bytes]) -> None:
|
| 46 |
+
self._f = f
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
ident = self._read("16B")
|
| 50 |
+
except struct.error as e:
|
| 51 |
+
raise ELFInvalid("unable to parse identification") from e
|
| 52 |
+
magic = bytes(ident[:4])
|
| 53 |
+
if magic != b"\x7fELF":
|
| 54 |
+
raise ELFInvalid(f"invalid magic: {magic!r}")
|
| 55 |
+
|
| 56 |
+
self.capacity = ident[4] # Format for program header (bitness).
|
| 57 |
+
self.encoding = ident[5] # Data structure encoding (endianness).
|
| 58 |
+
|
| 59 |
+
try:
|
| 60 |
+
# e_fmt: Format for program header.
|
| 61 |
+
# p_fmt: Format for section header.
|
| 62 |
+
# p_idx: Indexes to find p_type, p_offset, and p_filesz.
|
| 63 |
+
e_fmt, self._p_fmt, self._p_idx = {
|
| 64 |
+
(1, 1): ("<HHIIIIIHHH", "<IIIIIIII", (0, 1, 4)), # 32-bit LSB.
|
| 65 |
+
(1, 2): (">HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB.
|
| 66 |
+
(2, 1): ("<HHIQQQIHHH", "<IIQQQQQQ", (0, 2, 5)), # 64-bit LSB.
|
| 67 |
+
(2, 2): (">HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB.
|
| 68 |
+
}[(self.capacity, self.encoding)]
|
| 69 |
+
except KeyError as e:
|
| 70 |
+
raise ELFInvalid(
|
| 71 |
+
f"unrecognized capacity ({self.capacity}) or encoding ({self.encoding})"
|
| 72 |
+
) from e
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
(
|
| 76 |
+
_,
|
| 77 |
+
self.machine, # Architecture type.
|
| 78 |
+
_,
|
| 79 |
+
_,
|
| 80 |
+
self._e_phoff, # Offset of program header.
|
| 81 |
+
_,
|
| 82 |
+
self.flags, # Processor-specific flags.
|
| 83 |
+
_,
|
| 84 |
+
self._e_phentsize, # Size of section.
|
| 85 |
+
self._e_phnum, # Number of sections.
|
| 86 |
+
) = self._read(e_fmt)
|
| 87 |
+
except struct.error as e:
|
| 88 |
+
raise ELFInvalid("unable to parse machine and section information") from e
|
| 89 |
+
|
| 90 |
+
def _read(self, fmt: str) -> tuple[int, ...]:
|
| 91 |
+
return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
|
| 92 |
+
|
| 93 |
+
@property
|
| 94 |
+
def interpreter(self) -> str | None:
|
| 95 |
+
"""
|
| 96 |
+
The path recorded in the ``PT_INTERP`` section header.
|
| 97 |
+
"""
|
| 98 |
+
for index in range(self._e_phnum):
|
| 99 |
+
self._f.seek(self._e_phoff + self._e_phentsize * index)
|
| 100 |
+
try:
|
| 101 |
+
data = self._read(self._p_fmt)
|
| 102 |
+
except struct.error:
|
| 103 |
+
continue
|
| 104 |
+
if data[self._p_idx[0]] != 3: # Not PT_INTERP.
|
| 105 |
+
continue
|
| 106 |
+
self._f.seek(data[self._p_idx[1]])
|
| 107 |
+
return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
|
| 108 |
+
return None
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_manylinux.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import collections
|
| 4 |
+
import contextlib
|
| 5 |
+
import functools
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import sys
|
| 9 |
+
import warnings
|
| 10 |
+
from typing import Generator, Iterator, NamedTuple, Sequence
|
| 11 |
+
|
| 12 |
+
from ._elffile import EIClass, EIData, ELFFile, EMachine
|
| 13 |
+
|
| 14 |
+
EF_ARM_ABIMASK = 0xFF000000
|
| 15 |
+
EF_ARM_ABI_VER5 = 0x05000000
|
| 16 |
+
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
| 17 |
+
|
| 18 |
+
_ALLOWED_ARCHS = {
|
| 19 |
+
"x86_64",
|
| 20 |
+
"aarch64",
|
| 21 |
+
"ppc64",
|
| 22 |
+
"ppc64le",
|
| 23 |
+
"s390x",
|
| 24 |
+
"loongarch64",
|
| 25 |
+
"riscv64",
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
| 30 |
+
# as the type for `path` until then.
|
| 31 |
+
@contextlib.contextmanager
|
| 32 |
+
def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
|
| 33 |
+
try:
|
| 34 |
+
with open(path, "rb") as f:
|
| 35 |
+
yield ELFFile(f)
|
| 36 |
+
except (OSError, TypeError, ValueError):
|
| 37 |
+
yield None
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _is_linux_armhf(executable: str) -> bool:
|
| 41 |
+
# hard-float ABI can be detected from the ELF header of the running
|
| 42 |
+
# process
|
| 43 |
+
# https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
|
| 44 |
+
with _parse_elf(executable) as f:
|
| 45 |
+
return (
|
| 46 |
+
f is not None
|
| 47 |
+
and f.capacity == EIClass.C32
|
| 48 |
+
and f.encoding == EIData.Lsb
|
| 49 |
+
and f.machine == EMachine.Arm
|
| 50 |
+
and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
|
| 51 |
+
and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _is_linux_i686(executable: str) -> bool:
|
| 56 |
+
with _parse_elf(executable) as f:
|
| 57 |
+
return (
|
| 58 |
+
f is not None
|
| 59 |
+
and f.capacity == EIClass.C32
|
| 60 |
+
and f.encoding == EIData.Lsb
|
| 61 |
+
and f.machine == EMachine.I386
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
|
| 66 |
+
if "armv7l" in archs:
|
| 67 |
+
return _is_linux_armhf(executable)
|
| 68 |
+
if "i686" in archs:
|
| 69 |
+
return _is_linux_i686(executable)
|
| 70 |
+
return any(arch in _ALLOWED_ARCHS for arch in archs)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# If glibc ever changes its major version, we need to know what the last
|
| 74 |
+
# minor version was, so we can build the complete list of all versions.
|
| 75 |
+
# For now, guess what the highest minor version might be, assume it will
|
| 76 |
+
# be 50 for testing. Once this actually happens, update the dictionary
|
| 77 |
+
# with the actual value.
|
| 78 |
+
_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class _GLibCVersion(NamedTuple):
|
| 82 |
+
major: int
|
| 83 |
+
minor: int
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _glibc_version_string_confstr() -> str | None:
|
| 87 |
+
"""
|
| 88 |
+
Primary implementation of glibc_version_string using os.confstr.
|
| 89 |
+
"""
|
| 90 |
+
# os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
|
| 91 |
+
# to be broken or missing. This strategy is used in the standard library
|
| 92 |
+
# platform module.
|
| 93 |
+
# https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
|
| 94 |
+
try:
|
| 95 |
+
# Should be a string like "glibc 2.17".
|
| 96 |
+
version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
|
| 97 |
+
assert version_string is not None
|
| 98 |
+
_, version = version_string.rsplit()
|
| 99 |
+
except (AssertionError, AttributeError, OSError, ValueError):
|
| 100 |
+
# os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
|
| 101 |
+
return None
|
| 102 |
+
return version
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def _glibc_version_string_ctypes() -> str | None:
|
| 106 |
+
"""
|
| 107 |
+
Fallback implementation of glibc_version_string using ctypes.
|
| 108 |
+
"""
|
| 109 |
+
try:
|
| 110 |
+
import ctypes # noqa: PLC0415
|
| 111 |
+
except ImportError:
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
# ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
|
| 115 |
+
# manpage says, "If filename is NULL, then the returned handle is for the
|
| 116 |
+
# main program". This way we can let the linker do the work to figure out
|
| 117 |
+
# which libc our process is actually using.
|
| 118 |
+
#
|
| 119 |
+
# We must also handle the special case where the executable is not a
|
| 120 |
+
# dynamically linked executable. This can occur when using musl libc,
|
| 121 |
+
# for example. In this situation, dlopen() will error, leading to an
|
| 122 |
+
# OSError. Interestingly, at least in the case of musl, there is no
|
| 123 |
+
# errno set on the OSError. The single string argument used to construct
|
| 124 |
+
# OSError comes from libc itself and is therefore not portable to
|
| 125 |
+
# hard code here. In any case, failure to call dlopen() means we
|
| 126 |
+
# can proceed, so we bail on our attempt.
|
| 127 |
+
try:
|
| 128 |
+
process_namespace = ctypes.CDLL(None)
|
| 129 |
+
except OSError:
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
try:
|
| 133 |
+
gnu_get_libc_version = process_namespace.gnu_get_libc_version
|
| 134 |
+
except AttributeError:
|
| 135 |
+
# Symbol doesn't exist -> therefore, we are not linked to
|
| 136 |
+
# glibc.
|
| 137 |
+
return None
|
| 138 |
+
|
| 139 |
+
# Call gnu_get_libc_version, which returns a string like "2.5"
|
| 140 |
+
gnu_get_libc_version.restype = ctypes.c_char_p
|
| 141 |
+
version_str: str = gnu_get_libc_version()
|
| 142 |
+
# py2 / py3 compatibility:
|
| 143 |
+
if not isinstance(version_str, str):
|
| 144 |
+
version_str = version_str.decode("ascii")
|
| 145 |
+
|
| 146 |
+
return version_str
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def _glibc_version_string() -> str | None:
|
| 150 |
+
"""Returns glibc version string, or None if not using glibc."""
|
| 151 |
+
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _parse_glibc_version(version_str: str) -> _GLibCVersion:
|
| 155 |
+
"""Parse glibc version.
|
| 156 |
+
|
| 157 |
+
We use a regexp instead of str.split because we want to discard any
|
| 158 |
+
random junk that might come after the minor version -- this might happen
|
| 159 |
+
in patched/forked versions of glibc (e.g. Linaro's version of glibc
|
| 160 |
+
uses version strings like "2.20-2014.11"). See gh-3588.
|
| 161 |
+
"""
|
| 162 |
+
m = re.match(r"(?P<major>[0-9]+)\.(?P<minor>[0-9]+)", version_str)
|
| 163 |
+
if not m:
|
| 164 |
+
warnings.warn(
|
| 165 |
+
f"Expected glibc version with 2 components major.minor, got: {version_str}",
|
| 166 |
+
RuntimeWarning,
|
| 167 |
+
stacklevel=2,
|
| 168 |
+
)
|
| 169 |
+
return _GLibCVersion(-1, -1)
|
| 170 |
+
return _GLibCVersion(int(m.group("major")), int(m.group("minor")))
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
@functools.lru_cache
|
| 174 |
+
def _get_glibc_version() -> _GLibCVersion:
|
| 175 |
+
version_str = _glibc_version_string()
|
| 176 |
+
if version_str is None:
|
| 177 |
+
return _GLibCVersion(-1, -1)
|
| 178 |
+
return _parse_glibc_version(version_str)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# From PEP 513, PEP 600
|
| 182 |
+
def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
|
| 183 |
+
sys_glibc = _get_glibc_version()
|
| 184 |
+
if sys_glibc < version:
|
| 185 |
+
return False
|
| 186 |
+
# Check for presence of _manylinux module.
|
| 187 |
+
try:
|
| 188 |
+
import _manylinux # noqa: PLC0415
|
| 189 |
+
except ImportError:
|
| 190 |
+
return True
|
| 191 |
+
if hasattr(_manylinux, "manylinux_compatible"):
|
| 192 |
+
result = _manylinux.manylinux_compatible(version[0], version[1], arch)
|
| 193 |
+
if result is not None:
|
| 194 |
+
return bool(result)
|
| 195 |
+
return True
|
| 196 |
+
if version == _GLibCVersion(2, 5) and hasattr(_manylinux, "manylinux1_compatible"):
|
| 197 |
+
return bool(_manylinux.manylinux1_compatible)
|
| 198 |
+
if version == _GLibCVersion(2, 12) and hasattr(
|
| 199 |
+
_manylinux, "manylinux2010_compatible"
|
| 200 |
+
):
|
| 201 |
+
return bool(_manylinux.manylinux2010_compatible)
|
| 202 |
+
if version == _GLibCVersion(2, 17) and hasattr(
|
| 203 |
+
_manylinux, "manylinux2014_compatible"
|
| 204 |
+
):
|
| 205 |
+
return bool(_manylinux.manylinux2014_compatible)
|
| 206 |
+
return True
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
_LEGACY_MANYLINUX_MAP: dict[_GLibCVersion, str] = {
|
| 210 |
+
# CentOS 7 w/ glibc 2.17 (PEP 599)
|
| 211 |
+
_GLibCVersion(2, 17): "manylinux2014",
|
| 212 |
+
# CentOS 6 w/ glibc 2.12 (PEP 571)
|
| 213 |
+
_GLibCVersion(2, 12): "manylinux2010",
|
| 214 |
+
# CentOS 5 w/ glibc 2.5 (PEP 513)
|
| 215 |
+
_GLibCVersion(2, 5): "manylinux1",
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
| 220 |
+
"""Generate manylinux tags compatible to the current platform.
|
| 221 |
+
|
| 222 |
+
:param archs: Sequence of compatible architectures.
|
| 223 |
+
The first one shall be the closest to the actual architecture and be the part of
|
| 224 |
+
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
| 225 |
+
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
| 226 |
+
be manylinux-compatible.
|
| 227 |
+
|
| 228 |
+
:returns: An iterator of compatible manylinux tags.
|
| 229 |
+
"""
|
| 230 |
+
if not _have_compatible_abi(sys.executable, archs):
|
| 231 |
+
return
|
| 232 |
+
# Oldest glibc to be supported regardless of architecture is (2, 17).
|
| 233 |
+
too_old_glibc2 = _GLibCVersion(2, 16)
|
| 234 |
+
if set(archs) & {"x86_64", "i686"}:
|
| 235 |
+
# On x86/i686 also oldest glibc to be supported is (2, 5).
|
| 236 |
+
too_old_glibc2 = _GLibCVersion(2, 4)
|
| 237 |
+
current_glibc = _GLibCVersion(*_get_glibc_version())
|
| 238 |
+
glibc_max_list = [current_glibc]
|
| 239 |
+
# We can assume compatibility across glibc major versions.
|
| 240 |
+
# https://sourceware.org/bugzilla/show_bug.cgi?id=24636
|
| 241 |
+
#
|
| 242 |
+
# Build a list of maximum glibc versions so that we can
|
| 243 |
+
# output the canonical list of all glibc from current_glibc
|
| 244 |
+
# down to too_old_glibc2, including all intermediary versions.
|
| 245 |
+
for glibc_major in range(current_glibc.major - 1, 1, -1):
|
| 246 |
+
glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
|
| 247 |
+
glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
|
| 248 |
+
for arch in archs:
|
| 249 |
+
for glibc_max in glibc_max_list:
|
| 250 |
+
if glibc_max.major == too_old_glibc2.major:
|
| 251 |
+
min_minor = too_old_glibc2.minor
|
| 252 |
+
else:
|
| 253 |
+
# For other glibc major versions oldest supported is (x, 0).
|
| 254 |
+
min_minor = -1
|
| 255 |
+
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
| 256 |
+
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
| 257 |
+
if _is_compatible(arch, glibc_version):
|
| 258 |
+
yield "manylinux_{}_{}_{}".format(*glibc_version, arch)
|
| 259 |
+
|
| 260 |
+
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
| 261 |
+
if legacy_tag := _LEGACY_MANYLINUX_MAP.get(glibc_version):
|
| 262 |
+
yield f"{legacy_tag}_{arch}"
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_musllinux.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PEP 656 support.
|
| 2 |
+
|
| 3 |
+
This module implements logic to detect if the currently running Python is
|
| 4 |
+
linked against musl, and what musl version is used.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import functools
|
| 10 |
+
import re
|
| 11 |
+
import subprocess
|
| 12 |
+
import sys
|
| 13 |
+
from typing import Iterator, NamedTuple, Sequence
|
| 14 |
+
|
| 15 |
+
from ._elffile import ELFFile
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class _MuslVersion(NamedTuple):
|
| 19 |
+
major: int
|
| 20 |
+
minor: int
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _parse_musl_version(output: str) -> _MuslVersion | None:
|
| 24 |
+
lines = [n for n in (n.strip() for n in output.splitlines()) if n]
|
| 25 |
+
if len(lines) < 2 or lines[0][:4] != "musl":
|
| 26 |
+
return None
|
| 27 |
+
m = re.match(r"Version (\d+)\.(\d+)", lines[1])
|
| 28 |
+
if not m:
|
| 29 |
+
return None
|
| 30 |
+
return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@functools.lru_cache
|
| 34 |
+
def _get_musl_version(executable: str) -> _MuslVersion | None:
|
| 35 |
+
"""Detect currently-running musl runtime version.
|
| 36 |
+
|
| 37 |
+
This is done by checking the specified executable's dynamic linking
|
| 38 |
+
information, and invoking the loader to parse its output for a version
|
| 39 |
+
string. If the loader is musl, the output would be something like::
|
| 40 |
+
|
| 41 |
+
musl libc (x86_64)
|
| 42 |
+
Version 1.2.2
|
| 43 |
+
Dynamic Program Loader
|
| 44 |
+
"""
|
| 45 |
+
try:
|
| 46 |
+
with open(executable, "rb") as f:
|
| 47 |
+
ld = ELFFile(f).interpreter
|
| 48 |
+
except (OSError, TypeError, ValueError):
|
| 49 |
+
return None
|
| 50 |
+
if ld is None or "musl" not in ld:
|
| 51 |
+
return None
|
| 52 |
+
proc = subprocess.run([ld], check=False, stderr=subprocess.PIPE, text=True)
|
| 53 |
+
return _parse_musl_version(proc.stderr)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
| 57 |
+
"""Generate musllinux tags compatible to the current platform.
|
| 58 |
+
|
| 59 |
+
:param archs: Sequence of compatible architectures.
|
| 60 |
+
The first one shall be the closest to the actual architecture and be the part of
|
| 61 |
+
platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
|
| 62 |
+
The ``linux_`` prefix is assumed as a prerequisite for the current platform to
|
| 63 |
+
be musllinux-compatible.
|
| 64 |
+
|
| 65 |
+
:returns: An iterator of compatible musllinux tags.
|
| 66 |
+
"""
|
| 67 |
+
sys_musl = _get_musl_version(sys.executable)
|
| 68 |
+
if sys_musl is None: # Python not dynamically linked against musl.
|
| 69 |
+
return
|
| 70 |
+
for arch in archs:
|
| 71 |
+
for minor in range(sys_musl.minor, -1, -1):
|
| 72 |
+
yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__": # pragma: no cover
|
| 76 |
+
import sysconfig
|
| 77 |
+
|
| 78 |
+
plat = sysconfig.get_platform()
|
| 79 |
+
assert plat.startswith("linux-"), "not linux"
|
| 80 |
+
|
| 81 |
+
print("plat:", plat)
|
| 82 |
+
print("musl:", _get_musl_version(sys.executable))
|
| 83 |
+
print("tags:", end=" ")
|
| 84 |
+
for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
|
| 85 |
+
print(t, end="\n ")
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_parser.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Handwritten parser of dependency specifiers.
|
| 2 |
+
|
| 3 |
+
The docstring for each __parse_* function contains EBNF-inspired grammar representing
|
| 4 |
+
the implementation.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import ast
|
| 10 |
+
from typing import List, Literal, NamedTuple, Sequence, Tuple, Union
|
| 11 |
+
|
| 12 |
+
from ._tokenizer import DEFAULT_RULES, Tokenizer
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Node:
|
| 16 |
+
__slots__ = ("value",)
|
| 17 |
+
|
| 18 |
+
def __init__(self, value: str) -> None:
|
| 19 |
+
self.value = value
|
| 20 |
+
|
| 21 |
+
def __str__(self) -> str:
|
| 22 |
+
return self.value
|
| 23 |
+
|
| 24 |
+
def __repr__(self) -> str:
|
| 25 |
+
return f"<{self.__class__.__name__}({self.value!r})>"
|
| 26 |
+
|
| 27 |
+
def serialize(self) -> str:
|
| 28 |
+
raise NotImplementedError
|
| 29 |
+
|
| 30 |
+
def __getstate__(self) -> str:
|
| 31 |
+
# Return just the value string for compactness and stability.
|
| 32 |
+
return self.value
|
| 33 |
+
|
| 34 |
+
def _restore_value(self, value: object) -> None:
|
| 35 |
+
if not isinstance(value, str):
|
| 36 |
+
raise TypeError(
|
| 37 |
+
f"Cannot restore {self.__class__.__name__} value from {value!r}"
|
| 38 |
+
)
|
| 39 |
+
self.value = value
|
| 40 |
+
|
| 41 |
+
def __setstate__(self, state: object) -> None:
|
| 42 |
+
if isinstance(state, str):
|
| 43 |
+
# New format (26.2+): just the value string.
|
| 44 |
+
self._restore_value(state)
|
| 45 |
+
return
|
| 46 |
+
if isinstance(state, tuple) and len(state) == 2:
|
| 47 |
+
# Old format (packaging <= 26.0, __slots__): (None, {slot: value}).
|
| 48 |
+
_, slot_dict = state
|
| 49 |
+
if isinstance(slot_dict, dict) and "value" in slot_dict:
|
| 50 |
+
self._restore_value(slot_dict["value"])
|
| 51 |
+
return
|
| 52 |
+
if isinstance(state, dict) and "value" in state:
|
| 53 |
+
# Old format (packaging <= 25.0, no __slots__): plain __dict__.
|
| 54 |
+
self._restore_value(state["value"])
|
| 55 |
+
return
|
| 56 |
+
raise TypeError(f"Cannot restore {self.__class__.__name__} from {state!r}")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class Variable(Node):
|
| 60 |
+
__slots__ = ()
|
| 61 |
+
|
| 62 |
+
def serialize(self) -> str:
|
| 63 |
+
return str(self)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class Value(Node):
|
| 67 |
+
__slots__ = ()
|
| 68 |
+
|
| 69 |
+
def serialize(self) -> str:
|
| 70 |
+
return f'"{self}"'
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class Op(Node):
|
| 74 |
+
__slots__ = ()
|
| 75 |
+
|
| 76 |
+
def serialize(self) -> str:
|
| 77 |
+
return str(self)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
MarkerLogical = Literal["and", "or"]
|
| 81 |
+
MarkerVar = Union[Variable, Value]
|
| 82 |
+
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
|
| 83 |
+
MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
|
| 84 |
+
MarkerList = List[Union["MarkerList", MarkerAtom, MarkerLogical]]
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class ParsedRequirement(NamedTuple):
|
| 88 |
+
name: str
|
| 89 |
+
url: str
|
| 90 |
+
extras: list[str]
|
| 91 |
+
specifier: str
|
| 92 |
+
marker: MarkerList | None
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# --------------------------------------------------------------------------------------
|
| 96 |
+
# Recursive descent parser for dependency specifier
|
| 97 |
+
# --------------------------------------------------------------------------------------
|
| 98 |
+
def parse_requirement(source: str) -> ParsedRequirement:
|
| 99 |
+
return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
|
| 103 |
+
"""
|
| 104 |
+
requirement = WS? IDENTIFIER WS? extras WS? requirement_details
|
| 105 |
+
"""
|
| 106 |
+
tokenizer.consume("WS")
|
| 107 |
+
|
| 108 |
+
name_token = tokenizer.expect(
|
| 109 |
+
"IDENTIFIER", expected="package name at the start of dependency specifier"
|
| 110 |
+
)
|
| 111 |
+
name = name_token.text
|
| 112 |
+
tokenizer.consume("WS")
|
| 113 |
+
|
| 114 |
+
extras = _parse_extras(tokenizer)
|
| 115 |
+
tokenizer.consume("WS")
|
| 116 |
+
|
| 117 |
+
url, specifier, marker = _parse_requirement_details(tokenizer)
|
| 118 |
+
tokenizer.expect("END", expected="end of dependency specifier")
|
| 119 |
+
|
| 120 |
+
return ParsedRequirement(name, url, extras, specifier, marker)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _parse_requirement_details(
|
| 124 |
+
tokenizer: Tokenizer,
|
| 125 |
+
) -> tuple[str, str, MarkerList | None]:
|
| 126 |
+
"""
|
| 127 |
+
requirement_details = AT URL (WS requirement_marker?)?
|
| 128 |
+
| specifier WS? (requirement_marker)?
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
specifier = ""
|
| 132 |
+
url = ""
|
| 133 |
+
marker = None
|
| 134 |
+
|
| 135 |
+
if tokenizer.check("AT"):
|
| 136 |
+
tokenizer.read()
|
| 137 |
+
tokenizer.consume("WS")
|
| 138 |
+
|
| 139 |
+
url_start = tokenizer.position
|
| 140 |
+
url = tokenizer.expect("URL", expected="URL after @").text
|
| 141 |
+
if tokenizer.check("END", peek=True):
|
| 142 |
+
return (url, specifier, marker)
|
| 143 |
+
|
| 144 |
+
tokenizer.expect("WS", expected="whitespace after URL")
|
| 145 |
+
|
| 146 |
+
# The input might end after whitespace.
|
| 147 |
+
if tokenizer.check("END", peek=True):
|
| 148 |
+
return (url, specifier, marker)
|
| 149 |
+
|
| 150 |
+
marker = _parse_requirement_marker(
|
| 151 |
+
tokenizer,
|
| 152 |
+
span_start=url_start,
|
| 153 |
+
expected="semicolon (after URL and whitespace)",
|
| 154 |
+
)
|
| 155 |
+
else:
|
| 156 |
+
specifier_start = tokenizer.position
|
| 157 |
+
specifier = _parse_specifier(tokenizer)
|
| 158 |
+
tokenizer.consume("WS")
|
| 159 |
+
|
| 160 |
+
if tokenizer.check("END", peek=True):
|
| 161 |
+
return (url, specifier, marker)
|
| 162 |
+
|
| 163 |
+
marker = _parse_requirement_marker(
|
| 164 |
+
tokenizer,
|
| 165 |
+
span_start=specifier_start,
|
| 166 |
+
expected=(
|
| 167 |
+
"comma (within version specifier), semicolon (after version specifier)"
|
| 168 |
+
if specifier
|
| 169 |
+
else "semicolon (after name with no version specifier)"
|
| 170 |
+
),
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
return (url, specifier, marker)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _parse_requirement_marker(
|
| 177 |
+
tokenizer: Tokenizer, *, span_start: int, expected: str
|
| 178 |
+
) -> MarkerList:
|
| 179 |
+
"""
|
| 180 |
+
requirement_marker = SEMICOLON marker WS?
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
if not tokenizer.check("SEMICOLON"):
|
| 184 |
+
tokenizer.raise_syntax_error(
|
| 185 |
+
f"Expected {expected} or end",
|
| 186 |
+
span_start=span_start,
|
| 187 |
+
span_end=None,
|
| 188 |
+
)
|
| 189 |
+
tokenizer.read()
|
| 190 |
+
|
| 191 |
+
marker = _parse_marker(tokenizer)
|
| 192 |
+
tokenizer.consume("WS")
|
| 193 |
+
|
| 194 |
+
return marker
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _parse_extras(tokenizer: Tokenizer) -> list[str]:
|
| 198 |
+
"""
|
| 199 |
+
extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
|
| 200 |
+
"""
|
| 201 |
+
if not tokenizer.check("LEFT_BRACKET", peek=True):
|
| 202 |
+
return []
|
| 203 |
+
|
| 204 |
+
with tokenizer.enclosing_tokens(
|
| 205 |
+
"LEFT_BRACKET",
|
| 206 |
+
"RIGHT_BRACKET",
|
| 207 |
+
around="extras",
|
| 208 |
+
):
|
| 209 |
+
tokenizer.consume("WS")
|
| 210 |
+
extras = _parse_extras_list(tokenizer)
|
| 211 |
+
tokenizer.consume("WS")
|
| 212 |
+
|
| 213 |
+
return extras
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _parse_extras_list(tokenizer: Tokenizer) -> list[str]:
|
| 217 |
+
"""
|
| 218 |
+
extras_list = identifier (wsp* ',' wsp* identifier)*
|
| 219 |
+
"""
|
| 220 |
+
extras: list[str] = []
|
| 221 |
+
|
| 222 |
+
if not tokenizer.check("IDENTIFIER"):
|
| 223 |
+
return extras
|
| 224 |
+
|
| 225 |
+
extras.append(tokenizer.read().text)
|
| 226 |
+
|
| 227 |
+
while True:
|
| 228 |
+
tokenizer.consume("WS")
|
| 229 |
+
if tokenizer.check("IDENTIFIER", peek=True):
|
| 230 |
+
tokenizer.raise_syntax_error("Expected comma between extra names")
|
| 231 |
+
elif not tokenizer.check("COMMA"):
|
| 232 |
+
break
|
| 233 |
+
|
| 234 |
+
tokenizer.read()
|
| 235 |
+
tokenizer.consume("WS")
|
| 236 |
+
|
| 237 |
+
extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
|
| 238 |
+
extras.append(extra_token.text)
|
| 239 |
+
|
| 240 |
+
return extras
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def _parse_specifier(tokenizer: Tokenizer) -> str:
|
| 244 |
+
"""
|
| 245 |
+
specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
|
| 246 |
+
| WS? version_many WS?
|
| 247 |
+
"""
|
| 248 |
+
with tokenizer.enclosing_tokens(
|
| 249 |
+
"LEFT_PARENTHESIS",
|
| 250 |
+
"RIGHT_PARENTHESIS",
|
| 251 |
+
around="version specifier",
|
| 252 |
+
):
|
| 253 |
+
tokenizer.consume("WS")
|
| 254 |
+
parsed_specifiers = _parse_version_many(tokenizer)
|
| 255 |
+
tokenizer.consume("WS")
|
| 256 |
+
|
| 257 |
+
return parsed_specifiers
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def _parse_version_many(tokenizer: Tokenizer) -> str:
|
| 261 |
+
"""
|
| 262 |
+
version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
|
| 263 |
+
"""
|
| 264 |
+
parsed_specifiers = ""
|
| 265 |
+
while tokenizer.check("SPECIFIER"):
|
| 266 |
+
span_start = tokenizer.position
|
| 267 |
+
parsed_specifiers += tokenizer.read().text
|
| 268 |
+
if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
|
| 269 |
+
tokenizer.raise_syntax_error(
|
| 270 |
+
".* suffix can only be used with `==` or `!=` operators",
|
| 271 |
+
span_start=span_start,
|
| 272 |
+
span_end=tokenizer.position + 1,
|
| 273 |
+
)
|
| 274 |
+
if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
|
| 275 |
+
tokenizer.raise_syntax_error(
|
| 276 |
+
"Local version label can only be used with `==` or `!=` operators",
|
| 277 |
+
span_start=span_start,
|
| 278 |
+
span_end=tokenizer.position,
|
| 279 |
+
)
|
| 280 |
+
tokenizer.consume("WS")
|
| 281 |
+
if not tokenizer.check("COMMA"):
|
| 282 |
+
break
|
| 283 |
+
parsed_specifiers += tokenizer.read().text
|
| 284 |
+
tokenizer.consume("WS")
|
| 285 |
+
|
| 286 |
+
return parsed_specifiers
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
# --------------------------------------------------------------------------------------
|
| 290 |
+
# Recursive descent parser for marker expression
|
| 291 |
+
# --------------------------------------------------------------------------------------
|
| 292 |
+
def parse_marker(source: str) -> MarkerList:
|
| 293 |
+
return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
|
| 297 |
+
retval = _parse_marker(tokenizer)
|
| 298 |
+
tokenizer.expect("END", expected="end of marker expression")
|
| 299 |
+
return retval
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
|
| 303 |
+
"""
|
| 304 |
+
marker = marker_atom (BOOLOP marker_atom)+
|
| 305 |
+
"""
|
| 306 |
+
expression = [_parse_marker_atom(tokenizer)]
|
| 307 |
+
while tokenizer.check("BOOLOP"):
|
| 308 |
+
token = tokenizer.read()
|
| 309 |
+
expr_right = _parse_marker_atom(tokenizer)
|
| 310 |
+
expression.extend((token.text, expr_right))
|
| 311 |
+
return expression
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
|
| 315 |
+
"""
|
| 316 |
+
marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
|
| 317 |
+
| WS? marker_item WS?
|
| 318 |
+
"""
|
| 319 |
+
|
| 320 |
+
tokenizer.consume("WS")
|
| 321 |
+
if tokenizer.check("LEFT_PARENTHESIS", peek=True):
|
| 322 |
+
with tokenizer.enclosing_tokens(
|
| 323 |
+
"LEFT_PARENTHESIS",
|
| 324 |
+
"RIGHT_PARENTHESIS",
|
| 325 |
+
around="marker expression",
|
| 326 |
+
):
|
| 327 |
+
tokenizer.consume("WS")
|
| 328 |
+
marker: MarkerAtom = _parse_marker(tokenizer)
|
| 329 |
+
tokenizer.consume("WS")
|
| 330 |
+
else:
|
| 331 |
+
marker = _parse_marker_item(tokenizer)
|
| 332 |
+
tokenizer.consume("WS")
|
| 333 |
+
return marker
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
|
| 337 |
+
"""
|
| 338 |
+
marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
|
| 339 |
+
"""
|
| 340 |
+
tokenizer.consume("WS")
|
| 341 |
+
marker_var_left = _parse_marker_var(tokenizer)
|
| 342 |
+
tokenizer.consume("WS")
|
| 343 |
+
marker_op = _parse_marker_op(tokenizer)
|
| 344 |
+
tokenizer.consume("WS")
|
| 345 |
+
marker_var_right = _parse_marker_var(tokenizer)
|
| 346 |
+
tokenizer.consume("WS")
|
| 347 |
+
return (marker_var_left, marker_op, marker_var_right)
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: # noqa: RET503
|
| 351 |
+
"""
|
| 352 |
+
marker_var = VARIABLE | QUOTED_STRING
|
| 353 |
+
"""
|
| 354 |
+
if tokenizer.check("VARIABLE"):
|
| 355 |
+
return process_env_var(tokenizer.read().text.replace(".", "_"))
|
| 356 |
+
elif tokenizer.check("QUOTED_STRING"):
|
| 357 |
+
return process_python_str(tokenizer.read().text)
|
| 358 |
+
else:
|
| 359 |
+
tokenizer.raise_syntax_error(
|
| 360 |
+
message="Expected a marker variable or quoted string"
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def process_env_var(env_var: str) -> Variable:
|
| 365 |
+
if env_var in ("platform_python_implementation", "python_implementation"):
|
| 366 |
+
return Variable("platform_python_implementation")
|
| 367 |
+
else:
|
| 368 |
+
return Variable(env_var)
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
def process_python_str(python_str: str) -> Value:
|
| 372 |
+
value = ast.literal_eval(python_str)
|
| 373 |
+
return Value(str(value))
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def _parse_marker_op(tokenizer: Tokenizer) -> Op:
|
| 377 |
+
"""
|
| 378 |
+
marker_op = IN | NOT IN | OP
|
| 379 |
+
"""
|
| 380 |
+
if tokenizer.check("IN"):
|
| 381 |
+
tokenizer.read()
|
| 382 |
+
return Op("in")
|
| 383 |
+
elif tokenizer.check("NOT"):
|
| 384 |
+
tokenizer.read()
|
| 385 |
+
tokenizer.expect("WS", expected="whitespace after 'not'")
|
| 386 |
+
tokenizer.expect("IN", expected="'in' after 'not'")
|
| 387 |
+
return Op("not in")
|
| 388 |
+
elif tokenizer.check("OP"):
|
| 389 |
+
return Op(tokenizer.read().text)
|
| 390 |
+
else:
|
| 391 |
+
return tokenizer.raise_syntax_error(
|
| 392 |
+
"Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in"
|
| 393 |
+
)
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_structures.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
"""Backward-compatibility shim for unpickling Version objects serialized before
|
| 6 |
+
packaging 26.1.
|
| 7 |
+
|
| 8 |
+
Old pickles reference ``packaging._structures.InfinityType`` and
|
| 9 |
+
``packaging._structures.NegativeInfinityType``. This module provides minimal
|
| 10 |
+
stand-in classes so that ``pickle.loads()`` can resolve those references.
|
| 11 |
+
The deserialized objects are not used for comparisons — ``Version.__setstate__``
|
| 12 |
+
discards the stale ``_key`` cache and recomputes it from the core version fields.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class InfinityType:
|
| 19 |
+
"""Stand-in for the removed ``InfinityType`` used in old comparison keys."""
|
| 20 |
+
|
| 21 |
+
def __repr__(self) -> str:
|
| 22 |
+
return "Infinity"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class NegativeInfinityType:
|
| 26 |
+
"""Stand-in for the removed ``NegativeInfinityType`` used in old comparison keys."""
|
| 27 |
+
|
| 28 |
+
def __repr__(self) -> str:
|
| 29 |
+
return "-Infinity"
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
Infinity = InfinityType()
|
| 33 |
+
NegativeInfinity = NegativeInfinityType()
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_tokenizer.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import contextlib
|
| 4 |
+
import re
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Generator, Mapping, NoReturn
|
| 7 |
+
|
| 8 |
+
from .specifiers import Specifier
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass
|
| 12 |
+
class Token:
|
| 13 |
+
name: str
|
| 14 |
+
text: str
|
| 15 |
+
position: int
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ParserSyntaxError(Exception):
|
| 19 |
+
"""The provided source text could not be parsed correctly."""
|
| 20 |
+
|
| 21 |
+
def __init__(
|
| 22 |
+
self,
|
| 23 |
+
message: str,
|
| 24 |
+
*,
|
| 25 |
+
source: str,
|
| 26 |
+
span: tuple[int, int],
|
| 27 |
+
) -> None:
|
| 28 |
+
self.span = span
|
| 29 |
+
self.message = message
|
| 30 |
+
self.source = source
|
| 31 |
+
|
| 32 |
+
super().__init__()
|
| 33 |
+
|
| 34 |
+
def __str__(self) -> str:
|
| 35 |
+
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
|
| 36 |
+
return f"{self.message}\n {self.source}\n {marker}"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
DEFAULT_RULES: dict[str, re.Pattern[str]] = {
|
| 40 |
+
"LEFT_PARENTHESIS": re.compile(r"\("),
|
| 41 |
+
"RIGHT_PARENTHESIS": re.compile(r"\)"),
|
| 42 |
+
"LEFT_BRACKET": re.compile(r"\["),
|
| 43 |
+
"RIGHT_BRACKET": re.compile(r"\]"),
|
| 44 |
+
"SEMICOLON": re.compile(r";"),
|
| 45 |
+
"COMMA": re.compile(r","),
|
| 46 |
+
"QUOTED_STRING": re.compile(
|
| 47 |
+
r"""
|
| 48 |
+
(
|
| 49 |
+
('[^']*')
|
| 50 |
+
|
|
| 51 |
+
("[^"]*")
|
| 52 |
+
)
|
| 53 |
+
""",
|
| 54 |
+
re.VERBOSE,
|
| 55 |
+
),
|
| 56 |
+
"OP": re.compile(r"(===|==|~=|!=|<=|>=|<|>)"),
|
| 57 |
+
"BOOLOP": re.compile(r"\b(or|and)\b"),
|
| 58 |
+
"IN": re.compile(r"\bin\b"),
|
| 59 |
+
"NOT": re.compile(r"\bnot\b"),
|
| 60 |
+
"VARIABLE": re.compile(
|
| 61 |
+
r"""
|
| 62 |
+
\b(
|
| 63 |
+
python_version
|
| 64 |
+
|python_full_version
|
| 65 |
+
|os[._]name
|
| 66 |
+
|sys[._]platform
|
| 67 |
+
|platform_(release|system)
|
| 68 |
+
|platform[._](version|machine|python_implementation)
|
| 69 |
+
|python_implementation
|
| 70 |
+
|implementation_(name|version)
|
| 71 |
+
|extras?
|
| 72 |
+
|dependency_groups
|
| 73 |
+
)\b
|
| 74 |
+
""",
|
| 75 |
+
re.VERBOSE,
|
| 76 |
+
),
|
| 77 |
+
"SPECIFIER": re.compile(
|
| 78 |
+
Specifier._specifier_regex_str,
|
| 79 |
+
re.VERBOSE | re.IGNORECASE,
|
| 80 |
+
),
|
| 81 |
+
"AT": re.compile(r"\@"),
|
| 82 |
+
"URL": re.compile(r"[^ \t]+"),
|
| 83 |
+
"IDENTIFIER": re.compile(r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b"),
|
| 84 |
+
"VERSION_PREFIX_TRAIL": re.compile(r"\.\*"),
|
| 85 |
+
"VERSION_LOCAL_LABEL_TRAIL": re.compile(r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*"),
|
| 86 |
+
"WS": re.compile(r"[ \t]+"),
|
| 87 |
+
"END": re.compile(r"$"),
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class Tokenizer:
|
| 92 |
+
"""Context-sensitive token parsing.
|
| 93 |
+
|
| 94 |
+
Provides methods to examine the input stream to check whether the next token
|
| 95 |
+
matches.
|
| 96 |
+
"""
|
| 97 |
+
|
| 98 |
+
def __init__(
|
| 99 |
+
self,
|
| 100 |
+
source: str,
|
| 101 |
+
*,
|
| 102 |
+
rules: Mapping[str, re.Pattern[str]],
|
| 103 |
+
) -> None:
|
| 104 |
+
self.source = source
|
| 105 |
+
self.rules = rules
|
| 106 |
+
self.next_token: Token | None = None
|
| 107 |
+
self.position = 0
|
| 108 |
+
|
| 109 |
+
def consume(self, name: str) -> None:
|
| 110 |
+
"""Move beyond provided token name, if at current position."""
|
| 111 |
+
if self.check(name):
|
| 112 |
+
self.read()
|
| 113 |
+
|
| 114 |
+
def check(self, name: str, *, peek: bool = False) -> bool:
|
| 115 |
+
"""Check whether the next token has the provided name.
|
| 116 |
+
|
| 117 |
+
By default, if the check succeeds, the token *must* be read before
|
| 118 |
+
another check. If `peek` is set to `True`, the token is not loaded and
|
| 119 |
+
would need to be checked again.
|
| 120 |
+
"""
|
| 121 |
+
assert self.next_token is None, (
|
| 122 |
+
f"Cannot check for {name!r}, already have {self.next_token!r}"
|
| 123 |
+
)
|
| 124 |
+
assert name in self.rules, f"Unknown token name: {name!r}"
|
| 125 |
+
|
| 126 |
+
expression = self.rules[name]
|
| 127 |
+
|
| 128 |
+
match = expression.match(self.source, self.position)
|
| 129 |
+
if match is None:
|
| 130 |
+
return False
|
| 131 |
+
if not peek:
|
| 132 |
+
self.next_token = Token(name, match[0], self.position)
|
| 133 |
+
return True
|
| 134 |
+
|
| 135 |
+
def expect(self, name: str, *, expected: str) -> Token:
|
| 136 |
+
"""Expect a certain token name next, failing with a syntax error otherwise.
|
| 137 |
+
|
| 138 |
+
The token is *not* read.
|
| 139 |
+
"""
|
| 140 |
+
if not self.check(name):
|
| 141 |
+
raise self.raise_syntax_error(f"Expected {expected}")
|
| 142 |
+
return self.read()
|
| 143 |
+
|
| 144 |
+
def read(self) -> Token:
|
| 145 |
+
"""Consume the next token and return it."""
|
| 146 |
+
token = self.next_token
|
| 147 |
+
assert token is not None
|
| 148 |
+
|
| 149 |
+
self.position += len(token.text)
|
| 150 |
+
self.next_token = None
|
| 151 |
+
|
| 152 |
+
return token
|
| 153 |
+
|
| 154 |
+
def raise_syntax_error(
|
| 155 |
+
self,
|
| 156 |
+
message: str,
|
| 157 |
+
*,
|
| 158 |
+
span_start: int | None = None,
|
| 159 |
+
span_end: int | None = None,
|
| 160 |
+
) -> NoReturn:
|
| 161 |
+
"""Raise ParserSyntaxError at the given position."""
|
| 162 |
+
span = (
|
| 163 |
+
self.position if span_start is None else span_start,
|
| 164 |
+
self.position if span_end is None else span_end,
|
| 165 |
+
)
|
| 166 |
+
raise ParserSyntaxError(
|
| 167 |
+
message,
|
| 168 |
+
source=self.source,
|
| 169 |
+
span=span,
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
@contextlib.contextmanager
|
| 173 |
+
def enclosing_tokens(
|
| 174 |
+
self, open_token: str, close_token: str, *, around: str
|
| 175 |
+
) -> Generator[None, None, None]:
|
| 176 |
+
if self.check(open_token):
|
| 177 |
+
open_position = self.position
|
| 178 |
+
self.read()
|
| 179 |
+
else:
|
| 180 |
+
open_position = None
|
| 181 |
+
|
| 182 |
+
yield
|
| 183 |
+
|
| 184 |
+
if open_position is None:
|
| 185 |
+
return
|
| 186 |
+
|
| 187 |
+
if not self.check(close_token):
|
| 188 |
+
self.raise_syntax_error(
|
| 189 |
+
f"Expected matching {close_token} for {open_token}, after {around}",
|
| 190 |
+
span_start=open_position,
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
self.read()
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/dependency_groups.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
from collections.abc import Mapping, Sequence
|
| 5 |
+
|
| 6 |
+
from .errors import _ErrorCollector
|
| 7 |
+
from .requirements import Requirement
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"CyclicDependencyGroup",
|
| 11 |
+
"DependencyGroupInclude",
|
| 12 |
+
"DependencyGroupResolver",
|
| 13 |
+
"DuplicateGroupNames",
|
| 14 |
+
"InvalidDependencyGroupObject",
|
| 15 |
+
"resolve_dependency_groups",
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def __dir__() -> list[str]:
|
| 20 |
+
return __all__
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# -----------
|
| 24 |
+
# Error Types
|
| 25 |
+
# -----------
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class DuplicateGroupNames(ValueError):
|
| 29 |
+
"""
|
| 30 |
+
The same dependency groups were defined twice, with different non-normalized names.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class CyclicDependencyGroup(ValueError):
|
| 35 |
+
"""
|
| 36 |
+
The dependency group includes form a cycle.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def __init__(self, requested_group: str, group: str, include_group: str) -> None:
|
| 40 |
+
self.requested_group = requested_group
|
| 41 |
+
self.group = group
|
| 42 |
+
self.include_group = include_group
|
| 43 |
+
|
| 44 |
+
if include_group == group:
|
| 45 |
+
reason = f"{group} includes itself"
|
| 46 |
+
else:
|
| 47 |
+
reason = f"{include_group} -> {group}, {group} -> {include_group}"
|
| 48 |
+
super().__init__(
|
| 49 |
+
"Cyclic dependency group include while resolving "
|
| 50 |
+
f"{requested_group}: {reason}"
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# in the PEP 735 spec, the tables in dependency group lists were described as
|
| 55 |
+
# "Dependency Object Specifiers", but the only defined type of object was a
|
| 56 |
+
# "Dependency Group Include" -- hence the naming of this error as "Object"
|
| 57 |
+
class InvalidDependencyGroupObject(ValueError):
|
| 58 |
+
"""
|
| 59 |
+
A member of a dependency group was identified as a dict, but was not in a valid
|
| 60 |
+
format.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# ------------------------
|
| 65 |
+
# Object Model & Interface
|
| 66 |
+
# ------------------------
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class DependencyGroupInclude:
|
| 70 |
+
__slots__ = ("include_group",)
|
| 71 |
+
|
| 72 |
+
def __init__(self, include_group: str) -> None:
|
| 73 |
+
"""
|
| 74 |
+
Initialize a DependencyGroupInclude.
|
| 75 |
+
|
| 76 |
+
:param include_group: The name of the group referred to by this include.
|
| 77 |
+
"""
|
| 78 |
+
self.include_group = include_group
|
| 79 |
+
|
| 80 |
+
def __repr__(self) -> str:
|
| 81 |
+
return f"{self.__class__.__name__}({self.include_group!r})"
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class DependencyGroupResolver:
|
| 85 |
+
"""
|
| 86 |
+
A resolver for Dependency Group data.
|
| 87 |
+
|
| 88 |
+
This class handles caching, name normalization, cycle detection, and other
|
| 89 |
+
parsing requirements. There are only two public methods for exploring the data:
|
| 90 |
+
``lookup()`` and ``resolve()``.
|
| 91 |
+
|
| 92 |
+
:param dependency_groups: A mapping, as provided via pyproject
|
| 93 |
+
``[dependency-groups]``.
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
def __init__(
|
| 97 |
+
self,
|
| 98 |
+
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
|
| 99 |
+
) -> None:
|
| 100 |
+
errors = _ErrorCollector()
|
| 101 |
+
|
| 102 |
+
self.dependency_groups = _normalize_group_names(dependency_groups, errors)
|
| 103 |
+
|
| 104 |
+
# a map of group names to parsed data
|
| 105 |
+
self._parsed_groups: dict[
|
| 106 |
+
str, tuple[Requirement | DependencyGroupInclude, ...]
|
| 107 |
+
] = {}
|
| 108 |
+
# a map of group names to their ancestors, used for cycle detection
|
| 109 |
+
self._include_graph_ancestors: dict[str, tuple[str, ...]] = {}
|
| 110 |
+
# a cache of completed resolutions to Requirement lists
|
| 111 |
+
self._resolve_cache: dict[str, tuple[Requirement, ...]] = {}
|
| 112 |
+
|
| 113 |
+
errors.finalize("[dependency-groups] data was invalid")
|
| 114 |
+
|
| 115 |
+
def lookup(self, group: str) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
| 116 |
+
"""
|
| 117 |
+
Lookup a group name, returning the parsed dependency data for that group.
|
| 118 |
+
This will not resolve includes.
|
| 119 |
+
|
| 120 |
+
:param group: the name of the group to lookup
|
| 121 |
+
"""
|
| 122 |
+
group = _normalize_name(group)
|
| 123 |
+
|
| 124 |
+
with _ErrorCollector().on_exit(
|
| 125 |
+
f"[dependency-groups] data for {group!r} was malformed"
|
| 126 |
+
) as errors:
|
| 127 |
+
return self._parse_group(group, errors)
|
| 128 |
+
|
| 129 |
+
def resolve(self, group: str) -> tuple[Requirement, ...]:
|
| 130 |
+
"""
|
| 131 |
+
Resolve a dependency group to a list of requirements.
|
| 132 |
+
|
| 133 |
+
:param group: the name of the group to resolve
|
| 134 |
+
"""
|
| 135 |
+
group = _normalize_name(group)
|
| 136 |
+
|
| 137 |
+
with _ErrorCollector().on_exit(
|
| 138 |
+
f"[dependency-groups] data for {group!r} was malformed"
|
| 139 |
+
) as errors:
|
| 140 |
+
return self._resolve(group, group, errors)
|
| 141 |
+
|
| 142 |
+
def _resolve(
|
| 143 |
+
self, group: str, requested_group: str, errors: _ErrorCollector
|
| 144 |
+
) -> tuple[Requirement, ...]:
|
| 145 |
+
"""
|
| 146 |
+
This is a helper for cached resolution to strings. It preserves the name of the
|
| 147 |
+
group which the user initially requested in order to present a clearer error in
|
| 148 |
+
the event that a cycle is detected.
|
| 149 |
+
|
| 150 |
+
:param group: The normalized name of the group to resolve.
|
| 151 |
+
:param requested_group: The group which was used in the original, user-facing
|
| 152 |
+
request.
|
| 153 |
+
"""
|
| 154 |
+
if group in self._resolve_cache:
|
| 155 |
+
return self._resolve_cache[group]
|
| 156 |
+
|
| 157 |
+
parsed = self._parse_group(group, errors)
|
| 158 |
+
|
| 159 |
+
resolved_group = []
|
| 160 |
+
|
| 161 |
+
for item in parsed:
|
| 162 |
+
if isinstance(item, Requirement):
|
| 163 |
+
resolved_group.append(item)
|
| 164 |
+
elif isinstance(item, DependencyGroupInclude):
|
| 165 |
+
include_group = _normalize_name(item.include_group)
|
| 166 |
+
|
| 167 |
+
# if a group is cyclic, record the error
|
| 168 |
+
# otherwise, follow the include_group reference
|
| 169 |
+
#
|
| 170 |
+
# this allows us to examine all includes in a group, even in the
|
| 171 |
+
# presence of errors
|
| 172 |
+
if include_group in self._include_graph_ancestors.get(group, ()):
|
| 173 |
+
errors.error(
|
| 174 |
+
CyclicDependencyGroup(
|
| 175 |
+
requested_group, group, item.include_group
|
| 176 |
+
)
|
| 177 |
+
)
|
| 178 |
+
else:
|
| 179 |
+
self._include_graph_ancestors[include_group] = (
|
| 180 |
+
*self._include_graph_ancestors.get(group, ()),
|
| 181 |
+
group,
|
| 182 |
+
)
|
| 183 |
+
resolved_group.extend(
|
| 184 |
+
self._resolve(include_group, requested_group, errors)
|
| 185 |
+
)
|
| 186 |
+
else: # pragma: no cover
|
| 187 |
+
raise NotImplementedError(
|
| 188 |
+
f"Invalid dependency group item after parse: {item}"
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
# in the event that errors were detected, present the group as empty and do not
|
| 192 |
+
# cache the result
|
| 193 |
+
# this ensures that repeated access to a cyclic group will raise multiple errors
|
| 194 |
+
if errors.errors:
|
| 195 |
+
return ()
|
| 196 |
+
|
| 197 |
+
self._resolve_cache[group] = tuple(resolved_group)
|
| 198 |
+
return self._resolve_cache[group]
|
| 199 |
+
|
| 200 |
+
def _parse_group(
|
| 201 |
+
self, group: str, errors: _ErrorCollector
|
| 202 |
+
) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
| 203 |
+
# short circuit -- never do the work twice
|
| 204 |
+
if group in self._parsed_groups:
|
| 205 |
+
return self._parsed_groups[group]
|
| 206 |
+
|
| 207 |
+
if group not in self.dependency_groups:
|
| 208 |
+
errors.error(LookupError(f"Dependency group '{group}' not found"))
|
| 209 |
+
return ()
|
| 210 |
+
|
| 211 |
+
raw_group = self.dependency_groups[group]
|
| 212 |
+
if isinstance(raw_group, str):
|
| 213 |
+
errors.error(
|
| 214 |
+
TypeError(
|
| 215 |
+
f"Dependency group {group!r} contained a string rather than a list."
|
| 216 |
+
)
|
| 217 |
+
)
|
| 218 |
+
return ()
|
| 219 |
+
|
| 220 |
+
if not isinstance(raw_group, Sequence):
|
| 221 |
+
errors.error(
|
| 222 |
+
TypeError(f"Dependency group {group!r} is not a sequence type.")
|
| 223 |
+
)
|
| 224 |
+
return ()
|
| 225 |
+
|
| 226 |
+
elements: list[Requirement | DependencyGroupInclude] = []
|
| 227 |
+
for item in raw_group:
|
| 228 |
+
if isinstance(item, str):
|
| 229 |
+
# packaging.requirements.Requirement parsing ensures that this is a
|
| 230 |
+
# valid PEP 508 Dependency Specifier
|
| 231 |
+
# raises InvalidRequirement on failure
|
| 232 |
+
elements.append(Requirement(item))
|
| 233 |
+
elif isinstance(item, Mapping):
|
| 234 |
+
if tuple(item.keys()) != ("include-group",):
|
| 235 |
+
errors.error(
|
| 236 |
+
InvalidDependencyGroupObject(
|
| 237 |
+
f"Invalid dependency group item: {item!r}"
|
| 238 |
+
)
|
| 239 |
+
)
|
| 240 |
+
else:
|
| 241 |
+
include_group = item["include-group"]
|
| 242 |
+
elements.append(DependencyGroupInclude(include_group=include_group))
|
| 243 |
+
else:
|
| 244 |
+
errors.error(TypeError(f"Invalid dependency group item: {item!r}"))
|
| 245 |
+
|
| 246 |
+
self._parsed_groups[group] = tuple(elements)
|
| 247 |
+
return self._parsed_groups[group]
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# --------------------
|
| 251 |
+
# Functional Interface
|
| 252 |
+
# --------------------
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def resolve_dependency_groups(
|
| 256 |
+
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]], /, *groups: str
|
| 257 |
+
) -> tuple[str, ...]:
|
| 258 |
+
"""
|
| 259 |
+
Resolve a dependency group to a tuple of requirements, as strings.
|
| 260 |
+
|
| 261 |
+
:param dependency_groups: the parsed contents of the ``[dependency-groups]`` table
|
| 262 |
+
from ``pyproject.toml``
|
| 263 |
+
:param groups: the name of the group(s) to resolve
|
| 264 |
+
"""
|
| 265 |
+
resolver = DependencyGroupResolver(dependency_groups)
|
| 266 |
+
return tuple(str(r) for group in groups for r in resolver.resolve(group))
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
# ----------------
|
| 270 |
+
# internal helpers
|
| 271 |
+
# ----------------
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
_NORMALIZE_PATTERN = re.compile(r"[-_.]+")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def _normalize_name(name: str) -> str:
|
| 278 |
+
return _NORMALIZE_PATTERN.sub("-", name).lower()
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _normalize_group_names(
|
| 282 |
+
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
|
| 283 |
+
errors: _ErrorCollector,
|
| 284 |
+
) -> dict[str, Sequence[str | Mapping[str, str]]]:
|
| 285 |
+
original_names: dict[str, list[str]] = {}
|
| 286 |
+
normalized_groups: dict[str, Sequence[str | Mapping[str, str]]] = {}
|
| 287 |
+
|
| 288 |
+
for group_name, value in dependency_groups.items():
|
| 289 |
+
normed_group_name = _normalize_name(group_name)
|
| 290 |
+
original_names.setdefault(normed_group_name, []).append(group_name)
|
| 291 |
+
normalized_groups[normed_group_name] = value
|
| 292 |
+
|
| 293 |
+
for normed_name, names in original_names.items():
|
| 294 |
+
if len(names) > 1:
|
| 295 |
+
errors.error(
|
| 296 |
+
DuplicateGroupNames(
|
| 297 |
+
"Duplicate dependency group names: "
|
| 298 |
+
f"{normed_name} ({', '.join(names)})"
|
| 299 |
+
)
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
return normalized_groups
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/direct_url.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import dataclasses
|
| 4 |
+
import re
|
| 5 |
+
import urllib.parse
|
| 6 |
+
from collections.abc import Mapping
|
| 7 |
+
from typing import TYPE_CHECKING, Any, Protocol, TypeVar
|
| 8 |
+
|
| 9 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 10 |
+
import sys
|
| 11 |
+
from collections.abc import Collection
|
| 12 |
+
|
| 13 |
+
if sys.version_info >= (3, 11):
|
| 14 |
+
from typing import Self
|
| 15 |
+
else:
|
| 16 |
+
from typing_extensions import Self
|
| 17 |
+
|
| 18 |
+
__all__ = [
|
| 19 |
+
"ArchiveInfo",
|
| 20 |
+
"DirInfo",
|
| 21 |
+
"DirectUrl",
|
| 22 |
+
"DirectUrlValidationError",
|
| 23 |
+
"VcsInfo",
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def __dir__() -> list[str]:
|
| 28 |
+
return __all__
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
_T = TypeVar("_T")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class _FromMappingProtocol(Protocol): # pragma: no cover
|
| 35 |
+
@classmethod
|
| 36 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _json_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
|
| 43 |
+
return {key: value for key, value in data if value is not None}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
| 47 |
+
"""Get a value from the dictionary and verify it's the expected type."""
|
| 48 |
+
if (value := d.get(key)) is None:
|
| 49 |
+
return None
|
| 50 |
+
if not isinstance(value, expected_type):
|
| 51 |
+
raise DirectUrlValidationError(
|
| 52 |
+
f"Unexpected type {type(value).__name__} "
|
| 53 |
+
f"(expected {expected_type.__name__})",
|
| 54 |
+
context=key,
|
| 55 |
+
)
|
| 56 |
+
return value
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
|
| 60 |
+
"""Get a required value from the dictionary and verify it's the expected type."""
|
| 61 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 62 |
+
raise _DirectUrlRequiredKeyError(key)
|
| 63 |
+
return value
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _get_object(
|
| 67 |
+
d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
|
| 68 |
+
) -> _FromMappingProtocolT | None:
|
| 69 |
+
"""Get a dictionary value from the dictionary and convert it to a dataclass."""
|
| 70 |
+
if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 71 |
+
return None
|
| 72 |
+
try:
|
| 73 |
+
return target_type._from_dict(value)
|
| 74 |
+
except Exception as e:
|
| 75 |
+
raise DirectUrlValidationError(e, context=key) from e
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
_PEP610_USER_PASS_ENV_VARS_REGEX = re.compile(
|
| 79 |
+
r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$"
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _strip_auth_from_netloc(netloc: str, safe_user_passwords: Collection[str]) -> str:
|
| 84 |
+
if "@" not in netloc:
|
| 85 |
+
return netloc
|
| 86 |
+
user_pass, netloc_no_user_pass = netloc.split("@", 1)
|
| 87 |
+
if user_pass in safe_user_passwords:
|
| 88 |
+
return netloc
|
| 89 |
+
if _PEP610_USER_PASS_ENV_VARS_REGEX.match(user_pass):
|
| 90 |
+
return netloc
|
| 91 |
+
return netloc_no_user_pass
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def _strip_url(url: str, safe_user_passwords: Collection[str]) -> str:
|
| 95 |
+
"""url with user:password part removed unless it is formed with
|
| 96 |
+
environment variables as specified in PEP 610, or it is a safe user:password
|
| 97 |
+
such as `git`.
|
| 98 |
+
"""
|
| 99 |
+
parsed_url = urllib.parse.urlsplit(url)
|
| 100 |
+
netloc = _strip_auth_from_netloc(parsed_url.netloc, safe_user_passwords)
|
| 101 |
+
return urllib.parse.urlunsplit(
|
| 102 |
+
(
|
| 103 |
+
parsed_url.scheme,
|
| 104 |
+
netloc,
|
| 105 |
+
parsed_url.path,
|
| 106 |
+
parsed_url.query,
|
| 107 |
+
parsed_url.fragment,
|
| 108 |
+
)
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class DirectUrlValidationError(Exception):
|
| 113 |
+
"""Raised when when input data is not spec-compliant."""
|
| 114 |
+
|
| 115 |
+
context: str | None = None
|
| 116 |
+
message: str
|
| 117 |
+
|
| 118 |
+
def __init__(
|
| 119 |
+
self,
|
| 120 |
+
cause: str | Exception,
|
| 121 |
+
*,
|
| 122 |
+
context: str | None = None,
|
| 123 |
+
) -> None:
|
| 124 |
+
if isinstance(cause, DirectUrlValidationError):
|
| 125 |
+
if cause.context:
|
| 126 |
+
self.context = (
|
| 127 |
+
f"{context}.{cause.context}" if context else cause.context
|
| 128 |
+
)
|
| 129 |
+
else:
|
| 130 |
+
self.context = context # pragma: no cover
|
| 131 |
+
self.message = cause.message
|
| 132 |
+
else:
|
| 133 |
+
self.context = context
|
| 134 |
+
self.message = str(cause)
|
| 135 |
+
|
| 136 |
+
def __str__(self) -> str:
|
| 137 |
+
if self.context:
|
| 138 |
+
return f"{self.message} in {self.context!r}"
|
| 139 |
+
return self.message
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class _DirectUrlRequiredKeyError(DirectUrlValidationError):
|
| 143 |
+
def __init__(self, key: str) -> None:
|
| 144 |
+
super().__init__("Missing required value", context=key)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@dataclasses.dataclass(frozen=True, init=False)
|
| 148 |
+
class VcsInfo:
|
| 149 |
+
vcs: str
|
| 150 |
+
commit_id: str
|
| 151 |
+
requested_revision: str | None = None
|
| 152 |
+
|
| 153 |
+
def __init__(
|
| 154 |
+
self,
|
| 155 |
+
*,
|
| 156 |
+
vcs: str,
|
| 157 |
+
commit_id: str,
|
| 158 |
+
requested_revision: str | None = None,
|
| 159 |
+
) -> None:
|
| 160 |
+
object.__setattr__(self, "vcs", vcs)
|
| 161 |
+
object.__setattr__(self, "commit_id", commit_id)
|
| 162 |
+
object.__setattr__(self, "requested_revision", requested_revision)
|
| 163 |
+
|
| 164 |
+
@classmethod
|
| 165 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 166 |
+
# We can't validate vcs value because is not closed.
|
| 167 |
+
return cls(
|
| 168 |
+
vcs=_get_required(d, str, "vcs"),
|
| 169 |
+
requested_revision=_get(d, str, "requested_revision"),
|
| 170 |
+
commit_id=_get_required(d, str, "commit_id"),
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
@dataclasses.dataclass(frozen=True, init=False)
|
| 175 |
+
class ArchiveInfo:
|
| 176 |
+
hashes: Mapping[str, str] | None = None
|
| 177 |
+
|
| 178 |
+
def __init__(
|
| 179 |
+
self,
|
| 180 |
+
*,
|
| 181 |
+
hashes: Mapping[str, str] | None = None,
|
| 182 |
+
) -> None:
|
| 183 |
+
object.__setattr__(self, "hashes", hashes)
|
| 184 |
+
|
| 185 |
+
@classmethod
|
| 186 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 187 |
+
hashes = _get(d, Mapping, "hashes") # type: ignore[type-abstract]
|
| 188 |
+
if hashes is not None and not all(isinstance(h, str) for h in hashes.values()):
|
| 189 |
+
raise DirectUrlValidationError(
|
| 190 |
+
"Hash values must be strings", context="hashes"
|
| 191 |
+
)
|
| 192 |
+
legacy_hash = _get(d, str, "hash")
|
| 193 |
+
if legacy_hash is not None:
|
| 194 |
+
if "=" not in legacy_hash:
|
| 195 |
+
raise DirectUrlValidationError(
|
| 196 |
+
"Invalid hash format (expected '<algorithm>=<hash>')",
|
| 197 |
+
context="hash",
|
| 198 |
+
)
|
| 199 |
+
hash_algorithm, hash_value = legacy_hash.split("=", 1)
|
| 200 |
+
if hashes is None:
|
| 201 |
+
# if `hashes` are not present, we can derive it from the legacy `hash`
|
| 202 |
+
hashes = {hash_algorithm: hash_value}
|
| 203 |
+
else:
|
| 204 |
+
# if `hashes` are present, the legacy `hash` must match one of them
|
| 205 |
+
if hash_algorithm not in hashes:
|
| 206 |
+
raise DirectUrlValidationError(
|
| 207 |
+
f"Algorithm {hash_algorithm!r} used in hash field "
|
| 208 |
+
f"is not present in hashes field",
|
| 209 |
+
context="hashes",
|
| 210 |
+
)
|
| 211 |
+
if hashes[hash_algorithm] != hash_value:
|
| 212 |
+
raise DirectUrlValidationError(
|
| 213 |
+
f"Algorithm {hash_algorithm!r} used in hash field "
|
| 214 |
+
f"has different value in hashes field",
|
| 215 |
+
context="hash",
|
| 216 |
+
)
|
| 217 |
+
return cls(hashes=hashes)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
@dataclasses.dataclass(frozen=True, init=False)
|
| 221 |
+
class DirInfo:
|
| 222 |
+
editable: bool | None = None
|
| 223 |
+
|
| 224 |
+
def __init__(
|
| 225 |
+
self,
|
| 226 |
+
*,
|
| 227 |
+
editable: bool | None = None,
|
| 228 |
+
) -> None:
|
| 229 |
+
object.__setattr__(self, "editable", editable)
|
| 230 |
+
|
| 231 |
+
@classmethod
|
| 232 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 233 |
+
return cls(
|
| 234 |
+
editable=_get(d, bool, "editable"),
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
@dataclasses.dataclass(frozen=True, init=False)
|
| 239 |
+
class DirectUrl:
|
| 240 |
+
"""A class representing a direct URL."""
|
| 241 |
+
|
| 242 |
+
url: str
|
| 243 |
+
archive_info: ArchiveInfo | None = None
|
| 244 |
+
vcs_info: VcsInfo | None = None
|
| 245 |
+
dir_info: DirInfo | None = None
|
| 246 |
+
subdirectory: str | None = None # XXX Path or str?
|
| 247 |
+
|
| 248 |
+
def __init__(
|
| 249 |
+
self,
|
| 250 |
+
*,
|
| 251 |
+
url: str,
|
| 252 |
+
archive_info: ArchiveInfo | None = None,
|
| 253 |
+
vcs_info: VcsInfo | None = None,
|
| 254 |
+
dir_info: DirInfo | None = None,
|
| 255 |
+
subdirectory: str | None = None,
|
| 256 |
+
) -> None:
|
| 257 |
+
object.__setattr__(self, "url", url)
|
| 258 |
+
object.__setattr__(self, "archive_info", archive_info)
|
| 259 |
+
object.__setattr__(self, "vcs_info", vcs_info)
|
| 260 |
+
object.__setattr__(self, "dir_info", dir_info)
|
| 261 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 262 |
+
|
| 263 |
+
@classmethod
|
| 264 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 265 |
+
direct_url = cls(
|
| 266 |
+
url=_get_required(d, str, "url"),
|
| 267 |
+
archive_info=_get_object(d, ArchiveInfo, "archive_info"),
|
| 268 |
+
vcs_info=_get_object(d, VcsInfo, "vcs_info"),
|
| 269 |
+
dir_info=_get_object(d, DirInfo, "dir_info"),
|
| 270 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 271 |
+
)
|
| 272 |
+
if (
|
| 273 |
+
bool(direct_url.vcs_info)
|
| 274 |
+
+ bool(direct_url.archive_info)
|
| 275 |
+
+ bool(direct_url.dir_info)
|
| 276 |
+
) != 1:
|
| 277 |
+
raise DirectUrlValidationError(
|
| 278 |
+
"Exactly one of vcs_info, archive_info, dir_info must be present"
|
| 279 |
+
)
|
| 280 |
+
if direct_url.dir_info is not None and not direct_url.url.startswith("file://"):
|
| 281 |
+
raise DirectUrlValidationError(
|
| 282 |
+
"URL scheme must be file:// when dir_info is present",
|
| 283 |
+
context="url",
|
| 284 |
+
)
|
| 285 |
+
# XXX subdirectory must be relative, can we, should we validate that here?
|
| 286 |
+
return direct_url
|
| 287 |
+
|
| 288 |
+
@classmethod
|
| 289 |
+
def from_dict(cls, d: Mapping[str, Any], /) -> Self:
|
| 290 |
+
"""Create and validate a DirectUrl instance from a JSON dictionary."""
|
| 291 |
+
return cls._from_dict(d)
|
| 292 |
+
|
| 293 |
+
def to_dict(
|
| 294 |
+
self,
|
| 295 |
+
*,
|
| 296 |
+
generate_legacy_hash: bool = False,
|
| 297 |
+
strip_user_password: bool = True,
|
| 298 |
+
safe_user_passwords: Collection[str] = ("git",),
|
| 299 |
+
) -> Mapping[str, Any]:
|
| 300 |
+
"""Convert the DirectUrl instance to a JSON dictionary.
|
| 301 |
+
|
| 302 |
+
:param generate_legacy_hash: If True, include a legacy `hash` field in
|
| 303 |
+
`archive_info` for backward compatibility with tools that don't
|
| 304 |
+
support the `hashes` field.
|
| 305 |
+
:param strip_user_password: If True, strip user:password from the URL
|
| 306 |
+
unless it is formed with environment variables as specified in PEP
|
| 307 |
+
610, or it is a safe user:password such as `git`.
|
| 308 |
+
:param safe_user_passwords: A collection of user:password strings that
|
| 309 |
+
should not be stripped from the URL even if `strip_user_password` is
|
| 310 |
+
True.
|
| 311 |
+
"""
|
| 312 |
+
res = dataclasses.asdict(self, dict_factory=_json_dict_factory)
|
| 313 |
+
if generate_legacy_hash and self.archive_info and self.archive_info.hashes:
|
| 314 |
+
hash_algorithm, hash_value = next(iter(self.archive_info.hashes.items()))
|
| 315 |
+
res["archive_info"]["hash"] = f"{hash_algorithm}={hash_value}"
|
| 316 |
+
if strip_user_password:
|
| 317 |
+
res["url"] = _strip_url(self.url, safe_user_passwords)
|
| 318 |
+
return res
|
| 319 |
+
|
| 320 |
+
def validate(self) -> None:
|
| 321 |
+
"""Validate the DirectUrl instance against the specification.
|
| 322 |
+
|
| 323 |
+
Raises :class:`DirectUrlValidationError` if invalid.
|
| 324 |
+
"""
|
| 325 |
+
self.from_dict(self.to_dict())
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/errors.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import contextlib
|
| 4 |
+
import dataclasses
|
| 5 |
+
import sys
|
| 6 |
+
import typing
|
| 7 |
+
|
| 8 |
+
__all__ = ["ExceptionGroup"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def __dir__() -> list[str]:
|
| 12 |
+
return __all__
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
if sys.version_info >= (3, 11): # pragma: no cover
|
| 16 |
+
from builtins import ExceptionGroup
|
| 17 |
+
else: # pragma: no cover
|
| 18 |
+
|
| 19 |
+
class ExceptionGroup(Exception):
|
| 20 |
+
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
|
| 21 |
+
|
| 22 |
+
If :external:exc:`ExceptionGroup` is already defined by Python itself,
|
| 23 |
+
that version is used instead.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
message: str
|
| 27 |
+
exceptions: list[Exception]
|
| 28 |
+
|
| 29 |
+
def __init__(self, message: str, exceptions: list[Exception]) -> None:
|
| 30 |
+
self.message = message
|
| 31 |
+
self.exceptions = exceptions
|
| 32 |
+
|
| 33 |
+
def __repr__(self) -> str:
|
| 34 |
+
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclasses.dataclass
|
| 38 |
+
class _ErrorCollector:
|
| 39 |
+
"""
|
| 40 |
+
Collect errors into ExceptionGroups.
|
| 41 |
+
|
| 42 |
+
Used like this:
|
| 43 |
+
|
| 44 |
+
collector = _ErrorCollector()
|
| 45 |
+
# Add a single exception
|
| 46 |
+
collector.error(ValueError("one"))
|
| 47 |
+
|
| 48 |
+
# Supports nesting, including combining ExceptionGroups
|
| 49 |
+
with collector.collect():
|
| 50 |
+
raise ValueError("two")
|
| 51 |
+
collector.finalize("Found some errors")
|
| 52 |
+
|
| 53 |
+
Since making a collector and then calling finalize later is a common pattern,
|
| 54 |
+
a convenience method ``on_exit`` is provided.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
errors: list[Exception] = dataclasses.field(default_factory=list, init=False)
|
| 58 |
+
|
| 59 |
+
def finalize(self, msg: str) -> None:
|
| 60 |
+
"""Raise a group exception if there are any errors."""
|
| 61 |
+
if self.errors:
|
| 62 |
+
raise ExceptionGroup(msg, self.errors)
|
| 63 |
+
|
| 64 |
+
@contextlib.contextmanager
|
| 65 |
+
def on_exit(self, msg: str) -> typing.Generator[_ErrorCollector, None, None]:
|
| 66 |
+
"""
|
| 67 |
+
Calls finalize if no uncollected errors were present.
|
| 68 |
+
|
| 69 |
+
Uncollected errors are raised normally.
|
| 70 |
+
"""
|
| 71 |
+
yield self
|
| 72 |
+
self.finalize(msg)
|
| 73 |
+
|
| 74 |
+
@contextlib.contextmanager
|
| 75 |
+
def collect(self, *err_cls: type[Exception]) -> typing.Generator[None, None, None]:
|
| 76 |
+
"""
|
| 77 |
+
Context manager to collect errors into the error list.
|
| 78 |
+
|
| 79 |
+
Must be inside loops, as only one error can be collected at a time.
|
| 80 |
+
"""
|
| 81 |
+
error_classes = err_cls or (Exception,)
|
| 82 |
+
try:
|
| 83 |
+
yield
|
| 84 |
+
except ExceptionGroup as error:
|
| 85 |
+
self.errors.extend(error.exceptions)
|
| 86 |
+
except error_classes as error:
|
| 87 |
+
self.errors.append(error)
|
| 88 |
+
|
| 89 |
+
def error(
|
| 90 |
+
self,
|
| 91 |
+
error: Exception,
|
| 92 |
+
) -> None:
|
| 93 |
+
"""Add an error to the list."""
|
| 94 |
+
self.errors.append(error)
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/licenses/__init__.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#######################################################################################
|
| 2 |
+
#
|
| 3 |
+
# Adapted from:
|
| 4 |
+
# https://github.com/pypa/hatch/blob/5352e44/backend/src/hatchling/licenses/parse.py
|
| 5 |
+
#
|
| 6 |
+
# MIT License
|
| 7 |
+
#
|
| 8 |
+
# Copyright (c) 2017-present Ofek Lev <oss@ofek.dev>
|
| 9 |
+
#
|
| 10 |
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
| 11 |
+
# software and associated documentation files (the "Software"), to deal in the Software
|
| 12 |
+
# without restriction, including without limitation the rights to use, copy, modify,
|
| 13 |
+
# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
| 14 |
+
# permit persons to whom the Software is furnished to do so, subject to the following
|
| 15 |
+
# conditions:
|
| 16 |
+
#
|
| 17 |
+
# The above copyright notice and this permission notice shall be included in all copies
|
| 18 |
+
# or substantial portions of the Software.
|
| 19 |
+
#
|
| 20 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
| 21 |
+
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
| 22 |
+
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
| 23 |
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
| 24 |
+
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
| 25 |
+
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 26 |
+
#
|
| 27 |
+
#
|
| 28 |
+
# With additional allowance of arbitrary `LicenseRef-` identifiers, not just
|
| 29 |
+
# `LicenseRef-Public-Domain` and `LicenseRef-Proprietary`.
|
| 30 |
+
#
|
| 31 |
+
#######################################################################################
|
| 32 |
+
from __future__ import annotations
|
| 33 |
+
|
| 34 |
+
import re
|
| 35 |
+
from typing import NewType, cast
|
| 36 |
+
|
| 37 |
+
from ._spdx import EXCEPTIONS, LICENSES
|
| 38 |
+
|
| 39 |
+
__all__ = [
|
| 40 |
+
"InvalidLicenseExpression",
|
| 41 |
+
"NormalizedLicenseExpression",
|
| 42 |
+
"canonicalize_license_expression",
|
| 43 |
+
]
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# Simple __dir__ implementation since there are no public submodules
|
| 47 |
+
def __dir__() -> list[str]:
|
| 48 |
+
return __all__
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$")
|
| 52 |
+
|
| 53 |
+
NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str)
|
| 54 |
+
"""
|
| 55 |
+
A :class:`typing.NewType` of :class:`str`, representing a normalized
|
| 56 |
+
License-Expression.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class InvalidLicenseExpression(ValueError):
|
| 61 |
+
"""Raised when a license-expression string is invalid
|
| 62 |
+
|
| 63 |
+
>>> from packaging.licenses import canonicalize_license_expression
|
| 64 |
+
>>> canonicalize_license_expression("invalid")
|
| 65 |
+
Traceback (most recent call last):
|
| 66 |
+
...
|
| 67 |
+
packaging.licenses.InvalidLicenseExpression: Invalid license expression: 'invalid'
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def canonicalize_license_expression(
|
| 72 |
+
raw_license_expression: str,
|
| 73 |
+
) -> NormalizedLicenseExpression:
|
| 74 |
+
"""
|
| 75 |
+
This function takes a valid License-Expression, and returns the normalized
|
| 76 |
+
form of it.
|
| 77 |
+
|
| 78 |
+
The return type is typed as :class:`NormalizedLicenseExpression`. This
|
| 79 |
+
allows type checkers to help require that a string has passed through this
|
| 80 |
+
function before use.
|
| 81 |
+
|
| 82 |
+
:param str raw_license_expression: The License-Expression to canonicalize.
|
| 83 |
+
:raises InvalidLicenseExpression: If the License-Expression is invalid due to an
|
| 84 |
+
invalid/unknown license identifier or invalid syntax.
|
| 85 |
+
|
| 86 |
+
.. doctest::
|
| 87 |
+
|
| 88 |
+
>>> from packaging.licenses import canonicalize_license_expression
|
| 89 |
+
>>> canonicalize_license_expression("mit")
|
| 90 |
+
'MIT'
|
| 91 |
+
>>> canonicalize_license_expression("mit and (apache-2.0 or bsd-2-clause)")
|
| 92 |
+
'MIT AND (Apache-2.0 OR BSD-2-Clause)'
|
| 93 |
+
>>> canonicalize_license_expression("(mit")
|
| 94 |
+
Traceback (most recent call last):
|
| 95 |
+
...
|
| 96 |
+
InvalidLicenseExpression: Invalid license expression: '(mit'
|
| 97 |
+
>>> canonicalize_license_expression("Use-it-after-midnight")
|
| 98 |
+
Traceback (most recent call last):
|
| 99 |
+
...
|
| 100 |
+
InvalidLicenseExpression: Unknown license: 'Use-it-after-midnight'
|
| 101 |
+
"""
|
| 102 |
+
if not raw_license_expression:
|
| 103 |
+
message = f"Invalid license expression: {raw_license_expression!r}"
|
| 104 |
+
raise InvalidLicenseExpression(message)
|
| 105 |
+
|
| 106 |
+
# Pad any parentheses so tokenization can be achieved by merely splitting on
|
| 107 |
+
# whitespace.
|
| 108 |
+
license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ")
|
| 109 |
+
licenseref_prefix = "LicenseRef-"
|
| 110 |
+
license_refs = {
|
| 111 |
+
ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :]
|
| 112 |
+
for ref in license_expression.split()
|
| 113 |
+
if ref.lower().startswith(licenseref_prefix.lower())
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
# Normalize to lower case so we can look up licenses/exceptions
|
| 117 |
+
# and so boolean operators are Python-compatible.
|
| 118 |
+
license_expression = license_expression.lower()
|
| 119 |
+
|
| 120 |
+
tokens = license_expression.split()
|
| 121 |
+
|
| 122 |
+
# Rather than implementing a parenthesis/boolean logic parser, create an
|
| 123 |
+
# expression that Python can parse. Everything that is not involved with the
|
| 124 |
+
# grammar itself is replaced with the placeholder `False` and the resultant
|
| 125 |
+
# expression should become a valid Python expression.
|
| 126 |
+
python_tokens = []
|
| 127 |
+
for token in tokens:
|
| 128 |
+
if token not in {"or", "and", "with", "(", ")"}:
|
| 129 |
+
python_tokens.append("False")
|
| 130 |
+
elif token == "with":
|
| 131 |
+
python_tokens.append("or")
|
| 132 |
+
elif (
|
| 133 |
+
token == "("
|
| 134 |
+
and python_tokens
|
| 135 |
+
and python_tokens[-1] not in {"or", "and", "("}
|
| 136 |
+
) or (token == ")" and python_tokens and python_tokens[-1] == "("):
|
| 137 |
+
message = f"Invalid license expression: {raw_license_expression!r}"
|
| 138 |
+
raise InvalidLicenseExpression(message)
|
| 139 |
+
else:
|
| 140 |
+
python_tokens.append(token)
|
| 141 |
+
|
| 142 |
+
python_expression = " ".join(python_tokens)
|
| 143 |
+
try:
|
| 144 |
+
compile(python_expression, "", "eval")
|
| 145 |
+
except SyntaxError:
|
| 146 |
+
message = f"Invalid license expression: {raw_license_expression!r}"
|
| 147 |
+
raise InvalidLicenseExpression(message) from None
|
| 148 |
+
|
| 149 |
+
# Take a final pass to check for unknown licenses/exceptions.
|
| 150 |
+
normalized_tokens = []
|
| 151 |
+
for token in tokens:
|
| 152 |
+
if token in {"or", "and", "with", "(", ")"}:
|
| 153 |
+
normalized_tokens.append(token.upper())
|
| 154 |
+
continue
|
| 155 |
+
|
| 156 |
+
if normalized_tokens and normalized_tokens[-1] == "WITH":
|
| 157 |
+
if token not in EXCEPTIONS:
|
| 158 |
+
message = f"Unknown license exception: {token!r}"
|
| 159 |
+
raise InvalidLicenseExpression(message)
|
| 160 |
+
|
| 161 |
+
normalized_tokens.append(EXCEPTIONS[token]["id"])
|
| 162 |
+
else:
|
| 163 |
+
if token.endswith("+"):
|
| 164 |
+
final_token = token[:-1]
|
| 165 |
+
suffix = "+"
|
| 166 |
+
else:
|
| 167 |
+
final_token = token
|
| 168 |
+
suffix = ""
|
| 169 |
+
|
| 170 |
+
if final_token.startswith("licenseref-"):
|
| 171 |
+
if not license_ref_allowed.match(final_token):
|
| 172 |
+
message = f"Invalid licenseref: {final_token!r}"
|
| 173 |
+
raise InvalidLicenseExpression(message)
|
| 174 |
+
normalized_tokens.append(license_refs[final_token] + suffix)
|
| 175 |
+
else:
|
| 176 |
+
if final_token not in LICENSES:
|
| 177 |
+
message = f"Unknown license: {final_token!r}"
|
| 178 |
+
raise InvalidLicenseExpression(message)
|
| 179 |
+
normalized_tokens.append(LICENSES[final_token]["id"] + suffix)
|
| 180 |
+
|
| 181 |
+
normalized_expression = " ".join(normalized_tokens)
|
| 182 |
+
|
| 183 |
+
return cast(
|
| 184 |
+
"NormalizedLicenseExpression",
|
| 185 |
+
normalized_expression.replace("( ", "(").replace(" )", ")"),
|
| 186 |
+
)
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/licenses/_spdx.py
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
from typing import TypedDict
|
| 5 |
+
|
| 6 |
+
class SPDXLicense(TypedDict):
|
| 7 |
+
id: str
|
| 8 |
+
deprecated: bool
|
| 9 |
+
|
| 10 |
+
class SPDXException(TypedDict):
|
| 11 |
+
id: str
|
| 12 |
+
deprecated: bool
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
VERSION = '3.27.0'
|
| 16 |
+
|
| 17 |
+
LICENSES: dict[str, SPDXLicense] = {
|
| 18 |
+
'0bsd': {'id': '0BSD', 'deprecated': False},
|
| 19 |
+
'3d-slicer-1.0': {'id': '3D-Slicer-1.0', 'deprecated': False},
|
| 20 |
+
'aal': {'id': 'AAL', 'deprecated': False},
|
| 21 |
+
'abstyles': {'id': 'Abstyles', 'deprecated': False},
|
| 22 |
+
'adacore-doc': {'id': 'AdaCore-doc', 'deprecated': False},
|
| 23 |
+
'adobe-2006': {'id': 'Adobe-2006', 'deprecated': False},
|
| 24 |
+
'adobe-display-postscript': {'id': 'Adobe-Display-PostScript', 'deprecated': False},
|
| 25 |
+
'adobe-glyph': {'id': 'Adobe-Glyph', 'deprecated': False},
|
| 26 |
+
'adobe-utopia': {'id': 'Adobe-Utopia', 'deprecated': False},
|
| 27 |
+
'adsl': {'id': 'ADSL', 'deprecated': False},
|
| 28 |
+
'afl-1.1': {'id': 'AFL-1.1', 'deprecated': False},
|
| 29 |
+
'afl-1.2': {'id': 'AFL-1.2', 'deprecated': False},
|
| 30 |
+
'afl-2.0': {'id': 'AFL-2.0', 'deprecated': False},
|
| 31 |
+
'afl-2.1': {'id': 'AFL-2.1', 'deprecated': False},
|
| 32 |
+
'afl-3.0': {'id': 'AFL-3.0', 'deprecated': False},
|
| 33 |
+
'afmparse': {'id': 'Afmparse', 'deprecated': False},
|
| 34 |
+
'agpl-1.0': {'id': 'AGPL-1.0', 'deprecated': True},
|
| 35 |
+
'agpl-1.0-only': {'id': 'AGPL-1.0-only', 'deprecated': False},
|
| 36 |
+
'agpl-1.0-or-later': {'id': 'AGPL-1.0-or-later', 'deprecated': False},
|
| 37 |
+
'agpl-3.0': {'id': 'AGPL-3.0', 'deprecated': True},
|
| 38 |
+
'agpl-3.0-only': {'id': 'AGPL-3.0-only', 'deprecated': False},
|
| 39 |
+
'agpl-3.0-or-later': {'id': 'AGPL-3.0-or-later', 'deprecated': False},
|
| 40 |
+
'aladdin': {'id': 'Aladdin', 'deprecated': False},
|
| 41 |
+
'amd-newlib': {'id': 'AMD-newlib', 'deprecated': False},
|
| 42 |
+
'amdplpa': {'id': 'AMDPLPA', 'deprecated': False},
|
| 43 |
+
'aml': {'id': 'AML', 'deprecated': False},
|
| 44 |
+
'aml-glslang': {'id': 'AML-glslang', 'deprecated': False},
|
| 45 |
+
'ampas': {'id': 'AMPAS', 'deprecated': False},
|
| 46 |
+
'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False},
|
| 47 |
+
'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False},
|
| 48 |
+
'any-osi': {'id': 'any-OSI', 'deprecated': False},
|
| 49 |
+
'any-osi-perl-modules': {'id': 'any-OSI-perl-modules', 'deprecated': False},
|
| 50 |
+
'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False},
|
| 51 |
+
'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False},
|
| 52 |
+
'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False},
|
| 53 |
+
'apafml': {'id': 'APAFML', 'deprecated': False},
|
| 54 |
+
'apl-1.0': {'id': 'APL-1.0', 'deprecated': False},
|
| 55 |
+
'app-s2p': {'id': 'App-s2p', 'deprecated': False},
|
| 56 |
+
'apsl-1.0': {'id': 'APSL-1.0', 'deprecated': False},
|
| 57 |
+
'apsl-1.1': {'id': 'APSL-1.1', 'deprecated': False},
|
| 58 |
+
'apsl-1.2': {'id': 'APSL-1.2', 'deprecated': False},
|
| 59 |
+
'apsl-2.0': {'id': 'APSL-2.0', 'deprecated': False},
|
| 60 |
+
'arphic-1999': {'id': 'Arphic-1999', 'deprecated': False},
|
| 61 |
+
'artistic-1.0': {'id': 'Artistic-1.0', 'deprecated': False},
|
| 62 |
+
'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False},
|
| 63 |
+
'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False},
|
| 64 |
+
'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False},
|
| 65 |
+
'artistic-dist': {'id': 'Artistic-dist', 'deprecated': False},
|
| 66 |
+
'aspell-ru': {'id': 'Aspell-RU', 'deprecated': False},
|
| 67 |
+
'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False},
|
| 68 |
+
'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False},
|
| 69 |
+
'baekmuk': {'id': 'Baekmuk', 'deprecated': False},
|
| 70 |
+
'bahyph': {'id': 'Bahyph', 'deprecated': False},
|
| 71 |
+
'barr': {'id': 'Barr', 'deprecated': False},
|
| 72 |
+
'bcrypt-solar-designer': {'id': 'bcrypt-Solar-Designer', 'deprecated': False},
|
| 73 |
+
'beerware': {'id': 'Beerware', 'deprecated': False},
|
| 74 |
+
'bitstream-charter': {'id': 'Bitstream-Charter', 'deprecated': False},
|
| 75 |
+
'bitstream-vera': {'id': 'Bitstream-Vera', 'deprecated': False},
|
| 76 |
+
'bittorrent-1.0': {'id': 'BitTorrent-1.0', 'deprecated': False},
|
| 77 |
+
'bittorrent-1.1': {'id': 'BitTorrent-1.1', 'deprecated': False},
|
| 78 |
+
'blessing': {'id': 'blessing', 'deprecated': False},
|
| 79 |
+
'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False},
|
| 80 |
+
'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False},
|
| 81 |
+
'boehm-gc-without-fee': {'id': 'Boehm-GC-without-fee', 'deprecated': False},
|
| 82 |
+
'borceux': {'id': 'Borceux', 'deprecated': False},
|
| 83 |
+
'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False},
|
| 84 |
+
'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False},
|
| 85 |
+
'bsd-1-clause': {'id': 'BSD-1-Clause', 'deprecated': False},
|
| 86 |
+
'bsd-2-clause': {'id': 'BSD-2-Clause', 'deprecated': False},
|
| 87 |
+
'bsd-2-clause-darwin': {'id': 'BSD-2-Clause-Darwin', 'deprecated': False},
|
| 88 |
+
'bsd-2-clause-first-lines': {'id': 'BSD-2-Clause-first-lines', 'deprecated': False},
|
| 89 |
+
'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True},
|
| 90 |
+
'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True},
|
| 91 |
+
'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False},
|
| 92 |
+
'bsd-2-clause-pkgconf-disclaimer': {'id': 'BSD-2-Clause-pkgconf-disclaimer', 'deprecated': False},
|
| 93 |
+
'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False},
|
| 94 |
+
'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False},
|
| 95 |
+
'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False},
|
| 96 |
+
'bsd-3-clause-attribution': {'id': 'BSD-3-Clause-Attribution', 'deprecated': False},
|
| 97 |
+
'bsd-3-clause-clear': {'id': 'BSD-3-Clause-Clear', 'deprecated': False},
|
| 98 |
+
'bsd-3-clause-flex': {'id': 'BSD-3-Clause-flex', 'deprecated': False},
|
| 99 |
+
'bsd-3-clause-hp': {'id': 'BSD-3-Clause-HP', 'deprecated': False},
|
| 100 |
+
'bsd-3-clause-lbnl': {'id': 'BSD-3-Clause-LBNL', 'deprecated': False},
|
| 101 |
+
'bsd-3-clause-modification': {'id': 'BSD-3-Clause-Modification', 'deprecated': False},
|
| 102 |
+
'bsd-3-clause-no-military-license': {'id': 'BSD-3-Clause-No-Military-License', 'deprecated': False},
|
| 103 |
+
'bsd-3-clause-no-nuclear-license': {'id': 'BSD-3-Clause-No-Nuclear-License', 'deprecated': False},
|
| 104 |
+
'bsd-3-clause-no-nuclear-license-2014': {'id': 'BSD-3-Clause-No-Nuclear-License-2014', 'deprecated': False},
|
| 105 |
+
'bsd-3-clause-no-nuclear-warranty': {'id': 'BSD-3-Clause-No-Nuclear-Warranty', 'deprecated': False},
|
| 106 |
+
'bsd-3-clause-open-mpi': {'id': 'BSD-3-Clause-Open-MPI', 'deprecated': False},
|
| 107 |
+
'bsd-3-clause-sun': {'id': 'BSD-3-Clause-Sun', 'deprecated': False},
|
| 108 |
+
'bsd-4-clause': {'id': 'BSD-4-Clause', 'deprecated': False},
|
| 109 |
+
'bsd-4-clause-shortened': {'id': 'BSD-4-Clause-Shortened', 'deprecated': False},
|
| 110 |
+
'bsd-4-clause-uc': {'id': 'BSD-4-Clause-UC', 'deprecated': False},
|
| 111 |
+
'bsd-4.3reno': {'id': 'BSD-4.3RENO', 'deprecated': False},
|
| 112 |
+
'bsd-4.3tahoe': {'id': 'BSD-4.3TAHOE', 'deprecated': False},
|
| 113 |
+
'bsd-advertising-acknowledgement': {'id': 'BSD-Advertising-Acknowledgement', 'deprecated': False},
|
| 114 |
+
'bsd-attribution-hpnd-disclaimer': {'id': 'BSD-Attribution-HPND-disclaimer', 'deprecated': False},
|
| 115 |
+
'bsd-inferno-nettverk': {'id': 'BSD-Inferno-Nettverk', 'deprecated': False},
|
| 116 |
+
'bsd-protection': {'id': 'BSD-Protection', 'deprecated': False},
|
| 117 |
+
'bsd-source-beginning-file': {'id': 'BSD-Source-beginning-file', 'deprecated': False},
|
| 118 |
+
'bsd-source-code': {'id': 'BSD-Source-Code', 'deprecated': False},
|
| 119 |
+
'bsd-systemics': {'id': 'BSD-Systemics', 'deprecated': False},
|
| 120 |
+
'bsd-systemics-w3works': {'id': 'BSD-Systemics-W3Works', 'deprecated': False},
|
| 121 |
+
'bsl-1.0': {'id': 'BSL-1.0', 'deprecated': False},
|
| 122 |
+
'busl-1.1': {'id': 'BUSL-1.1', 'deprecated': False},
|
| 123 |
+
'bzip2-1.0.5': {'id': 'bzip2-1.0.5', 'deprecated': True},
|
| 124 |
+
'bzip2-1.0.6': {'id': 'bzip2-1.0.6', 'deprecated': False},
|
| 125 |
+
'c-uda-1.0': {'id': 'C-UDA-1.0', 'deprecated': False},
|
| 126 |
+
'cal-1.0': {'id': 'CAL-1.0', 'deprecated': False},
|
| 127 |
+
'cal-1.0-combined-work-exception': {'id': 'CAL-1.0-Combined-Work-Exception', 'deprecated': False},
|
| 128 |
+
'caldera': {'id': 'Caldera', 'deprecated': False},
|
| 129 |
+
'caldera-no-preamble': {'id': 'Caldera-no-preamble', 'deprecated': False},
|
| 130 |
+
'catharon': {'id': 'Catharon', 'deprecated': False},
|
| 131 |
+
'catosl-1.1': {'id': 'CATOSL-1.1', 'deprecated': False},
|
| 132 |
+
'cc-by-1.0': {'id': 'CC-BY-1.0', 'deprecated': False},
|
| 133 |
+
'cc-by-2.0': {'id': 'CC-BY-2.0', 'deprecated': False},
|
| 134 |
+
'cc-by-2.5': {'id': 'CC-BY-2.5', 'deprecated': False},
|
| 135 |
+
'cc-by-2.5-au': {'id': 'CC-BY-2.5-AU', 'deprecated': False},
|
| 136 |
+
'cc-by-3.0': {'id': 'CC-BY-3.0', 'deprecated': False},
|
| 137 |
+
'cc-by-3.0-at': {'id': 'CC-BY-3.0-AT', 'deprecated': False},
|
| 138 |
+
'cc-by-3.0-au': {'id': 'CC-BY-3.0-AU', 'deprecated': False},
|
| 139 |
+
'cc-by-3.0-de': {'id': 'CC-BY-3.0-DE', 'deprecated': False},
|
| 140 |
+
'cc-by-3.0-igo': {'id': 'CC-BY-3.0-IGO', 'deprecated': False},
|
| 141 |
+
'cc-by-3.0-nl': {'id': 'CC-BY-3.0-NL', 'deprecated': False},
|
| 142 |
+
'cc-by-3.0-us': {'id': 'CC-BY-3.0-US', 'deprecated': False},
|
| 143 |
+
'cc-by-4.0': {'id': 'CC-BY-4.0', 'deprecated': False},
|
| 144 |
+
'cc-by-nc-1.0': {'id': 'CC-BY-NC-1.0', 'deprecated': False},
|
| 145 |
+
'cc-by-nc-2.0': {'id': 'CC-BY-NC-2.0', 'deprecated': False},
|
| 146 |
+
'cc-by-nc-2.5': {'id': 'CC-BY-NC-2.5', 'deprecated': False},
|
| 147 |
+
'cc-by-nc-3.0': {'id': 'CC-BY-NC-3.0', 'deprecated': False},
|
| 148 |
+
'cc-by-nc-3.0-de': {'id': 'CC-BY-NC-3.0-DE', 'deprecated': False},
|
| 149 |
+
'cc-by-nc-4.0': {'id': 'CC-BY-NC-4.0', 'deprecated': False},
|
| 150 |
+
'cc-by-nc-nd-1.0': {'id': 'CC-BY-NC-ND-1.0', 'deprecated': False},
|
| 151 |
+
'cc-by-nc-nd-2.0': {'id': 'CC-BY-NC-ND-2.0', 'deprecated': False},
|
| 152 |
+
'cc-by-nc-nd-2.5': {'id': 'CC-BY-NC-ND-2.5', 'deprecated': False},
|
| 153 |
+
'cc-by-nc-nd-3.0': {'id': 'CC-BY-NC-ND-3.0', 'deprecated': False},
|
| 154 |
+
'cc-by-nc-nd-3.0-de': {'id': 'CC-BY-NC-ND-3.0-DE', 'deprecated': False},
|
| 155 |
+
'cc-by-nc-nd-3.0-igo': {'id': 'CC-BY-NC-ND-3.0-IGO', 'deprecated': False},
|
| 156 |
+
'cc-by-nc-nd-4.0': {'id': 'CC-BY-NC-ND-4.0', 'deprecated': False},
|
| 157 |
+
'cc-by-nc-sa-1.0': {'id': 'CC-BY-NC-SA-1.0', 'deprecated': False},
|
| 158 |
+
'cc-by-nc-sa-2.0': {'id': 'CC-BY-NC-SA-2.0', 'deprecated': False},
|
| 159 |
+
'cc-by-nc-sa-2.0-de': {'id': 'CC-BY-NC-SA-2.0-DE', 'deprecated': False},
|
| 160 |
+
'cc-by-nc-sa-2.0-fr': {'id': 'CC-BY-NC-SA-2.0-FR', 'deprecated': False},
|
| 161 |
+
'cc-by-nc-sa-2.0-uk': {'id': 'CC-BY-NC-SA-2.0-UK', 'deprecated': False},
|
| 162 |
+
'cc-by-nc-sa-2.5': {'id': 'CC-BY-NC-SA-2.5', 'deprecated': False},
|
| 163 |
+
'cc-by-nc-sa-3.0': {'id': 'CC-BY-NC-SA-3.0', 'deprecated': False},
|
| 164 |
+
'cc-by-nc-sa-3.0-de': {'id': 'CC-BY-NC-SA-3.0-DE', 'deprecated': False},
|
| 165 |
+
'cc-by-nc-sa-3.0-igo': {'id': 'CC-BY-NC-SA-3.0-IGO', 'deprecated': False},
|
| 166 |
+
'cc-by-nc-sa-4.0': {'id': 'CC-BY-NC-SA-4.0', 'deprecated': False},
|
| 167 |
+
'cc-by-nd-1.0': {'id': 'CC-BY-ND-1.0', 'deprecated': False},
|
| 168 |
+
'cc-by-nd-2.0': {'id': 'CC-BY-ND-2.0', 'deprecated': False},
|
| 169 |
+
'cc-by-nd-2.5': {'id': 'CC-BY-ND-2.5', 'deprecated': False},
|
| 170 |
+
'cc-by-nd-3.0': {'id': 'CC-BY-ND-3.0', 'deprecated': False},
|
| 171 |
+
'cc-by-nd-3.0-de': {'id': 'CC-BY-ND-3.0-DE', 'deprecated': False},
|
| 172 |
+
'cc-by-nd-4.0': {'id': 'CC-BY-ND-4.0', 'deprecated': False},
|
| 173 |
+
'cc-by-sa-1.0': {'id': 'CC-BY-SA-1.0', 'deprecated': False},
|
| 174 |
+
'cc-by-sa-2.0': {'id': 'CC-BY-SA-2.0', 'deprecated': False},
|
| 175 |
+
'cc-by-sa-2.0-uk': {'id': 'CC-BY-SA-2.0-UK', 'deprecated': False},
|
| 176 |
+
'cc-by-sa-2.1-jp': {'id': 'CC-BY-SA-2.1-JP', 'deprecated': False},
|
| 177 |
+
'cc-by-sa-2.5': {'id': 'CC-BY-SA-2.5', 'deprecated': False},
|
| 178 |
+
'cc-by-sa-3.0': {'id': 'CC-BY-SA-3.0', 'deprecated': False},
|
| 179 |
+
'cc-by-sa-3.0-at': {'id': 'CC-BY-SA-3.0-AT', 'deprecated': False},
|
| 180 |
+
'cc-by-sa-3.0-de': {'id': 'CC-BY-SA-3.0-DE', 'deprecated': False},
|
| 181 |
+
'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False},
|
| 182 |
+
'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False},
|
| 183 |
+
'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False},
|
| 184 |
+
'cc-pdm-1.0': {'id': 'CC-PDM-1.0', 'deprecated': False},
|
| 185 |
+
'cc-sa-1.0': {'id': 'CC-SA-1.0', 'deprecated': False},
|
| 186 |
+
'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False},
|
| 187 |
+
'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False},
|
| 188 |
+
'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False},
|
| 189 |
+
'cdl-1.0': {'id': 'CDL-1.0', 'deprecated': False},
|
| 190 |
+
'cdla-permissive-1.0': {'id': 'CDLA-Permissive-1.0', 'deprecated': False},
|
| 191 |
+
'cdla-permissive-2.0': {'id': 'CDLA-Permissive-2.0', 'deprecated': False},
|
| 192 |
+
'cdla-sharing-1.0': {'id': 'CDLA-Sharing-1.0', 'deprecated': False},
|
| 193 |
+
'cecill-1.0': {'id': 'CECILL-1.0', 'deprecated': False},
|
| 194 |
+
'cecill-1.1': {'id': 'CECILL-1.1', 'deprecated': False},
|
| 195 |
+
'cecill-2.0': {'id': 'CECILL-2.0', 'deprecated': False},
|
| 196 |
+
'cecill-2.1': {'id': 'CECILL-2.1', 'deprecated': False},
|
| 197 |
+
'cecill-b': {'id': 'CECILL-B', 'deprecated': False},
|
| 198 |
+
'cecill-c': {'id': 'CECILL-C', 'deprecated': False},
|
| 199 |
+
'cern-ohl-1.1': {'id': 'CERN-OHL-1.1', 'deprecated': False},
|
| 200 |
+
'cern-ohl-1.2': {'id': 'CERN-OHL-1.2', 'deprecated': False},
|
| 201 |
+
'cern-ohl-p-2.0': {'id': 'CERN-OHL-P-2.0', 'deprecated': False},
|
| 202 |
+
'cern-ohl-s-2.0': {'id': 'CERN-OHL-S-2.0', 'deprecated': False},
|
| 203 |
+
'cern-ohl-w-2.0': {'id': 'CERN-OHL-W-2.0', 'deprecated': False},
|
| 204 |
+
'cfitsio': {'id': 'CFITSIO', 'deprecated': False},
|
| 205 |
+
'check-cvs': {'id': 'check-cvs', 'deprecated': False},
|
| 206 |
+
'checkmk': {'id': 'checkmk', 'deprecated': False},
|
| 207 |
+
'clartistic': {'id': 'ClArtistic', 'deprecated': False},
|
| 208 |
+
'clips': {'id': 'Clips', 'deprecated': False},
|
| 209 |
+
'cmu-mach': {'id': 'CMU-Mach', 'deprecated': False},
|
| 210 |
+
'cmu-mach-nodoc': {'id': 'CMU-Mach-nodoc', 'deprecated': False},
|
| 211 |
+
'cnri-jython': {'id': 'CNRI-Jython', 'deprecated': False},
|
| 212 |
+
'cnri-python': {'id': 'CNRI-Python', 'deprecated': False},
|
| 213 |
+
'cnri-python-gpl-compatible': {'id': 'CNRI-Python-GPL-Compatible', 'deprecated': False},
|
| 214 |
+
'coil-1.0': {'id': 'COIL-1.0', 'deprecated': False},
|
| 215 |
+
'community-spec-1.0': {'id': 'Community-Spec-1.0', 'deprecated': False},
|
| 216 |
+
'condor-1.1': {'id': 'Condor-1.1', 'deprecated': False},
|
| 217 |
+
'copyleft-next-0.3.0': {'id': 'copyleft-next-0.3.0', 'deprecated': False},
|
| 218 |
+
'copyleft-next-0.3.1': {'id': 'copyleft-next-0.3.1', 'deprecated': False},
|
| 219 |
+
'cornell-lossless-jpeg': {'id': 'Cornell-Lossless-JPEG', 'deprecated': False},
|
| 220 |
+
'cpal-1.0': {'id': 'CPAL-1.0', 'deprecated': False},
|
| 221 |
+
'cpl-1.0': {'id': 'CPL-1.0', 'deprecated': False},
|
| 222 |
+
'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False},
|
| 223 |
+
'cronyx': {'id': 'Cronyx', 'deprecated': False},
|
| 224 |
+
'crossword': {'id': 'Crossword', 'deprecated': False},
|
| 225 |
+
'cryptoswift': {'id': 'CryptoSwift', 'deprecated': False},
|
| 226 |
+
'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False},
|
| 227 |
+
'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False},
|
| 228 |
+
'cube': {'id': 'Cube', 'deprecated': False},
|
| 229 |
+
'curl': {'id': 'curl', 'deprecated': False},
|
| 230 |
+
'cve-tou': {'id': 'cve-tou', 'deprecated': False},
|
| 231 |
+
'd-fsl-1.0': {'id': 'D-FSL-1.0', 'deprecated': False},
|
| 232 |
+
'dec-3-clause': {'id': 'DEC-3-Clause', 'deprecated': False},
|
| 233 |
+
'diffmark': {'id': 'diffmark', 'deprecated': False},
|
| 234 |
+
'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False},
|
| 235 |
+
'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False},
|
| 236 |
+
'doc': {'id': 'DOC', 'deprecated': False},
|
| 237 |
+
'docbook-dtd': {'id': 'DocBook-DTD', 'deprecated': False},
|
| 238 |
+
'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False},
|
| 239 |
+
'docbook-stylesheet': {'id': 'DocBook-Stylesheet', 'deprecated': False},
|
| 240 |
+
'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False},
|
| 241 |
+
'dotseqn': {'id': 'Dotseqn', 'deprecated': False},
|
| 242 |
+
'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False},
|
| 243 |
+
'drl-1.1': {'id': 'DRL-1.1', 'deprecated': False},
|
| 244 |
+
'dsdp': {'id': 'DSDP', 'deprecated': False},
|
| 245 |
+
'dtoa': {'id': 'dtoa', 'deprecated': False},
|
| 246 |
+
'dvipdfm': {'id': 'dvipdfm', 'deprecated': False},
|
| 247 |
+
'ecl-1.0': {'id': 'ECL-1.0', 'deprecated': False},
|
| 248 |
+
'ecl-2.0': {'id': 'ECL-2.0', 'deprecated': False},
|
| 249 |
+
'ecos-2.0': {'id': 'eCos-2.0', 'deprecated': True},
|
| 250 |
+
'efl-1.0': {'id': 'EFL-1.0', 'deprecated': False},
|
| 251 |
+
'efl-2.0': {'id': 'EFL-2.0', 'deprecated': False},
|
| 252 |
+
'egenix': {'id': 'eGenix', 'deprecated': False},
|
| 253 |
+
'elastic-2.0': {'id': 'Elastic-2.0', 'deprecated': False},
|
| 254 |
+
'entessa': {'id': 'Entessa', 'deprecated': False},
|
| 255 |
+
'epics': {'id': 'EPICS', 'deprecated': False},
|
| 256 |
+
'epl-1.0': {'id': 'EPL-1.0', 'deprecated': False},
|
| 257 |
+
'epl-2.0': {'id': 'EPL-2.0', 'deprecated': False},
|
| 258 |
+
'erlpl-1.1': {'id': 'ErlPL-1.1', 'deprecated': False},
|
| 259 |
+
'etalab-2.0': {'id': 'etalab-2.0', 'deprecated': False},
|
| 260 |
+
'eudatagrid': {'id': 'EUDatagrid', 'deprecated': False},
|
| 261 |
+
'eupl-1.0': {'id': 'EUPL-1.0', 'deprecated': False},
|
| 262 |
+
'eupl-1.1': {'id': 'EUPL-1.1', 'deprecated': False},
|
| 263 |
+
'eupl-1.2': {'id': 'EUPL-1.2', 'deprecated': False},
|
| 264 |
+
'eurosym': {'id': 'Eurosym', 'deprecated': False},
|
| 265 |
+
'fair': {'id': 'Fair', 'deprecated': False},
|
| 266 |
+
'fbm': {'id': 'FBM', 'deprecated': False},
|
| 267 |
+
'fdk-aac': {'id': 'FDK-AAC', 'deprecated': False},
|
| 268 |
+
'ferguson-twofish': {'id': 'Ferguson-Twofish', 'deprecated': False},
|
| 269 |
+
'frameworx-1.0': {'id': 'Frameworx-1.0', 'deprecated': False},
|
| 270 |
+
'freebsd-doc': {'id': 'FreeBSD-DOC', 'deprecated': False},
|
| 271 |
+
'freeimage': {'id': 'FreeImage', 'deprecated': False},
|
| 272 |
+
'fsfap': {'id': 'FSFAP', 'deprecated': False},
|
| 273 |
+
'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False},
|
| 274 |
+
'fsful': {'id': 'FSFUL', 'deprecated': False},
|
| 275 |
+
'fsfullr': {'id': 'FSFULLR', 'deprecated': False},
|
| 276 |
+
'fsfullrsd': {'id': 'FSFULLRSD', 'deprecated': False},
|
| 277 |
+
'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False},
|
| 278 |
+
'fsl-1.1-alv2': {'id': 'FSL-1.1-ALv2', 'deprecated': False},
|
| 279 |
+
'fsl-1.1-mit': {'id': 'FSL-1.1-MIT', 'deprecated': False},
|
| 280 |
+
'ftl': {'id': 'FTL', 'deprecated': False},
|
| 281 |
+
'furuseth': {'id': 'Furuseth', 'deprecated': False},
|
| 282 |
+
'fwlw': {'id': 'fwlw', 'deprecated': False},
|
| 283 |
+
'game-programming-gems': {'id': 'Game-Programming-Gems', 'deprecated': False},
|
| 284 |
+
'gcr-docs': {'id': 'GCR-docs', 'deprecated': False},
|
| 285 |
+
'gd': {'id': 'GD', 'deprecated': False},
|
| 286 |
+
'generic-xts': {'id': 'generic-xts', 'deprecated': False},
|
| 287 |
+
'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True},
|
| 288 |
+
'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False},
|
| 289 |
+
'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False},
|
| 290 |
+
'gfdl-1.1-no-invariants-only': {'id': 'GFDL-1.1-no-invariants-only', 'deprecated': False},
|
| 291 |
+
'gfdl-1.1-no-invariants-or-later': {'id': 'GFDL-1.1-no-invariants-or-later', 'deprecated': False},
|
| 292 |
+
'gfdl-1.1-only': {'id': 'GFDL-1.1-only', 'deprecated': False},
|
| 293 |
+
'gfdl-1.1-or-later': {'id': 'GFDL-1.1-or-later', 'deprecated': False},
|
| 294 |
+
'gfdl-1.2': {'id': 'GFDL-1.2', 'deprecated': True},
|
| 295 |
+
'gfdl-1.2-invariants-only': {'id': 'GFDL-1.2-invariants-only', 'deprecated': False},
|
| 296 |
+
'gfdl-1.2-invariants-or-later': {'id': 'GFDL-1.2-invariants-or-later', 'deprecated': False},
|
| 297 |
+
'gfdl-1.2-no-invariants-only': {'id': 'GFDL-1.2-no-invariants-only', 'deprecated': False},
|
| 298 |
+
'gfdl-1.2-no-invariants-or-later': {'id': 'GFDL-1.2-no-invariants-or-later', 'deprecated': False},
|
| 299 |
+
'gfdl-1.2-only': {'id': 'GFDL-1.2-only', 'deprecated': False},
|
| 300 |
+
'gfdl-1.2-or-later': {'id': 'GFDL-1.2-or-later', 'deprecated': False},
|
| 301 |
+
'gfdl-1.3': {'id': 'GFDL-1.3', 'deprecated': True},
|
| 302 |
+
'gfdl-1.3-invariants-only': {'id': 'GFDL-1.3-invariants-only', 'deprecated': False},
|
| 303 |
+
'gfdl-1.3-invariants-or-later': {'id': 'GFDL-1.3-invariants-or-later', 'deprecated': False},
|
| 304 |
+
'gfdl-1.3-no-invariants-only': {'id': 'GFDL-1.3-no-invariants-only', 'deprecated': False},
|
| 305 |
+
'gfdl-1.3-no-invariants-or-later': {'id': 'GFDL-1.3-no-invariants-or-later', 'deprecated': False},
|
| 306 |
+
'gfdl-1.3-only': {'id': 'GFDL-1.3-only', 'deprecated': False},
|
| 307 |
+
'gfdl-1.3-or-later': {'id': 'GFDL-1.3-or-later', 'deprecated': False},
|
| 308 |
+
'giftware': {'id': 'Giftware', 'deprecated': False},
|
| 309 |
+
'gl2ps': {'id': 'GL2PS', 'deprecated': False},
|
| 310 |
+
'glide': {'id': 'Glide', 'deprecated': False},
|
| 311 |
+
'glulxe': {'id': 'Glulxe', 'deprecated': False},
|
| 312 |
+
'glwtpl': {'id': 'GLWTPL', 'deprecated': False},
|
| 313 |
+
'gnuplot': {'id': 'gnuplot', 'deprecated': False},
|
| 314 |
+
'gpl-1.0': {'id': 'GPL-1.0', 'deprecated': True},
|
| 315 |
+
'gpl-1.0+': {'id': 'GPL-1.0+', 'deprecated': True},
|
| 316 |
+
'gpl-1.0-only': {'id': 'GPL-1.0-only', 'deprecated': False},
|
| 317 |
+
'gpl-1.0-or-later': {'id': 'GPL-1.0-or-later', 'deprecated': False},
|
| 318 |
+
'gpl-2.0': {'id': 'GPL-2.0', 'deprecated': True},
|
| 319 |
+
'gpl-2.0+': {'id': 'GPL-2.0+', 'deprecated': True},
|
| 320 |
+
'gpl-2.0-only': {'id': 'GPL-2.0-only', 'deprecated': False},
|
| 321 |
+
'gpl-2.0-or-later': {'id': 'GPL-2.0-or-later', 'deprecated': False},
|
| 322 |
+
'gpl-2.0-with-autoconf-exception': {'id': 'GPL-2.0-with-autoconf-exception', 'deprecated': True},
|
| 323 |
+
'gpl-2.0-with-bison-exception': {'id': 'GPL-2.0-with-bison-exception', 'deprecated': True},
|
| 324 |
+
'gpl-2.0-with-classpath-exception': {'id': 'GPL-2.0-with-classpath-exception', 'deprecated': True},
|
| 325 |
+
'gpl-2.0-with-font-exception': {'id': 'GPL-2.0-with-font-exception', 'deprecated': True},
|
| 326 |
+
'gpl-2.0-with-gcc-exception': {'id': 'GPL-2.0-with-GCC-exception', 'deprecated': True},
|
| 327 |
+
'gpl-3.0': {'id': 'GPL-3.0', 'deprecated': True},
|
| 328 |
+
'gpl-3.0+': {'id': 'GPL-3.0+', 'deprecated': True},
|
| 329 |
+
'gpl-3.0-only': {'id': 'GPL-3.0-only', 'deprecated': False},
|
| 330 |
+
'gpl-3.0-or-later': {'id': 'GPL-3.0-or-later', 'deprecated': False},
|
| 331 |
+
'gpl-3.0-with-autoconf-exception': {'id': 'GPL-3.0-with-autoconf-exception', 'deprecated': True},
|
| 332 |
+
'gpl-3.0-with-gcc-exception': {'id': 'GPL-3.0-with-GCC-exception', 'deprecated': True},
|
| 333 |
+
'graphics-gems': {'id': 'Graphics-Gems', 'deprecated': False},
|
| 334 |
+
'gsoap-1.3b': {'id': 'gSOAP-1.3b', 'deprecated': False},
|
| 335 |
+
'gtkbook': {'id': 'gtkbook', 'deprecated': False},
|
| 336 |
+
'gutmann': {'id': 'Gutmann', 'deprecated': False},
|
| 337 |
+
'haskellreport': {'id': 'HaskellReport', 'deprecated': False},
|
| 338 |
+
'hdf5': {'id': 'HDF5', 'deprecated': False},
|
| 339 |
+
'hdparm': {'id': 'hdparm', 'deprecated': False},
|
| 340 |
+
'hidapi': {'id': 'HIDAPI', 'deprecated': False},
|
| 341 |
+
'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False},
|
| 342 |
+
'hp-1986': {'id': 'HP-1986', 'deprecated': False},
|
| 343 |
+
'hp-1989': {'id': 'HP-1989', 'deprecated': False},
|
| 344 |
+
'hpnd': {'id': 'HPND', 'deprecated': False},
|
| 345 |
+
'hpnd-dec': {'id': 'HPND-DEC', 'deprecated': False},
|
| 346 |
+
'hpnd-doc': {'id': 'HPND-doc', 'deprecated': False},
|
| 347 |
+
'hpnd-doc-sell': {'id': 'HPND-doc-sell', 'deprecated': False},
|
| 348 |
+
'hpnd-export-us': {'id': 'HPND-export-US', 'deprecated': False},
|
| 349 |
+
'hpnd-export-us-acknowledgement': {'id': 'HPND-export-US-acknowledgement', 'deprecated': False},
|
| 350 |
+
'hpnd-export-us-modify': {'id': 'HPND-export-US-modify', 'deprecated': False},
|
| 351 |
+
'hpnd-export2-us': {'id': 'HPND-export2-US', 'deprecated': False},
|
| 352 |
+
'hpnd-fenneberg-livingston': {'id': 'HPND-Fenneberg-Livingston', 'deprecated': False},
|
| 353 |
+
'hpnd-inria-imag': {'id': 'HPND-INRIA-IMAG', 'deprecated': False},
|
| 354 |
+
'hpnd-intel': {'id': 'HPND-Intel', 'deprecated': False},
|
| 355 |
+
'hpnd-kevlin-henney': {'id': 'HPND-Kevlin-Henney', 'deprecated': False},
|
| 356 |
+
'hpnd-markus-kuhn': {'id': 'HPND-Markus-Kuhn', 'deprecated': False},
|
| 357 |
+
'hpnd-merchantability-variant': {'id': 'HPND-merchantability-variant', 'deprecated': False},
|
| 358 |
+
'hpnd-mit-disclaimer': {'id': 'HPND-MIT-disclaimer', 'deprecated': False},
|
| 359 |
+
'hpnd-netrek': {'id': 'HPND-Netrek', 'deprecated': False},
|
| 360 |
+
'hpnd-pbmplus': {'id': 'HPND-Pbmplus', 'deprecated': False},
|
| 361 |
+
'hpnd-sell-mit-disclaimer-xserver': {'id': 'HPND-sell-MIT-disclaimer-xserver', 'deprecated': False},
|
| 362 |
+
'hpnd-sell-regexpr': {'id': 'HPND-sell-regexpr', 'deprecated': False},
|
| 363 |
+
'hpnd-sell-variant': {'id': 'HPND-sell-variant', 'deprecated': False},
|
| 364 |
+
'hpnd-sell-variant-mit-disclaimer': {'id': 'HPND-sell-variant-MIT-disclaimer', 'deprecated': False},
|
| 365 |
+
'hpnd-sell-variant-mit-disclaimer-rev': {'id': 'HPND-sell-variant-MIT-disclaimer-rev', 'deprecated': False},
|
| 366 |
+
'hpnd-uc': {'id': 'HPND-UC', 'deprecated': False},
|
| 367 |
+
'hpnd-uc-export-us': {'id': 'HPND-UC-export-US', 'deprecated': False},
|
| 368 |
+
'htmltidy': {'id': 'HTMLTIDY', 'deprecated': False},
|
| 369 |
+
'ibm-pibs': {'id': 'IBM-pibs', 'deprecated': False},
|
| 370 |
+
'icu': {'id': 'ICU', 'deprecated': False},
|
| 371 |
+
'iec-code-components-eula': {'id': 'IEC-Code-Components-EULA', 'deprecated': False},
|
| 372 |
+
'ijg': {'id': 'IJG', 'deprecated': False},
|
| 373 |
+
'ijg-short': {'id': 'IJG-short', 'deprecated': False},
|
| 374 |
+
'imagemagick': {'id': 'ImageMagick', 'deprecated': False},
|
| 375 |
+
'imatix': {'id': 'iMatix', 'deprecated': False},
|
| 376 |
+
'imlib2': {'id': 'Imlib2', 'deprecated': False},
|
| 377 |
+
'info-zip': {'id': 'Info-ZIP', 'deprecated': False},
|
| 378 |
+
'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False},
|
| 379 |
+
'innosetup': {'id': 'InnoSetup', 'deprecated': False},
|
| 380 |
+
'intel': {'id': 'Intel', 'deprecated': False},
|
| 381 |
+
'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False},
|
| 382 |
+
'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False},
|
| 383 |
+
'ipa': {'id': 'IPA', 'deprecated': False},
|
| 384 |
+
'ipl-1.0': {'id': 'IPL-1.0', 'deprecated': False},
|
| 385 |
+
'isc': {'id': 'ISC', 'deprecated': False},
|
| 386 |
+
'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False},
|
| 387 |
+
'jam': {'id': 'Jam', 'deprecated': False},
|
| 388 |
+
'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False},
|
| 389 |
+
'jove': {'id': 'jove', 'deprecated': False},
|
| 390 |
+
'jpl-image': {'id': 'JPL-image', 'deprecated': False},
|
| 391 |
+
'jpnic': {'id': 'JPNIC', 'deprecated': False},
|
| 392 |
+
'json': {'id': 'JSON', 'deprecated': False},
|
| 393 |
+
'kastrup': {'id': 'Kastrup', 'deprecated': False},
|
| 394 |
+
'kazlib': {'id': 'Kazlib', 'deprecated': False},
|
| 395 |
+
'knuth-ctan': {'id': 'Knuth-CTAN', 'deprecated': False},
|
| 396 |
+
'lal-1.2': {'id': 'LAL-1.2', 'deprecated': False},
|
| 397 |
+
'lal-1.3': {'id': 'LAL-1.3', 'deprecated': False},
|
| 398 |
+
'latex2e': {'id': 'Latex2e', 'deprecated': False},
|
| 399 |
+
'latex2e-translated-notice': {'id': 'Latex2e-translated-notice', 'deprecated': False},
|
| 400 |
+
'leptonica': {'id': 'Leptonica', 'deprecated': False},
|
| 401 |
+
'lgpl-2.0': {'id': 'LGPL-2.0', 'deprecated': True},
|
| 402 |
+
'lgpl-2.0+': {'id': 'LGPL-2.0+', 'deprecated': True},
|
| 403 |
+
'lgpl-2.0-only': {'id': 'LGPL-2.0-only', 'deprecated': False},
|
| 404 |
+
'lgpl-2.0-or-later': {'id': 'LGPL-2.0-or-later', 'deprecated': False},
|
| 405 |
+
'lgpl-2.1': {'id': 'LGPL-2.1', 'deprecated': True},
|
| 406 |
+
'lgpl-2.1+': {'id': 'LGPL-2.1+', 'deprecated': True},
|
| 407 |
+
'lgpl-2.1-only': {'id': 'LGPL-2.1-only', 'deprecated': False},
|
| 408 |
+
'lgpl-2.1-or-later': {'id': 'LGPL-2.1-or-later', 'deprecated': False},
|
| 409 |
+
'lgpl-3.0': {'id': 'LGPL-3.0', 'deprecated': True},
|
| 410 |
+
'lgpl-3.0+': {'id': 'LGPL-3.0+', 'deprecated': True},
|
| 411 |
+
'lgpl-3.0-only': {'id': 'LGPL-3.0-only', 'deprecated': False},
|
| 412 |
+
'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False},
|
| 413 |
+
'lgpllr': {'id': 'LGPLLR', 'deprecated': False},
|
| 414 |
+
'libpng': {'id': 'Libpng', 'deprecated': False},
|
| 415 |
+
'libpng-1.6.35': {'id': 'libpng-1.6.35', 'deprecated': False},
|
| 416 |
+
'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False},
|
| 417 |
+
'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False},
|
| 418 |
+
'libtiff': {'id': 'libtiff', 'deprecated': False},
|
| 419 |
+
'libutil-david-nugent': {'id': 'libutil-David-Nugent', 'deprecated': False},
|
| 420 |
+
'liliq-p-1.1': {'id': 'LiLiQ-P-1.1', 'deprecated': False},
|
| 421 |
+
'liliq-r-1.1': {'id': 'LiLiQ-R-1.1', 'deprecated': False},
|
| 422 |
+
'liliq-rplus-1.1': {'id': 'LiLiQ-Rplus-1.1', 'deprecated': False},
|
| 423 |
+
'linux-man-pages-1-para': {'id': 'Linux-man-pages-1-para', 'deprecated': False},
|
| 424 |
+
'linux-man-pages-copyleft': {'id': 'Linux-man-pages-copyleft', 'deprecated': False},
|
| 425 |
+
'linux-man-pages-copyleft-2-para': {'id': 'Linux-man-pages-copyleft-2-para', 'deprecated': False},
|
| 426 |
+
'linux-man-pages-copyleft-var': {'id': 'Linux-man-pages-copyleft-var', 'deprecated': False},
|
| 427 |
+
'linux-openib': {'id': 'Linux-OpenIB', 'deprecated': False},
|
| 428 |
+
'loop': {'id': 'LOOP', 'deprecated': False},
|
| 429 |
+
'lpd-document': {'id': 'LPD-document', 'deprecated': False},
|
| 430 |
+
'lpl-1.0': {'id': 'LPL-1.0', 'deprecated': False},
|
| 431 |
+
'lpl-1.02': {'id': 'LPL-1.02', 'deprecated': False},
|
| 432 |
+
'lppl-1.0': {'id': 'LPPL-1.0', 'deprecated': False},
|
| 433 |
+
'lppl-1.1': {'id': 'LPPL-1.1', 'deprecated': False},
|
| 434 |
+
'lppl-1.2': {'id': 'LPPL-1.2', 'deprecated': False},
|
| 435 |
+
'lppl-1.3a': {'id': 'LPPL-1.3a', 'deprecated': False},
|
| 436 |
+
'lppl-1.3c': {'id': 'LPPL-1.3c', 'deprecated': False},
|
| 437 |
+
'lsof': {'id': 'lsof', 'deprecated': False},
|
| 438 |
+
'lucida-bitmap-fonts': {'id': 'Lucida-Bitmap-Fonts', 'deprecated': False},
|
| 439 |
+
'lzma-sdk-9.11-to-9.20': {'id': 'LZMA-SDK-9.11-to-9.20', 'deprecated': False},
|
| 440 |
+
'lzma-sdk-9.22': {'id': 'LZMA-SDK-9.22', 'deprecated': False},
|
| 441 |
+
'mackerras-3-clause': {'id': 'Mackerras-3-Clause', 'deprecated': False},
|
| 442 |
+
'mackerras-3-clause-acknowledgment': {'id': 'Mackerras-3-Clause-acknowledgment', 'deprecated': False},
|
| 443 |
+
'magaz': {'id': 'magaz', 'deprecated': False},
|
| 444 |
+
'mailprio': {'id': 'mailprio', 'deprecated': False},
|
| 445 |
+
'makeindex': {'id': 'MakeIndex', 'deprecated': False},
|
| 446 |
+
'man2html': {'id': 'man2html', 'deprecated': False},
|
| 447 |
+
'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False},
|
| 448 |
+
'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False},
|
| 449 |
+
'metamail': {'id': 'metamail', 'deprecated': False},
|
| 450 |
+
'minpack': {'id': 'Minpack', 'deprecated': False},
|
| 451 |
+
'mips': {'id': 'MIPS', 'deprecated': False},
|
| 452 |
+
'miros': {'id': 'MirOS', 'deprecated': False},
|
| 453 |
+
'mit': {'id': 'MIT', 'deprecated': False},
|
| 454 |
+
'mit-0': {'id': 'MIT-0', 'deprecated': False},
|
| 455 |
+
'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False},
|
| 456 |
+
'mit-click': {'id': 'MIT-Click', 'deprecated': False},
|
| 457 |
+
'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False},
|
| 458 |
+
'mit-enna': {'id': 'MIT-enna', 'deprecated': False},
|
| 459 |
+
'mit-feh': {'id': 'MIT-feh', 'deprecated': False},
|
| 460 |
+
'mit-festival': {'id': 'MIT-Festival', 'deprecated': False},
|
| 461 |
+
'mit-khronos-old': {'id': 'MIT-Khronos-old', 'deprecated': False},
|
| 462 |
+
'mit-modern-variant': {'id': 'MIT-Modern-Variant', 'deprecated': False},
|
| 463 |
+
'mit-open-group': {'id': 'MIT-open-group', 'deprecated': False},
|
| 464 |
+
'mit-testregex': {'id': 'MIT-testregex', 'deprecated': False},
|
| 465 |
+
'mit-wu': {'id': 'MIT-Wu', 'deprecated': False},
|
| 466 |
+
'mitnfa': {'id': 'MITNFA', 'deprecated': False},
|
| 467 |
+
'mmixware': {'id': 'MMIXware', 'deprecated': False},
|
| 468 |
+
'motosoto': {'id': 'Motosoto', 'deprecated': False},
|
| 469 |
+
'mpeg-ssg': {'id': 'MPEG-SSG', 'deprecated': False},
|
| 470 |
+
'mpi-permissive': {'id': 'mpi-permissive', 'deprecated': False},
|
| 471 |
+
'mpich2': {'id': 'mpich2', 'deprecated': False},
|
| 472 |
+
'mpl-1.0': {'id': 'MPL-1.0', 'deprecated': False},
|
| 473 |
+
'mpl-1.1': {'id': 'MPL-1.1', 'deprecated': False},
|
| 474 |
+
'mpl-2.0': {'id': 'MPL-2.0', 'deprecated': False},
|
| 475 |
+
'mpl-2.0-no-copyleft-exception': {'id': 'MPL-2.0-no-copyleft-exception', 'deprecated': False},
|
| 476 |
+
'mplus': {'id': 'mplus', 'deprecated': False},
|
| 477 |
+
'ms-lpl': {'id': 'MS-LPL', 'deprecated': False},
|
| 478 |
+
'ms-pl': {'id': 'MS-PL', 'deprecated': False},
|
| 479 |
+
'ms-rl': {'id': 'MS-RL', 'deprecated': False},
|
| 480 |
+
'mtll': {'id': 'MTLL', 'deprecated': False},
|
| 481 |
+
'mulanpsl-1.0': {'id': 'MulanPSL-1.0', 'deprecated': False},
|
| 482 |
+
'mulanpsl-2.0': {'id': 'MulanPSL-2.0', 'deprecated': False},
|
| 483 |
+
'multics': {'id': 'Multics', 'deprecated': False},
|
| 484 |
+
'mup': {'id': 'Mup', 'deprecated': False},
|
| 485 |
+
'naist-2003': {'id': 'NAIST-2003', 'deprecated': False},
|
| 486 |
+
'nasa-1.3': {'id': 'NASA-1.3', 'deprecated': False},
|
| 487 |
+
'naumen': {'id': 'Naumen', 'deprecated': False},
|
| 488 |
+
'nbpl-1.0': {'id': 'NBPL-1.0', 'deprecated': False},
|
| 489 |
+
'ncbi-pd': {'id': 'NCBI-PD', 'deprecated': False},
|
| 490 |
+
'ncgl-uk-2.0': {'id': 'NCGL-UK-2.0', 'deprecated': False},
|
| 491 |
+
'ncl': {'id': 'NCL', 'deprecated': False},
|
| 492 |
+
'ncsa': {'id': 'NCSA', 'deprecated': False},
|
| 493 |
+
'net-snmp': {'id': 'Net-SNMP', 'deprecated': True},
|
| 494 |
+
'netcdf': {'id': 'NetCDF', 'deprecated': False},
|
| 495 |
+
'newsletr': {'id': 'Newsletr', 'deprecated': False},
|
| 496 |
+
'ngpl': {'id': 'NGPL', 'deprecated': False},
|
| 497 |
+
'ngrep': {'id': 'ngrep', 'deprecated': False},
|
| 498 |
+
'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False},
|
| 499 |
+
'nist-pd': {'id': 'NIST-PD', 'deprecated': False},
|
| 500 |
+
'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False},
|
| 501 |
+
'nist-software': {'id': 'NIST-Software', 'deprecated': False},
|
| 502 |
+
'nlod-1.0': {'id': 'NLOD-1.0', 'deprecated': False},
|
| 503 |
+
'nlod-2.0': {'id': 'NLOD-2.0', 'deprecated': False},
|
| 504 |
+
'nlpl': {'id': 'NLPL', 'deprecated': False},
|
| 505 |
+
'nokia': {'id': 'Nokia', 'deprecated': False},
|
| 506 |
+
'nosl': {'id': 'NOSL', 'deprecated': False},
|
| 507 |
+
'noweb': {'id': 'Noweb', 'deprecated': False},
|
| 508 |
+
'npl-1.0': {'id': 'NPL-1.0', 'deprecated': False},
|
| 509 |
+
'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False},
|
| 510 |
+
'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False},
|
| 511 |
+
'nrl': {'id': 'NRL', 'deprecated': False},
|
| 512 |
+
'ntia-pd': {'id': 'NTIA-PD', 'deprecated': False},
|
| 513 |
+
'ntp': {'id': 'NTP', 'deprecated': False},
|
| 514 |
+
'ntp-0': {'id': 'NTP-0', 'deprecated': False},
|
| 515 |
+
'nunit': {'id': 'Nunit', 'deprecated': True},
|
| 516 |
+
'o-uda-1.0': {'id': 'O-UDA-1.0', 'deprecated': False},
|
| 517 |
+
'oar': {'id': 'OAR', 'deprecated': False},
|
| 518 |
+
'occt-pl': {'id': 'OCCT-PL', 'deprecated': False},
|
| 519 |
+
'oclc-2.0': {'id': 'OCLC-2.0', 'deprecated': False},
|
| 520 |
+
'odbl-1.0': {'id': 'ODbL-1.0', 'deprecated': False},
|
| 521 |
+
'odc-by-1.0': {'id': 'ODC-By-1.0', 'deprecated': False},
|
| 522 |
+
'offis': {'id': 'OFFIS', 'deprecated': False},
|
| 523 |
+
'ofl-1.0': {'id': 'OFL-1.0', 'deprecated': False},
|
| 524 |
+
'ofl-1.0-no-rfn': {'id': 'OFL-1.0-no-RFN', 'deprecated': False},
|
| 525 |
+
'ofl-1.0-rfn': {'id': 'OFL-1.0-RFN', 'deprecated': False},
|
| 526 |
+
'ofl-1.1': {'id': 'OFL-1.1', 'deprecated': False},
|
| 527 |
+
'ofl-1.1-no-rfn': {'id': 'OFL-1.1-no-RFN', 'deprecated': False},
|
| 528 |
+
'ofl-1.1-rfn': {'id': 'OFL-1.1-RFN', 'deprecated': False},
|
| 529 |
+
'ogc-1.0': {'id': 'OGC-1.0', 'deprecated': False},
|
| 530 |
+
'ogdl-taiwan-1.0': {'id': 'OGDL-Taiwan-1.0', 'deprecated': False},
|
| 531 |
+
'ogl-canada-2.0': {'id': 'OGL-Canada-2.0', 'deprecated': False},
|
| 532 |
+
'ogl-uk-1.0': {'id': 'OGL-UK-1.0', 'deprecated': False},
|
| 533 |
+
'ogl-uk-2.0': {'id': 'OGL-UK-2.0', 'deprecated': False},
|
| 534 |
+
'ogl-uk-3.0': {'id': 'OGL-UK-3.0', 'deprecated': False},
|
| 535 |
+
'ogtsl': {'id': 'OGTSL', 'deprecated': False},
|
| 536 |
+
'oldap-1.1': {'id': 'OLDAP-1.1', 'deprecated': False},
|
| 537 |
+
'oldap-1.2': {'id': 'OLDAP-1.2', 'deprecated': False},
|
| 538 |
+
'oldap-1.3': {'id': 'OLDAP-1.3', 'deprecated': False},
|
| 539 |
+
'oldap-1.4': {'id': 'OLDAP-1.4', 'deprecated': False},
|
| 540 |
+
'oldap-2.0': {'id': 'OLDAP-2.0', 'deprecated': False},
|
| 541 |
+
'oldap-2.0.1': {'id': 'OLDAP-2.0.1', 'deprecated': False},
|
| 542 |
+
'oldap-2.1': {'id': 'OLDAP-2.1', 'deprecated': False},
|
| 543 |
+
'oldap-2.2': {'id': 'OLDAP-2.2', 'deprecated': False},
|
| 544 |
+
'oldap-2.2.1': {'id': 'OLDAP-2.2.1', 'deprecated': False},
|
| 545 |
+
'oldap-2.2.2': {'id': 'OLDAP-2.2.2', 'deprecated': False},
|
| 546 |
+
'oldap-2.3': {'id': 'OLDAP-2.3', 'deprecated': False},
|
| 547 |
+
'oldap-2.4': {'id': 'OLDAP-2.4', 'deprecated': False},
|
| 548 |
+
'oldap-2.5': {'id': 'OLDAP-2.5', 'deprecated': False},
|
| 549 |
+
'oldap-2.6': {'id': 'OLDAP-2.6', 'deprecated': False},
|
| 550 |
+
'oldap-2.7': {'id': 'OLDAP-2.7', 'deprecated': False},
|
| 551 |
+
'oldap-2.8': {'id': 'OLDAP-2.8', 'deprecated': False},
|
| 552 |
+
'olfl-1.3': {'id': 'OLFL-1.3', 'deprecated': False},
|
| 553 |
+
'oml': {'id': 'OML', 'deprecated': False},
|
| 554 |
+
'openpbs-2.3': {'id': 'OpenPBS-2.3', 'deprecated': False},
|
| 555 |
+
'openssl': {'id': 'OpenSSL', 'deprecated': False},
|
| 556 |
+
'openssl-standalone': {'id': 'OpenSSL-standalone', 'deprecated': False},
|
| 557 |
+
'openvision': {'id': 'OpenVision', 'deprecated': False},
|
| 558 |
+
'opl-1.0': {'id': 'OPL-1.0', 'deprecated': False},
|
| 559 |
+
'opl-uk-3.0': {'id': 'OPL-UK-3.0', 'deprecated': False},
|
| 560 |
+
'opubl-1.0': {'id': 'OPUBL-1.0', 'deprecated': False},
|
| 561 |
+
'oset-pl-2.1': {'id': 'OSET-PL-2.1', 'deprecated': False},
|
| 562 |
+
'osl-1.0': {'id': 'OSL-1.0', 'deprecated': False},
|
| 563 |
+
'osl-1.1': {'id': 'OSL-1.1', 'deprecated': False},
|
| 564 |
+
'osl-2.0': {'id': 'OSL-2.0', 'deprecated': False},
|
| 565 |
+
'osl-2.1': {'id': 'OSL-2.1', 'deprecated': False},
|
| 566 |
+
'osl-3.0': {'id': 'OSL-3.0', 'deprecated': False},
|
| 567 |
+
'padl': {'id': 'PADL', 'deprecated': False},
|
| 568 |
+
'parity-6.0.0': {'id': 'Parity-6.0.0', 'deprecated': False},
|
| 569 |
+
'parity-7.0.0': {'id': 'Parity-7.0.0', 'deprecated': False},
|
| 570 |
+
'pddl-1.0': {'id': 'PDDL-1.0', 'deprecated': False},
|
| 571 |
+
'php-3.0': {'id': 'PHP-3.0', 'deprecated': False},
|
| 572 |
+
'php-3.01': {'id': 'PHP-3.01', 'deprecated': False},
|
| 573 |
+
'pixar': {'id': 'Pixar', 'deprecated': False},
|
| 574 |
+
'pkgconf': {'id': 'pkgconf', 'deprecated': False},
|
| 575 |
+
'plexus': {'id': 'Plexus', 'deprecated': False},
|
| 576 |
+
'pnmstitch': {'id': 'pnmstitch', 'deprecated': False},
|
| 577 |
+
'polyform-noncommercial-1.0.0': {'id': 'PolyForm-Noncommercial-1.0.0', 'deprecated': False},
|
| 578 |
+
'polyform-small-business-1.0.0': {'id': 'PolyForm-Small-Business-1.0.0', 'deprecated': False},
|
| 579 |
+
'postgresql': {'id': 'PostgreSQL', 'deprecated': False},
|
| 580 |
+
'ppl': {'id': 'PPL', 'deprecated': False},
|
| 581 |
+
'psf-2.0': {'id': 'PSF-2.0', 'deprecated': False},
|
| 582 |
+
'psfrag': {'id': 'psfrag', 'deprecated': False},
|
| 583 |
+
'psutils': {'id': 'psutils', 'deprecated': False},
|
| 584 |
+
'python-2.0': {'id': 'Python-2.0', 'deprecated': False},
|
| 585 |
+
'python-2.0.1': {'id': 'Python-2.0.1', 'deprecated': False},
|
| 586 |
+
'python-ldap': {'id': 'python-ldap', 'deprecated': False},
|
| 587 |
+
'qhull': {'id': 'Qhull', 'deprecated': False},
|
| 588 |
+
'qpl-1.0': {'id': 'QPL-1.0', 'deprecated': False},
|
| 589 |
+
'qpl-1.0-inria-2004': {'id': 'QPL-1.0-INRIA-2004', 'deprecated': False},
|
| 590 |
+
'radvd': {'id': 'radvd', 'deprecated': False},
|
| 591 |
+
'rdisc': {'id': 'Rdisc', 'deprecated': False},
|
| 592 |
+
'rhecos-1.1': {'id': 'RHeCos-1.1', 'deprecated': False},
|
| 593 |
+
'rpl-1.1': {'id': 'RPL-1.1', 'deprecated': False},
|
| 594 |
+
'rpl-1.5': {'id': 'RPL-1.5', 'deprecated': False},
|
| 595 |
+
'rpsl-1.0': {'id': 'RPSL-1.0', 'deprecated': False},
|
| 596 |
+
'rsa-md': {'id': 'RSA-MD', 'deprecated': False},
|
| 597 |
+
'rscpl': {'id': 'RSCPL', 'deprecated': False},
|
| 598 |
+
'ruby': {'id': 'Ruby', 'deprecated': False},
|
| 599 |
+
'ruby-pty': {'id': 'Ruby-pty', 'deprecated': False},
|
| 600 |
+
'sax-pd': {'id': 'SAX-PD', 'deprecated': False},
|
| 601 |
+
'sax-pd-2.0': {'id': 'SAX-PD-2.0', 'deprecated': False},
|
| 602 |
+
'saxpath': {'id': 'Saxpath', 'deprecated': False},
|
| 603 |
+
'scea': {'id': 'SCEA', 'deprecated': False},
|
| 604 |
+
'schemereport': {'id': 'SchemeReport', 'deprecated': False},
|
| 605 |
+
'sendmail': {'id': 'Sendmail', 'deprecated': False},
|
| 606 |
+
'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False},
|
| 607 |
+
'sendmail-open-source-1.1': {'id': 'Sendmail-Open-Source-1.1', 'deprecated': False},
|
| 608 |
+
'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False},
|
| 609 |
+
'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False},
|
| 610 |
+
'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False},
|
| 611 |
+
'sgi-opengl': {'id': 'SGI-OpenGL', 'deprecated': False},
|
| 612 |
+
'sgp4': {'id': 'SGP4', 'deprecated': False},
|
| 613 |
+
'shl-0.5': {'id': 'SHL-0.5', 'deprecated': False},
|
| 614 |
+
'shl-0.51': {'id': 'SHL-0.51', 'deprecated': False},
|
| 615 |
+
'simpl-2.0': {'id': 'SimPL-2.0', 'deprecated': False},
|
| 616 |
+
'sissl': {'id': 'SISSL', 'deprecated': False},
|
| 617 |
+
'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False},
|
| 618 |
+
'sl': {'id': 'SL', 'deprecated': False},
|
| 619 |
+
'sleepycat': {'id': 'Sleepycat', 'deprecated': False},
|
| 620 |
+
'smail-gpl': {'id': 'SMAIL-GPL', 'deprecated': False},
|
| 621 |
+
'smlnj': {'id': 'SMLNJ', 'deprecated': False},
|
| 622 |
+
'smppl': {'id': 'SMPPL', 'deprecated': False},
|
| 623 |
+
'snia': {'id': 'SNIA', 'deprecated': False},
|
| 624 |
+
'snprintf': {'id': 'snprintf', 'deprecated': False},
|
| 625 |
+
'sofa': {'id': 'SOFA', 'deprecated': False},
|
| 626 |
+
'softsurfer': {'id': 'softSurfer', 'deprecated': False},
|
| 627 |
+
'soundex': {'id': 'Soundex', 'deprecated': False},
|
| 628 |
+
'spencer-86': {'id': 'Spencer-86', 'deprecated': False},
|
| 629 |
+
'spencer-94': {'id': 'Spencer-94', 'deprecated': False},
|
| 630 |
+
'spencer-99': {'id': 'Spencer-99', 'deprecated': False},
|
| 631 |
+
'spl-1.0': {'id': 'SPL-1.0', 'deprecated': False},
|
| 632 |
+
'ssh-keyscan': {'id': 'ssh-keyscan', 'deprecated': False},
|
| 633 |
+
'ssh-openssh': {'id': 'SSH-OpenSSH', 'deprecated': False},
|
| 634 |
+
'ssh-short': {'id': 'SSH-short', 'deprecated': False},
|
| 635 |
+
'ssleay-standalone': {'id': 'SSLeay-standalone', 'deprecated': False},
|
| 636 |
+
'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False},
|
| 637 |
+
'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True},
|
| 638 |
+
'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False},
|
| 639 |
+
'sul-1.0': {'id': 'SUL-1.0', 'deprecated': False},
|
| 640 |
+
'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False},
|
| 641 |
+
'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False},
|
| 642 |
+
'sunpro': {'id': 'SunPro', 'deprecated': False},
|
| 643 |
+
'swl': {'id': 'SWL', 'deprecated': False},
|
| 644 |
+
'swrule': {'id': 'swrule', 'deprecated': False},
|
| 645 |
+
'symlinks': {'id': 'Symlinks', 'deprecated': False},
|
| 646 |
+
'tapr-ohl-1.0': {'id': 'TAPR-OHL-1.0', 'deprecated': False},
|
| 647 |
+
'tcl': {'id': 'TCL', 'deprecated': False},
|
| 648 |
+
'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False},
|
| 649 |
+
'termreadkey': {'id': 'TermReadKey', 'deprecated': False},
|
| 650 |
+
'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False},
|
| 651 |
+
'thirdeye': {'id': 'ThirdEye', 'deprecated': False},
|
| 652 |
+
'threeparttable': {'id': 'threeparttable', 'deprecated': False},
|
| 653 |
+
'tmate': {'id': 'TMate', 'deprecated': False},
|
| 654 |
+
'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False},
|
| 655 |
+
'tosl': {'id': 'TOSL', 'deprecated': False},
|
| 656 |
+
'tpdl': {'id': 'TPDL', 'deprecated': False},
|
| 657 |
+
'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False},
|
| 658 |
+
'trustedqsl': {'id': 'TrustedQSL', 'deprecated': False},
|
| 659 |
+
'ttwl': {'id': 'TTWL', 'deprecated': False},
|
| 660 |
+
'ttyp0': {'id': 'TTYP0', 'deprecated': False},
|
| 661 |
+
'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False},
|
| 662 |
+
'tu-berlin-2.0': {'id': 'TU-Berlin-2.0', 'deprecated': False},
|
| 663 |
+
'ubuntu-font-1.0': {'id': 'Ubuntu-font-1.0', 'deprecated': False},
|
| 664 |
+
'ucar': {'id': 'UCAR', 'deprecated': False},
|
| 665 |
+
'ucl-1.0': {'id': 'UCL-1.0', 'deprecated': False},
|
| 666 |
+
'ulem': {'id': 'ulem', 'deprecated': False},
|
| 667 |
+
'umich-merit': {'id': 'UMich-Merit', 'deprecated': False},
|
| 668 |
+
'unicode-3.0': {'id': 'Unicode-3.0', 'deprecated': False},
|
| 669 |
+
'unicode-dfs-2015': {'id': 'Unicode-DFS-2015', 'deprecated': False},
|
| 670 |
+
'unicode-dfs-2016': {'id': 'Unicode-DFS-2016', 'deprecated': False},
|
| 671 |
+
'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False},
|
| 672 |
+
'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False},
|
| 673 |
+
'unlicense': {'id': 'Unlicense', 'deprecated': False},
|
| 674 |
+
'unlicense-libtelnet': {'id': 'Unlicense-libtelnet', 'deprecated': False},
|
| 675 |
+
'unlicense-libwhirlpool': {'id': 'Unlicense-libwhirlpool', 'deprecated': False},
|
| 676 |
+
'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False},
|
| 677 |
+
'urt-rle': {'id': 'URT-RLE', 'deprecated': False},
|
| 678 |
+
'vim': {'id': 'Vim', 'deprecated': False},
|
| 679 |
+
'vostrom': {'id': 'VOSTROM', 'deprecated': False},
|
| 680 |
+
'vsl-1.0': {'id': 'VSL-1.0', 'deprecated': False},
|
| 681 |
+
'w3c': {'id': 'W3C', 'deprecated': False},
|
| 682 |
+
'w3c-19980720': {'id': 'W3C-19980720', 'deprecated': False},
|
| 683 |
+
'w3c-20150513': {'id': 'W3C-20150513', 'deprecated': False},
|
| 684 |
+
'w3m': {'id': 'w3m', 'deprecated': False},
|
| 685 |
+
'watcom-1.0': {'id': 'Watcom-1.0', 'deprecated': False},
|
| 686 |
+
'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False},
|
| 687 |
+
'wsuipa': {'id': 'Wsuipa', 'deprecated': False},
|
| 688 |
+
'wtfpl': {'id': 'WTFPL', 'deprecated': False},
|
| 689 |
+
'wwl': {'id': 'wwl', 'deprecated': False},
|
| 690 |
+
'wxwindows': {'id': 'wxWindows', 'deprecated': True},
|
| 691 |
+
'x11': {'id': 'X11', 'deprecated': False},
|
| 692 |
+
'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False},
|
| 693 |
+
'x11-swapped': {'id': 'X11-swapped', 'deprecated': False},
|
| 694 |
+
'xdebug-1.03': {'id': 'Xdebug-1.03', 'deprecated': False},
|
| 695 |
+
'xerox': {'id': 'Xerox', 'deprecated': False},
|
| 696 |
+
'xfig': {'id': 'Xfig', 'deprecated': False},
|
| 697 |
+
'xfree86-1.1': {'id': 'XFree86-1.1', 'deprecated': False},
|
| 698 |
+
'xinetd': {'id': 'xinetd', 'deprecated': False},
|
| 699 |
+
'xkeyboard-config-zinoviev': {'id': 'xkeyboard-config-Zinoviev', 'deprecated': False},
|
| 700 |
+
'xlock': {'id': 'xlock', 'deprecated': False},
|
| 701 |
+
'xnet': {'id': 'Xnet', 'deprecated': False},
|
| 702 |
+
'xpp': {'id': 'xpp', 'deprecated': False},
|
| 703 |
+
'xskat': {'id': 'XSkat', 'deprecated': False},
|
| 704 |
+
'xzoom': {'id': 'xzoom', 'deprecated': False},
|
| 705 |
+
'ypl-1.0': {'id': 'YPL-1.0', 'deprecated': False},
|
| 706 |
+
'ypl-1.1': {'id': 'YPL-1.1', 'deprecated': False},
|
| 707 |
+
'zed': {'id': 'Zed', 'deprecated': False},
|
| 708 |
+
'zeeff': {'id': 'Zeeff', 'deprecated': False},
|
| 709 |
+
'zend-2.0': {'id': 'Zend-2.0', 'deprecated': False},
|
| 710 |
+
'zimbra-1.3': {'id': 'Zimbra-1.3', 'deprecated': False},
|
| 711 |
+
'zimbra-1.4': {'id': 'Zimbra-1.4', 'deprecated': False},
|
| 712 |
+
'zlib': {'id': 'Zlib', 'deprecated': False},
|
| 713 |
+
'zlib-acknowledgement': {'id': 'zlib-acknowledgement', 'deprecated': False},
|
| 714 |
+
'zpl-1.1': {'id': 'ZPL-1.1', 'deprecated': False},
|
| 715 |
+
'zpl-2.0': {'id': 'ZPL-2.0', 'deprecated': False},
|
| 716 |
+
'zpl-2.1': {'id': 'ZPL-2.1', 'deprecated': False},
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
EXCEPTIONS: dict[str, SPDXException] = {
|
| 720 |
+
'389-exception': {'id': '389-exception', 'deprecated': False},
|
| 721 |
+
'asterisk-exception': {'id': 'Asterisk-exception', 'deprecated': False},
|
| 722 |
+
'asterisk-linking-protocols-exception': {'id': 'Asterisk-linking-protocols-exception', 'deprecated': False},
|
| 723 |
+
'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0', 'deprecated': False},
|
| 724 |
+
'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0', 'deprecated': False},
|
| 725 |
+
'autoconf-exception-generic': {'id': 'Autoconf-exception-generic', 'deprecated': False},
|
| 726 |
+
'autoconf-exception-generic-3.0': {'id': 'Autoconf-exception-generic-3.0', 'deprecated': False},
|
| 727 |
+
'autoconf-exception-macro': {'id': 'Autoconf-exception-macro', 'deprecated': False},
|
| 728 |
+
'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False},
|
| 729 |
+
'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False},
|
| 730 |
+
'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False},
|
| 731 |
+
'cgal-linking-exception': {'id': 'CGAL-linking-exception', 'deprecated': False},
|
| 732 |
+
'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False},
|
| 733 |
+
'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False},
|
| 734 |
+
'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False},
|
| 735 |
+
'digia-qt-lgpl-exception-1.1': {'id': 'Digia-Qt-LGPL-exception-1.1', 'deprecated': False},
|
| 736 |
+
'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False},
|
| 737 |
+
'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False},
|
| 738 |
+
'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False},
|
| 739 |
+
'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception', 'deprecated': False},
|
| 740 |
+
'fltk-exception': {'id': 'FLTK-exception', 'deprecated': False},
|
| 741 |
+
'fmt-exception': {'id': 'fmt-exception', 'deprecated': False},
|
| 742 |
+
'font-exception-2.0': {'id': 'Font-exception-2.0', 'deprecated': False},
|
| 743 |
+
'freertos-exception-2.0': {'id': 'freertos-exception-2.0', 'deprecated': False},
|
| 744 |
+
'gcc-exception-2.0': {'id': 'GCC-exception-2.0', 'deprecated': False},
|
| 745 |
+
'gcc-exception-2.0-note': {'id': 'GCC-exception-2.0-note', 'deprecated': False},
|
| 746 |
+
'gcc-exception-3.1': {'id': 'GCC-exception-3.1', 'deprecated': False},
|
| 747 |
+
'gmsh-exception': {'id': 'Gmsh-exception', 'deprecated': False},
|
| 748 |
+
'gnat-exception': {'id': 'GNAT-exception', 'deprecated': False},
|
| 749 |
+
'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False},
|
| 750 |
+
'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False},
|
| 751 |
+
'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False},
|
| 752 |
+
'gpl-3.0-389-ds-base-exception': {'id': 'GPL-3.0-389-ds-base-exception', 'deprecated': False},
|
| 753 |
+
'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False},
|
| 754 |
+
'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False},
|
| 755 |
+
'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False},
|
| 756 |
+
'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False},
|
| 757 |
+
'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False},
|
| 758 |
+
'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False},
|
| 759 |
+
'harbour-exception': {'id': 'harbour-exception', 'deprecated': False},
|
| 760 |
+
'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False},
|
| 761 |
+
'independent-modules-exception': {'id': 'Independent-modules-exception', 'deprecated': False},
|
| 762 |
+
'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False},
|
| 763 |
+
'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False},
|
| 764 |
+
'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False},
|
| 765 |
+
'libtool-exception': {'id': 'Libtool-exception', 'deprecated': False},
|
| 766 |
+
'linux-syscall-note': {'id': 'Linux-syscall-note', 'deprecated': False},
|
| 767 |
+
'llgpl': {'id': 'LLGPL', 'deprecated': False},
|
| 768 |
+
'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False},
|
| 769 |
+
'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False},
|
| 770 |
+
'mif-exception': {'id': 'mif-exception', 'deprecated': False},
|
| 771 |
+
'mxml-exception': {'id': 'mxml-exception', 'deprecated': False},
|
| 772 |
+
'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True},
|
| 773 |
+
'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False},
|
| 774 |
+
'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False},
|
| 775 |
+
'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False},
|
| 776 |
+
'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False},
|
| 777 |
+
'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False},
|
| 778 |
+
'polyparse-exception': {'id': 'polyparse-exception', 'deprecated': False},
|
| 779 |
+
'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False},
|
| 780 |
+
'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False},
|
| 781 |
+
'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False},
|
| 782 |
+
'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1', 'deprecated': False},
|
| 783 |
+
'qwt-exception-1.0': {'id': 'Qwt-exception-1.0', 'deprecated': False},
|
| 784 |
+
'romic-exception': {'id': 'romic-exception', 'deprecated': False},
|
| 785 |
+
'rrdtool-floss-exception-2.0': {'id': 'RRDtool-FLOSS-exception-2.0', 'deprecated': False},
|
| 786 |
+
'sane-exception': {'id': 'SANE-exception', 'deprecated': False},
|
| 787 |
+
'shl-2.0': {'id': 'SHL-2.0', 'deprecated': False},
|
| 788 |
+
'shl-2.1': {'id': 'SHL-2.1', 'deprecated': False},
|
| 789 |
+
'stunnel-exception': {'id': 'stunnel-exception', 'deprecated': False},
|
| 790 |
+
'swi-exception': {'id': 'SWI-exception', 'deprecated': False},
|
| 791 |
+
'swift-exception': {'id': 'Swift-exception', 'deprecated': False},
|
| 792 |
+
'texinfo-exception': {'id': 'Texinfo-exception', 'deprecated': False},
|
| 793 |
+
'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0', 'deprecated': False},
|
| 794 |
+
'ubdl-exception': {'id': 'UBDL-exception', 'deprecated': False},
|
| 795 |
+
'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0', 'deprecated': False},
|
| 796 |
+
'vsftpd-openssl-exception': {'id': 'vsftpd-openssl-exception', 'deprecated': False},
|
| 797 |
+
'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1', 'deprecated': False},
|
| 798 |
+
'x11vnc-openssl-exception': {'id': 'x11vnc-openssl-exception', 'deprecated': False},
|
| 799 |
+
}
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/markers.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import operator
|
| 8 |
+
import os
|
| 9 |
+
import platform
|
| 10 |
+
import sys
|
| 11 |
+
from typing import AbstractSet, Callable, Literal, Mapping, TypedDict, Union, cast
|
| 12 |
+
|
| 13 |
+
from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
|
| 14 |
+
from ._parser import parse_marker as _parse_marker
|
| 15 |
+
from ._tokenizer import ParserSyntaxError
|
| 16 |
+
from .specifiers import InvalidSpecifier, Specifier
|
| 17 |
+
from .utils import canonicalize_name
|
| 18 |
+
|
| 19 |
+
__all__ = [
|
| 20 |
+
"Environment",
|
| 21 |
+
"EvaluateContext",
|
| 22 |
+
"InvalidMarker",
|
| 23 |
+
"Marker",
|
| 24 |
+
"UndefinedComparison",
|
| 25 |
+
"UndefinedEnvironmentName",
|
| 26 |
+
"default_environment",
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def __dir__() -> list[str]:
|
| 31 |
+
return __all__
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
|
| 35 |
+
EvaluateContext = Literal["metadata", "lock_file", "requirement"]
|
| 36 |
+
"""A ``typing.Literal`` enumerating valid marker evaluation contexts.
|
| 37 |
+
|
| 38 |
+
Valid values for the ``context`` passed to :meth:`Marker.evaluate` are:
|
| 39 |
+
|
| 40 |
+
* ``"metadata"`` (for core metadata; default)
|
| 41 |
+
* ``"lock_file"`` (for lock files)
|
| 42 |
+
* ``"requirement"`` (i.e. all other situations)
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
MARKERS_ALLOWING_SET = {"extras", "dependency_groups"}
|
| 46 |
+
MARKERS_REQUIRING_VERSION = {
|
| 47 |
+
"implementation_version",
|
| 48 |
+
"platform_release",
|
| 49 |
+
"python_full_version",
|
| 50 |
+
"python_version",
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class InvalidMarker(ValueError):
|
| 55 |
+
"""Raised when attempting to create a :class:`Marker` from invalid input.
|
| 56 |
+
|
| 57 |
+
This error indicates that the given marker string does not conform to the
|
| 58 |
+
:ref:`specification of dependency specifiers <pypug:dependency-specifiers>`.
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class UndefinedComparison(ValueError):
|
| 63 |
+
"""Raised when evaluating an unsupported marker comparison.
|
| 64 |
+
|
| 65 |
+
This can happen when marker values are compared as versions but do not
|
| 66 |
+
conform to the :ref:`specification of version specifiers
|
| 67 |
+
<pypug:version-specifiers>`.
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class UndefinedEnvironmentName(ValueError):
|
| 72 |
+
"""Raised when evaluating a marker that references a missing environment key."""
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class Environment(TypedDict):
|
| 76 |
+
"""
|
| 77 |
+
A dictionary that represents a Python environment as captured by
|
| 78 |
+
:func:`default_environment`. All fields are required.
|
| 79 |
+
"""
|
| 80 |
+
|
| 81 |
+
implementation_name: str
|
| 82 |
+
"""The implementation's identifier, e.g. ``'cpython'``."""
|
| 83 |
+
|
| 84 |
+
implementation_version: str
|
| 85 |
+
"""
|
| 86 |
+
The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or
|
| 87 |
+
``'7.3.13'`` for PyPy3.10 v7.3.13.
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
os_name: str
|
| 91 |
+
"""
|
| 92 |
+
The value of :py:data:`os.name`. The name of the operating system dependent module
|
| 93 |
+
imported, e.g. ``'posix'``.
|
| 94 |
+
"""
|
| 95 |
+
|
| 96 |
+
platform_machine: str
|
| 97 |
+
"""
|
| 98 |
+
Returns the machine type, e.g. ``'i386'``.
|
| 99 |
+
|
| 100 |
+
An empty string if the value cannot be determined.
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
platform_release: str
|
| 104 |
+
"""
|
| 105 |
+
The system's release, e.g. ``'2.2.0'`` or ``'NT'``.
|
| 106 |
+
|
| 107 |
+
An empty string if the value cannot be determined.
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
platform_system: str
|
| 111 |
+
"""
|
| 112 |
+
The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``.
|
| 113 |
+
|
| 114 |
+
An empty string if the value cannot be determined.
|
| 115 |
+
"""
|
| 116 |
+
|
| 117 |
+
platform_version: str
|
| 118 |
+
"""
|
| 119 |
+
The system's release version, e.g. ``'#3 on degas'``.
|
| 120 |
+
|
| 121 |
+
An empty string if the value cannot be determined.
|
| 122 |
+
"""
|
| 123 |
+
|
| 124 |
+
python_full_version: str
|
| 125 |
+
"""
|
| 126 |
+
The Python version as string ``'major.minor.patchlevel'``.
|
| 127 |
+
|
| 128 |
+
Note that unlike the Python :py:data:`sys.version`, this value will always include
|
| 129 |
+
the patchlevel (it defaults to 0).
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
platform_python_implementation: str
|
| 133 |
+
"""
|
| 134 |
+
A string identifying the Python implementation, e.g. ``'CPython'``.
|
| 135 |
+
"""
|
| 136 |
+
|
| 137 |
+
python_version: str
|
| 138 |
+
"""The Python version as string ``'major.minor'``."""
|
| 139 |
+
|
| 140 |
+
sys_platform: str
|
| 141 |
+
"""
|
| 142 |
+
This string contains a platform identifier that can be used to append
|
| 143 |
+
platform-specific components to :py:data:`sys.path`, for instance.
|
| 144 |
+
|
| 145 |
+
For Unix systems, except on Linux and AIX, this is the lowercased OS name as
|
| 146 |
+
returned by ``uname -s`` with the first part of the version as returned by
|
| 147 |
+
``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python
|
| 148 |
+
was built.
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _normalize_extras(
|
| 153 |
+
result: MarkerList | MarkerAtom | str,
|
| 154 |
+
) -> MarkerList | MarkerAtom | str:
|
| 155 |
+
if not isinstance(result, tuple):
|
| 156 |
+
return result
|
| 157 |
+
|
| 158 |
+
lhs, op, rhs = result
|
| 159 |
+
if isinstance(lhs, Variable) and lhs.value == "extra":
|
| 160 |
+
normalized_extra = canonicalize_name(rhs.value)
|
| 161 |
+
rhs = Value(normalized_extra)
|
| 162 |
+
elif isinstance(rhs, Variable) and rhs.value == "extra":
|
| 163 |
+
normalized_extra = canonicalize_name(lhs.value)
|
| 164 |
+
lhs = Value(normalized_extra)
|
| 165 |
+
return lhs, op, rhs
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _normalize_extra_values(results: MarkerList) -> MarkerList:
|
| 169 |
+
"""
|
| 170 |
+
Normalize extra values.
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
return [_normalize_extras(r) for r in results]
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def _format_marker(
|
| 177 |
+
marker: list[str] | MarkerAtom | str, first: bool | None = True
|
| 178 |
+
) -> str:
|
| 179 |
+
assert isinstance(marker, (list, tuple, str))
|
| 180 |
+
|
| 181 |
+
# Sometimes we have a structure like [[...]] which is a single item list
|
| 182 |
+
# where the single item is itself it's own list. In that case we want skip
|
| 183 |
+
# the rest of this function so that we don't get extraneous () on the
|
| 184 |
+
# outside.
|
| 185 |
+
if (
|
| 186 |
+
isinstance(marker, list)
|
| 187 |
+
and len(marker) == 1
|
| 188 |
+
and isinstance(marker[0], (list, tuple))
|
| 189 |
+
):
|
| 190 |
+
return _format_marker(marker[0])
|
| 191 |
+
|
| 192 |
+
if isinstance(marker, list):
|
| 193 |
+
inner = (_format_marker(m, first=False) for m in marker)
|
| 194 |
+
if first:
|
| 195 |
+
return " ".join(inner)
|
| 196 |
+
else:
|
| 197 |
+
return "(" + " ".join(inner) + ")"
|
| 198 |
+
elif isinstance(marker, tuple):
|
| 199 |
+
return " ".join([m.serialize() for m in marker])
|
| 200 |
+
else:
|
| 201 |
+
return marker
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
_operators: dict[str, Operator] = {
|
| 205 |
+
"in": lambda lhs, rhs: lhs in rhs,
|
| 206 |
+
"not in": lambda lhs, rhs: lhs not in rhs,
|
| 207 |
+
"<": lambda _lhs, _rhs: False,
|
| 208 |
+
"<=": operator.eq,
|
| 209 |
+
"==": operator.eq,
|
| 210 |
+
"!=": operator.ne,
|
| 211 |
+
">=": operator.eq,
|
| 212 |
+
">": lambda _lhs, _rhs: False,
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool:
|
| 217 |
+
op_str = op.serialize()
|
| 218 |
+
if key in MARKERS_REQUIRING_VERSION:
|
| 219 |
+
try:
|
| 220 |
+
spec = Specifier(f"{op_str}{rhs}")
|
| 221 |
+
except InvalidSpecifier:
|
| 222 |
+
pass
|
| 223 |
+
else:
|
| 224 |
+
return spec.contains(lhs, prereleases=True)
|
| 225 |
+
|
| 226 |
+
oper: Operator | None = _operators.get(op_str)
|
| 227 |
+
if oper is None:
|
| 228 |
+
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
|
| 229 |
+
|
| 230 |
+
return oper(lhs, rhs)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def _normalize(
|
| 234 |
+
lhs: str, rhs: str | AbstractSet[str], key: str
|
| 235 |
+
) -> tuple[str, str | AbstractSet[str]]:
|
| 236 |
+
# PEP 685 - Comparison of extra names for optional distribution dependencies
|
| 237 |
+
# https://peps.python.org/pep-0685/
|
| 238 |
+
# > When comparing extra names, tools MUST normalize the names being
|
| 239 |
+
# > compared using the semantics outlined in PEP 503 for names
|
| 240 |
+
if key == "extra":
|
| 241 |
+
assert isinstance(rhs, str), "extra value must be a string"
|
| 242 |
+
# Both sides are normalized at this point already
|
| 243 |
+
return (lhs, rhs)
|
| 244 |
+
if key in MARKERS_ALLOWING_SET:
|
| 245 |
+
if isinstance(rhs, str): # pragma: no cover
|
| 246 |
+
return (canonicalize_name(lhs), canonicalize_name(rhs))
|
| 247 |
+
else:
|
| 248 |
+
return (canonicalize_name(lhs), {canonicalize_name(v) for v in rhs})
|
| 249 |
+
|
| 250 |
+
# other environment markers don't have such standards
|
| 251 |
+
return lhs, rhs
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def _evaluate_markers(
|
| 255 |
+
markers: MarkerList, environment: dict[str, str | AbstractSet[str]]
|
| 256 |
+
) -> bool:
|
| 257 |
+
groups: list[list[bool]] = [[]]
|
| 258 |
+
|
| 259 |
+
for marker in markers:
|
| 260 |
+
if isinstance(marker, list):
|
| 261 |
+
groups[-1].append(_evaluate_markers(marker, environment))
|
| 262 |
+
elif isinstance(marker, tuple):
|
| 263 |
+
lhs, op, rhs = marker
|
| 264 |
+
|
| 265 |
+
if isinstance(lhs, Variable):
|
| 266 |
+
environment_key = lhs.value
|
| 267 |
+
lhs_value = environment[environment_key]
|
| 268 |
+
rhs_value = rhs.value
|
| 269 |
+
else:
|
| 270 |
+
lhs_value = lhs.value
|
| 271 |
+
environment_key = rhs.value
|
| 272 |
+
rhs_value = environment[environment_key]
|
| 273 |
+
|
| 274 |
+
assert isinstance(lhs_value, str), "lhs must be a string"
|
| 275 |
+
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
|
| 276 |
+
groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
|
| 277 |
+
elif marker == "or":
|
| 278 |
+
groups.append([])
|
| 279 |
+
elif marker == "and":
|
| 280 |
+
pass
|
| 281 |
+
else: # pragma: nocover
|
| 282 |
+
raise TypeError(f"Unexpected marker {marker!r}")
|
| 283 |
+
|
| 284 |
+
return any(all(item) for item in groups)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def _format_full_version(info: sys._version_info) -> str:
|
| 288 |
+
version = f"{info.major}.{info.minor}.{info.micro}"
|
| 289 |
+
kind = info.releaselevel
|
| 290 |
+
if kind != "final":
|
| 291 |
+
version += kind[0] + str(info.serial)
|
| 292 |
+
return version
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def default_environment() -> Environment:
|
| 296 |
+
"""Return the default marker environment for the current Python process.
|
| 297 |
+
|
| 298 |
+
This is the base environment used by :meth:`Marker.evaluate`.
|
| 299 |
+
"""
|
| 300 |
+
iver = _format_full_version(sys.implementation.version)
|
| 301 |
+
implementation_name = sys.implementation.name
|
| 302 |
+
return {
|
| 303 |
+
"implementation_name": implementation_name,
|
| 304 |
+
"implementation_version": iver,
|
| 305 |
+
"os_name": os.name,
|
| 306 |
+
"platform_machine": platform.machine(),
|
| 307 |
+
"platform_release": platform.release(),
|
| 308 |
+
"platform_system": platform.system(),
|
| 309 |
+
"platform_version": platform.version(),
|
| 310 |
+
"python_full_version": platform.python_version(),
|
| 311 |
+
"platform_python_implementation": platform.python_implementation(),
|
| 312 |
+
"python_version": ".".join(platform.python_version_tuple()[:2]),
|
| 313 |
+
"sys_platform": sys.platform,
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
class Marker:
|
| 318 |
+
"""Represents a parsed dependency marker expression.
|
| 319 |
+
|
| 320 |
+
Marker expressions are parsed according to the
|
| 321 |
+
:ref:`specification of dependency specifiers <pypug:dependency-specifiers>`.
|
| 322 |
+
|
| 323 |
+
:param marker: The string representation of a marker expression.
|
| 324 |
+
:raises InvalidMarker: If ``marker`` cannot be parsed.
|
| 325 |
+
|
| 326 |
+
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
| 327 |
+
format so the same pickle can be loaded in future packaging releases.
|
| 328 |
+
|
| 329 |
+
.. versionchanged:: 26.2
|
| 330 |
+
|
| 331 |
+
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
| 332 |
+
be unpickled with future releases. Backward compatibility with pickles
|
| 333 |
+
from packaging < 26.2 is supported but may be removed in a future
|
| 334 |
+
release.
|
| 335 |
+
"""
|
| 336 |
+
|
| 337 |
+
__slots__ = ("_markers",)
|
| 338 |
+
|
| 339 |
+
def __init__(self, marker: str) -> None:
|
| 340 |
+
# Note: We create a Marker object without calling this constructor in
|
| 341 |
+
# packaging.requirements.Requirement. If any additional logic is
|
| 342 |
+
# added here, make sure to mirror/adapt Requirement.
|
| 343 |
+
|
| 344 |
+
# If this fails and throws an error, the repr still expects _markers to
|
| 345 |
+
# be defined.
|
| 346 |
+
self._markers: MarkerList = []
|
| 347 |
+
|
| 348 |
+
try:
|
| 349 |
+
self._markers = _normalize_extra_values(_parse_marker(marker))
|
| 350 |
+
# The attribute `_markers` can be described in terms of a recursive type:
|
| 351 |
+
# MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
|
| 352 |
+
#
|
| 353 |
+
# For example, the following expression:
|
| 354 |
+
# python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
|
| 355 |
+
#
|
| 356 |
+
# is parsed into:
|
| 357 |
+
# [
|
| 358 |
+
# (<Variable('python_version')>, <Op('>')>, <Value('3.6')>),
|
| 359 |
+
# 'and',
|
| 360 |
+
# [
|
| 361 |
+
# (<Variable('python_version')>, <Op('==')>, <Value('3.6')>),
|
| 362 |
+
# 'or',
|
| 363 |
+
# (<Variable('os_name')>, <Op('==')>, <Value('unix')>)
|
| 364 |
+
# ]
|
| 365 |
+
# ]
|
| 366 |
+
except ParserSyntaxError as e:
|
| 367 |
+
raise InvalidMarker(str(e)) from e
|
| 368 |
+
|
| 369 |
+
@classmethod
|
| 370 |
+
def _from_markers(cls, markers: MarkerList) -> Marker:
|
| 371 |
+
"""Create a Marker instance from a pre-parsed marker tree.
|
| 372 |
+
|
| 373 |
+
This avoids re-parsing serialised marker strings when combining markers.
|
| 374 |
+
"""
|
| 375 |
+
new = cls.__new__(cls)
|
| 376 |
+
new._markers = markers
|
| 377 |
+
return new
|
| 378 |
+
|
| 379 |
+
def __str__(self) -> str:
|
| 380 |
+
return _format_marker(self._markers)
|
| 381 |
+
|
| 382 |
+
def __repr__(self) -> str:
|
| 383 |
+
return f"<{self.__class__.__name__}({str(self)!r})>"
|
| 384 |
+
|
| 385 |
+
def __hash__(self) -> int:
|
| 386 |
+
return hash(str(self))
|
| 387 |
+
|
| 388 |
+
def __eq__(self, other: object) -> bool:
|
| 389 |
+
if not isinstance(other, Marker):
|
| 390 |
+
return NotImplemented
|
| 391 |
+
|
| 392 |
+
return str(self) == str(other)
|
| 393 |
+
|
| 394 |
+
def __getstate__(self) -> str:
|
| 395 |
+
# Return the marker expression string for compactness and stability.
|
| 396 |
+
# Internal Node objects are excluded; the string is re-parsed on load.
|
| 397 |
+
return str(self)
|
| 398 |
+
|
| 399 |
+
def __setstate__(self, state: object) -> None:
|
| 400 |
+
if isinstance(state, str):
|
| 401 |
+
# New format (26.2+): just the marker expression string.
|
| 402 |
+
try:
|
| 403 |
+
self._markers = _normalize_extra_values(_parse_marker(state))
|
| 404 |
+
except ParserSyntaxError as exc:
|
| 405 |
+
raise TypeError(f"Cannot restore Marker from {state!r}") from exc
|
| 406 |
+
return
|
| 407 |
+
if isinstance(state, dict) and "_markers" in state:
|
| 408 |
+
# Old format (packaging <= 26.1, no __slots__): plain __dict__.
|
| 409 |
+
markers = state["_markers"]
|
| 410 |
+
if isinstance(markers, list):
|
| 411 |
+
self._markers = markers
|
| 412 |
+
return
|
| 413 |
+
if isinstance(state, tuple) and len(state) == 2:
|
| 414 |
+
# Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
|
| 415 |
+
_, slot_dict = state
|
| 416 |
+
if isinstance(slot_dict, dict) and "_markers" in slot_dict:
|
| 417 |
+
markers = slot_dict["_markers"]
|
| 418 |
+
if isinstance(markers, list):
|
| 419 |
+
self._markers = markers
|
| 420 |
+
return
|
| 421 |
+
raise TypeError(f"Cannot restore Marker from {state!r}")
|
| 422 |
+
|
| 423 |
+
def __and__(self, other: Marker) -> Marker:
|
| 424 |
+
if not isinstance(other, Marker):
|
| 425 |
+
return NotImplemented
|
| 426 |
+
return self._from_markers([self._markers, "and", other._markers])
|
| 427 |
+
|
| 428 |
+
def __or__(self, other: Marker) -> Marker:
|
| 429 |
+
if not isinstance(other, Marker):
|
| 430 |
+
return NotImplemented
|
| 431 |
+
return self._from_markers([self._markers, "or", other._markers])
|
| 432 |
+
|
| 433 |
+
def evaluate(
|
| 434 |
+
self,
|
| 435 |
+
environment: Mapping[str, str | AbstractSet[str]] | None = None,
|
| 436 |
+
context: EvaluateContext = "metadata",
|
| 437 |
+
) -> bool:
|
| 438 |
+
"""Evaluate a marker.
|
| 439 |
+
|
| 440 |
+
Return the boolean from evaluating this marker against the environment.
|
| 441 |
+
The environment is determined from the current Python process unless
|
| 442 |
+
passed in explicitly.
|
| 443 |
+
|
| 444 |
+
:param environment: Mapping containing keys and values to override the
|
| 445 |
+
detected environment.
|
| 446 |
+
:param EvaluateContext context: The context in which the marker is
|
| 447 |
+
evaluated, which influences what marker names are considered valid.
|
| 448 |
+
Accepted values are ``"metadata"`` (for core metadata; default),
|
| 449 |
+
``"lock_file"``, and ``"requirement"`` (i.e. all other situations).
|
| 450 |
+
:raises UndefinedComparison: If the marker uses a comparison on values
|
| 451 |
+
that are not valid versions per the :ref:`specification of version
|
| 452 |
+
specifiers <pypug:version-specifiers>`.
|
| 453 |
+
:raises UndefinedEnvironmentName: If the marker references a value that
|
| 454 |
+
is missing from the evaluation environment.
|
| 455 |
+
:returns: ``True`` if the marker matches, otherwise ``False``.
|
| 456 |
+
|
| 457 |
+
"""
|
| 458 |
+
current_environment = cast(
|
| 459 |
+
"dict[str, str | AbstractSet[str]]", default_environment()
|
| 460 |
+
)
|
| 461 |
+
if context == "lock_file":
|
| 462 |
+
current_environment.update(
|
| 463 |
+
extras=frozenset(), dependency_groups=frozenset()
|
| 464 |
+
)
|
| 465 |
+
elif context == "metadata":
|
| 466 |
+
current_environment["extra"] = ""
|
| 467 |
+
|
| 468 |
+
if environment is not None:
|
| 469 |
+
current_environment.update(environment)
|
| 470 |
+
if "extra" in current_environment:
|
| 471 |
+
# The API used to allow setting extra to None. We need to handle
|
| 472 |
+
# this case for backwards compatibility. Also skip running
|
| 473 |
+
# normalize name if extra is empty.
|
| 474 |
+
extra = cast("str | None", current_environment["extra"])
|
| 475 |
+
current_environment["extra"] = canonicalize_name(extra) if extra else ""
|
| 476 |
+
|
| 477 |
+
return _evaluate_markers(
|
| 478 |
+
self._markers, _repair_python_full_version(current_environment)
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
def _repair_python_full_version(
|
| 483 |
+
env: dict[str, str | AbstractSet[str]],
|
| 484 |
+
) -> dict[str, str | AbstractSet[str]]:
|
| 485 |
+
"""
|
| 486 |
+
Work around platform.python_version() returning something that is not PEP 440
|
| 487 |
+
compliant for non-tagged Python builds.
|
| 488 |
+
"""
|
| 489 |
+
python_full_version = cast("str", env["python_full_version"])
|
| 490 |
+
if python_full_version.endswith("+"):
|
| 491 |
+
env["python_full_version"] = f"{python_full_version}local"
|
| 492 |
+
return env
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/metadata.py
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import email.header
|
| 4 |
+
import email.message
|
| 5 |
+
import email.parser
|
| 6 |
+
import email.policy
|
| 7 |
+
import keyword
|
| 8 |
+
import pathlib
|
| 9 |
+
import typing
|
| 10 |
+
from typing import (
|
| 11 |
+
Any,
|
| 12 |
+
Callable,
|
| 13 |
+
Generic,
|
| 14 |
+
Literal,
|
| 15 |
+
TypedDict,
|
| 16 |
+
cast,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
from . import licenses, requirements, specifiers, utils
|
| 20 |
+
from . import version as version_module
|
| 21 |
+
from .errors import ExceptionGroup, _ErrorCollector
|
| 22 |
+
|
| 23 |
+
if typing.TYPE_CHECKING:
|
| 24 |
+
from .licenses import NormalizedLicenseExpression
|
| 25 |
+
|
| 26 |
+
T = typing.TypeVar("T")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
__all__ = [
|
| 30 |
+
"ExceptionGroup", # Keep this for a bit (makes mypy happy w/ 26.0 compat)
|
| 31 |
+
"InvalidMetadata",
|
| 32 |
+
"Metadata",
|
| 33 |
+
"RFC822Message",
|
| 34 |
+
"RFC822Policy",
|
| 35 |
+
"RawMetadata",
|
| 36 |
+
"parse_email",
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def __dir__() -> list[str]:
|
| 41 |
+
return __all__
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class InvalidMetadata(ValueError):
|
| 45 |
+
"""A metadata field contains invalid data."""
|
| 46 |
+
|
| 47 |
+
field: str
|
| 48 |
+
"""The name of the field that contains invalid data."""
|
| 49 |
+
|
| 50 |
+
def __init__(self, field: str, message: str) -> None:
|
| 51 |
+
self.field = field
|
| 52 |
+
super().__init__(message)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# The RawMetadata class attempts to make as few assumptions about the underlying
|
| 56 |
+
# serialization formats as possible. The idea is that as long as a serialization
|
| 57 |
+
# formats offer some very basic primitives in *some* way then we can support
|
| 58 |
+
# serializing to and from that format.
|
| 59 |
+
class RawMetadata(TypedDict, total=False):
|
| 60 |
+
"""A dictionary of raw core metadata.
|
| 61 |
+
|
| 62 |
+
Each field in core metadata maps to a key of this dictionary (when data is
|
| 63 |
+
provided). The key is lower-case and underscores are used instead of dashes
|
| 64 |
+
compared to the equivalent core metadata field. Any core metadata field that
|
| 65 |
+
can be specified multiple times or can hold multiple values in a single
|
| 66 |
+
field have a key with a plural name. See :class:`Metadata` whose attributes
|
| 67 |
+
match the keys of this dictionary.
|
| 68 |
+
|
| 69 |
+
Core metadata fields that can be specified multiple times are stored as a
|
| 70 |
+
list or dict depending on which is appropriate for the field. Any fields
|
| 71 |
+
which hold multiple values in a single field are stored as a list. All fields
|
| 72 |
+
are considered optional.
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
# Metadata 1.0 - PEP 241
|
| 76 |
+
metadata_version: str
|
| 77 |
+
name: str
|
| 78 |
+
version: str
|
| 79 |
+
platforms: list[str]
|
| 80 |
+
summary: str
|
| 81 |
+
description: str
|
| 82 |
+
keywords: list[str]
|
| 83 |
+
home_page: str
|
| 84 |
+
author: str
|
| 85 |
+
author_email: str
|
| 86 |
+
license: str
|
| 87 |
+
|
| 88 |
+
# Metadata 1.1 - PEP 314
|
| 89 |
+
supported_platforms: list[str]
|
| 90 |
+
download_url: str
|
| 91 |
+
classifiers: list[str]
|
| 92 |
+
requires: list[str]
|
| 93 |
+
provides: list[str]
|
| 94 |
+
obsoletes: list[str]
|
| 95 |
+
|
| 96 |
+
# Metadata 1.2 - PEP 345
|
| 97 |
+
maintainer: str
|
| 98 |
+
maintainer_email: str
|
| 99 |
+
requires_dist: list[str]
|
| 100 |
+
provides_dist: list[str]
|
| 101 |
+
obsoletes_dist: list[str]
|
| 102 |
+
requires_python: str
|
| 103 |
+
requires_external: list[str]
|
| 104 |
+
project_urls: dict[str, str]
|
| 105 |
+
|
| 106 |
+
# Metadata 2.0
|
| 107 |
+
# PEP 426 attempted to completely revamp the metadata format
|
| 108 |
+
# but got stuck without ever being able to build consensus on
|
| 109 |
+
# it and ultimately ended up withdrawn.
|
| 110 |
+
#
|
| 111 |
+
# However, a number of tools had started emitting METADATA with
|
| 112 |
+
# `2.0` Metadata-Version, so for historical reasons, this version
|
| 113 |
+
# was skipped.
|
| 114 |
+
|
| 115 |
+
# Metadata 2.1 - PEP 566
|
| 116 |
+
description_content_type: str
|
| 117 |
+
provides_extra: list[str]
|
| 118 |
+
|
| 119 |
+
# Metadata 2.2 - PEP 643
|
| 120 |
+
dynamic: list[str]
|
| 121 |
+
|
| 122 |
+
# Metadata 2.3 - PEP 685
|
| 123 |
+
# No new fields were added in PEP 685, just some edge case were
|
| 124 |
+
# tightened up to provide better interoperability.
|
| 125 |
+
|
| 126 |
+
# Metadata 2.4 - PEP 639
|
| 127 |
+
license_expression: str
|
| 128 |
+
license_files: list[str]
|
| 129 |
+
|
| 130 |
+
# Metadata 2.5 - PEP 794
|
| 131 |
+
import_names: list[str]
|
| 132 |
+
import_namespaces: list[str]
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# 'keywords' is special as it's a string in the core metadata spec, but we
|
| 136 |
+
# represent it as a list.
|
| 137 |
+
_STRING_FIELDS = {
|
| 138 |
+
"author",
|
| 139 |
+
"author_email",
|
| 140 |
+
"description",
|
| 141 |
+
"description_content_type",
|
| 142 |
+
"download_url",
|
| 143 |
+
"home_page",
|
| 144 |
+
"license",
|
| 145 |
+
"license_expression",
|
| 146 |
+
"maintainer",
|
| 147 |
+
"maintainer_email",
|
| 148 |
+
"metadata_version",
|
| 149 |
+
"name",
|
| 150 |
+
"requires_python",
|
| 151 |
+
"summary",
|
| 152 |
+
"version",
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
_LIST_FIELDS = {
|
| 156 |
+
"classifiers",
|
| 157 |
+
"dynamic",
|
| 158 |
+
"license_files",
|
| 159 |
+
"obsoletes",
|
| 160 |
+
"obsoletes_dist",
|
| 161 |
+
"platforms",
|
| 162 |
+
"provides",
|
| 163 |
+
"provides_dist",
|
| 164 |
+
"provides_extra",
|
| 165 |
+
"requires",
|
| 166 |
+
"requires_dist",
|
| 167 |
+
"requires_external",
|
| 168 |
+
"supported_platforms",
|
| 169 |
+
"import_names",
|
| 170 |
+
"import_namespaces",
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
_DICT_FIELDS = {
|
| 174 |
+
"project_urls",
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def _parse_keywords(data: str) -> list[str]:
|
| 179 |
+
"""Split a string of comma-separated keywords into a list of keywords."""
|
| 180 |
+
return [k.strip() for k in data.split(",")]
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def _parse_project_urls(data: list[str]) -> dict[str, str]:
|
| 184 |
+
"""Parse a list of label/URL string pairings separated by a comma."""
|
| 185 |
+
urls = {}
|
| 186 |
+
for pair in data:
|
| 187 |
+
# Our logic is slightly tricky here as we want to try and do
|
| 188 |
+
# *something* reasonable with malformed data.
|
| 189 |
+
#
|
| 190 |
+
# The main thing that we have to worry about, is data that does
|
| 191 |
+
# not have a ',' at all to split the label from the Value. There
|
| 192 |
+
# isn't a singular right answer here, and we will fail validation
|
| 193 |
+
# later on (if the caller is validating) so it doesn't *really*
|
| 194 |
+
# matter, but since the missing value has to be an empty str
|
| 195 |
+
# and our return value is dict[str, str], if we let the key
|
| 196 |
+
# be the missing value, then they'd have multiple '' values that
|
| 197 |
+
# overwrite each other in a accumulating dict.
|
| 198 |
+
#
|
| 199 |
+
# The other potential issue is that it's possible to have the
|
| 200 |
+
# same label multiple times in the metadata, with no solid "right"
|
| 201 |
+
# answer with what to do in that case. As such, we'll do the only
|
| 202 |
+
# thing we can, which is treat the field as unparsable and add it
|
| 203 |
+
# to our list of unparsed fields.
|
| 204 |
+
#
|
| 205 |
+
# TODO: The spec doesn't say anything about if the keys should be
|
| 206 |
+
# considered case sensitive or not... logically they should
|
| 207 |
+
# be case-preserving and case-insensitive, but doing that
|
| 208 |
+
# would open up more cases where we might have duplicate
|
| 209 |
+
# entries.
|
| 210 |
+
label, _, url = (s.strip() for s in pair.partition(","))
|
| 211 |
+
|
| 212 |
+
if label in urls:
|
| 213 |
+
# The label already exists in our set of urls, so this field
|
| 214 |
+
# is unparsable, and we can just add the whole thing to our
|
| 215 |
+
# unparsable data and stop processing it.
|
| 216 |
+
raise KeyError("duplicate labels in project urls")
|
| 217 |
+
urls[label] = url
|
| 218 |
+
|
| 219 |
+
return urls
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
|
| 223 |
+
"""Get the body of the message."""
|
| 224 |
+
# If our source is a str, then our caller has managed encodings for us,
|
| 225 |
+
# and we don't need to deal with it.
|
| 226 |
+
if isinstance(source, str):
|
| 227 |
+
payload = msg.get_payload()
|
| 228 |
+
assert isinstance(payload, str)
|
| 229 |
+
return payload
|
| 230 |
+
# If our source is a bytes, then we're managing the encoding and we need
|
| 231 |
+
# to deal with it.
|
| 232 |
+
else:
|
| 233 |
+
bpayload = msg.get_payload(decode=True)
|
| 234 |
+
assert isinstance(bpayload, bytes)
|
| 235 |
+
try:
|
| 236 |
+
return bpayload.decode("utf8", "strict")
|
| 237 |
+
except UnicodeDecodeError as exc:
|
| 238 |
+
raise ValueError("payload in an invalid encoding") from exc
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
# The various parse_FORMAT functions here are intended to be as lenient as
|
| 242 |
+
# possible in their parsing, while still returning a correctly typed
|
| 243 |
+
# RawMetadata.
|
| 244 |
+
#
|
| 245 |
+
# To aid in this, we also generally want to do as little touching of the
|
| 246 |
+
# data as possible, except where there are possibly some historic holdovers
|
| 247 |
+
# that make valid data awkward to work with.
|
| 248 |
+
#
|
| 249 |
+
# While this is a lower level, intermediate format than our ``Metadata``
|
| 250 |
+
# class, some light touch ups can make a massive difference in usability.
|
| 251 |
+
|
| 252 |
+
# Map METADATA fields to RawMetadata.
|
| 253 |
+
_EMAIL_TO_RAW_MAPPING = {
|
| 254 |
+
"author": "author",
|
| 255 |
+
"author-email": "author_email",
|
| 256 |
+
"classifier": "classifiers",
|
| 257 |
+
"description": "description",
|
| 258 |
+
"description-content-type": "description_content_type",
|
| 259 |
+
"download-url": "download_url",
|
| 260 |
+
"dynamic": "dynamic",
|
| 261 |
+
"home-page": "home_page",
|
| 262 |
+
"import-name": "import_names",
|
| 263 |
+
"import-namespace": "import_namespaces",
|
| 264 |
+
"keywords": "keywords",
|
| 265 |
+
"license": "license",
|
| 266 |
+
"license-expression": "license_expression",
|
| 267 |
+
"license-file": "license_files",
|
| 268 |
+
"maintainer": "maintainer",
|
| 269 |
+
"maintainer-email": "maintainer_email",
|
| 270 |
+
"metadata-version": "metadata_version",
|
| 271 |
+
"name": "name",
|
| 272 |
+
"obsoletes": "obsoletes",
|
| 273 |
+
"obsoletes-dist": "obsoletes_dist",
|
| 274 |
+
"platform": "platforms",
|
| 275 |
+
"project-url": "project_urls",
|
| 276 |
+
"provides": "provides",
|
| 277 |
+
"provides-dist": "provides_dist",
|
| 278 |
+
"provides-extra": "provides_extra",
|
| 279 |
+
"requires": "requires",
|
| 280 |
+
"requires-dist": "requires_dist",
|
| 281 |
+
"requires-external": "requires_external",
|
| 282 |
+
"requires-python": "requires_python",
|
| 283 |
+
"summary": "summary",
|
| 284 |
+
"supported-platform": "supported_platforms",
|
| 285 |
+
"version": "version",
|
| 286 |
+
}
|
| 287 |
+
_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
# This class is for writing RFC822 messages
|
| 291 |
+
class RFC822Policy(email.policy.EmailPolicy):
|
| 292 |
+
"""
|
| 293 |
+
This is :class:`email.policy.EmailPolicy`, but with a simple ``header_store_parse``
|
| 294 |
+
implementation that handles multi-line values, and some nice defaults.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
utf8 = True
|
| 298 |
+
mangle_from_ = False
|
| 299 |
+
max_line_length = 0
|
| 300 |
+
|
| 301 |
+
def header_store_parse(self, name: str, value: str) -> tuple[str, str]:
|
| 302 |
+
size = len(name) + 2
|
| 303 |
+
value = value.replace("\n", "\n" + " " * size)
|
| 304 |
+
return (name, value)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
# This class is for writing RFC822 messages
|
| 308 |
+
class RFC822Message(email.message.EmailMessage):
|
| 309 |
+
"""
|
| 310 |
+
This is :class:`email.message.EmailMessage` with two small changes: it defaults to
|
| 311 |
+
our `RFC822Policy`, and it correctly writes unicode when being called
|
| 312 |
+
with `bytes()`.
|
| 313 |
+
"""
|
| 314 |
+
|
| 315 |
+
def __init__(self) -> None:
|
| 316 |
+
super().__init__(policy=RFC822Policy())
|
| 317 |
+
|
| 318 |
+
def as_bytes(
|
| 319 |
+
self, unixfrom: bool = False, policy: email.policy.Policy | None = None
|
| 320 |
+
) -> bytes:
|
| 321 |
+
"""
|
| 322 |
+
Return the bytes representation of the message.
|
| 323 |
+
|
| 324 |
+
This handles unicode encoding.
|
| 325 |
+
"""
|
| 326 |
+
return self.as_string(unixfrom, policy=policy).encode("utf-8")
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
| 330 |
+
"""Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
|
| 331 |
+
|
| 332 |
+
This function returns a two-item tuple of dicts. The first dict is of
|
| 333 |
+
recognized fields from the core metadata specification. Fields that can be
|
| 334 |
+
parsed and translated into Python's built-in types are converted
|
| 335 |
+
appropriately. All other fields are left as-is. Fields that are allowed to
|
| 336 |
+
appear multiple times are stored as lists.
|
| 337 |
+
|
| 338 |
+
The second dict contains all other fields from the metadata. This includes
|
| 339 |
+
any unrecognized fields. It also includes any fields which are expected to
|
| 340 |
+
be parsed into a built-in type but were not formatted appropriately. Finally,
|
| 341 |
+
any fields that are expected to appear only once but are repeated are
|
| 342 |
+
included in this dict.
|
| 343 |
+
|
| 344 |
+
"""
|
| 345 |
+
raw: dict[str, str | list[str] | dict[str, str]] = {}
|
| 346 |
+
unparsed: dict[str, list[str]] = {}
|
| 347 |
+
|
| 348 |
+
if isinstance(data, str):
|
| 349 |
+
parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
|
| 350 |
+
else:
|
| 351 |
+
parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
|
| 352 |
+
|
| 353 |
+
# We have to wrap parsed.keys() in a set, because in the case of multiple
|
| 354 |
+
# values for a key (a list), the key will appear multiple times in the
|
| 355 |
+
# list of keys, but we're avoiding that by using get_all().
|
| 356 |
+
for name_with_case in frozenset(parsed.keys()):
|
| 357 |
+
# Header names in RFC are case insensitive, so we'll normalize to all
|
| 358 |
+
# lower case to make comparisons easier.
|
| 359 |
+
name = name_with_case.lower()
|
| 360 |
+
|
| 361 |
+
# We use get_all() here, even for fields that aren't multiple use,
|
| 362 |
+
# because otherwise someone could have e.g. two Name fields, and we
|
| 363 |
+
# would just silently ignore it rather than doing something about it.
|
| 364 |
+
headers = parsed.get_all(name) or []
|
| 365 |
+
|
| 366 |
+
# The way the email module works when parsing bytes is that it
|
| 367 |
+
# unconditionally decodes the bytes as ascii using the surrogateescape
|
| 368 |
+
# handler. When you pull that data back out (such as with get_all() ),
|
| 369 |
+
# it looks to see if the str has any surrogate escapes, and if it does
|
| 370 |
+
# it wraps it in a Header object instead of returning the string.
|
| 371 |
+
#
|
| 372 |
+
# As such, we'll look for those Header objects, and fix up the encoding.
|
| 373 |
+
value = []
|
| 374 |
+
# Flag if we have run into any issues processing the headers, thus
|
| 375 |
+
# signalling that the data belongs in 'unparsed'.
|
| 376 |
+
valid_encoding = True
|
| 377 |
+
for h in headers:
|
| 378 |
+
# It's unclear if this can return more types than just a Header or
|
| 379 |
+
# a str, so we'll just assert here to make sure.
|
| 380 |
+
assert isinstance(h, (email.header.Header, str))
|
| 381 |
+
|
| 382 |
+
# If it's a header object, we need to do our little dance to get
|
| 383 |
+
# the real data out of it. In cases where there is invalid data
|
| 384 |
+
# we're going to end up with mojibake, but there's no obvious, good
|
| 385 |
+
# way around that without reimplementing parts of the Header object
|
| 386 |
+
# ourselves.
|
| 387 |
+
#
|
| 388 |
+
# That should be fine since, if mojibacked happens, this key is
|
| 389 |
+
# going into the unparsed dict anyways.
|
| 390 |
+
if isinstance(h, email.header.Header):
|
| 391 |
+
# The Header object stores it's data as chunks, and each chunk
|
| 392 |
+
# can be independently encoded, so we'll need to check each
|
| 393 |
+
# of them.
|
| 394 |
+
chunks: list[tuple[bytes, str | None]] = []
|
| 395 |
+
for binary, _encoding in email.header.decode_header(h):
|
| 396 |
+
try:
|
| 397 |
+
binary.decode("utf8", "strict")
|
| 398 |
+
except UnicodeDecodeError:
|
| 399 |
+
# Enable mojibake.
|
| 400 |
+
encoding = "latin1"
|
| 401 |
+
valid_encoding = False
|
| 402 |
+
else:
|
| 403 |
+
encoding = "utf8"
|
| 404 |
+
chunks.append((binary, encoding))
|
| 405 |
+
|
| 406 |
+
# Turn our chunks back into a Header object, then let that
|
| 407 |
+
# Header object do the right thing to turn them into a
|
| 408 |
+
# string for us.
|
| 409 |
+
value.append(str(email.header.make_header(chunks)))
|
| 410 |
+
# This is already a string, so just add it.
|
| 411 |
+
else:
|
| 412 |
+
value.append(h)
|
| 413 |
+
|
| 414 |
+
# We've processed all of our values to get them into a list of str,
|
| 415 |
+
# but we may have mojibake data, in which case this is an unparsed
|
| 416 |
+
# field.
|
| 417 |
+
if not valid_encoding:
|
| 418 |
+
unparsed[name] = value
|
| 419 |
+
continue
|
| 420 |
+
|
| 421 |
+
raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
|
| 422 |
+
if raw_name is None:
|
| 423 |
+
# This is a bit of a weird situation, we've encountered a key that
|
| 424 |
+
# we don't know what it means, so we don't know whether it's meant
|
| 425 |
+
# to be a list or not.
|
| 426 |
+
#
|
| 427 |
+
# Since we can't really tell one way or another, we'll just leave it
|
| 428 |
+
# as a list, even though it may be a single item list, because that's
|
| 429 |
+
# what makes the most sense for email headers.
|
| 430 |
+
unparsed[name] = value
|
| 431 |
+
continue
|
| 432 |
+
|
| 433 |
+
# If this is one of our string fields, then we'll check to see if our
|
| 434 |
+
# value is a list of a single item. If it is then we'll assume that
|
| 435 |
+
# it was emitted as a single string, and unwrap the str from inside
|
| 436 |
+
# the list.
|
| 437 |
+
#
|
| 438 |
+
# If it's any other kind of data, then we haven't the faintest clue
|
| 439 |
+
# what we should parse it as, and we have to just add it to our list
|
| 440 |
+
# of unparsed stuff.
|
| 441 |
+
if raw_name in _STRING_FIELDS and len(value) == 1:
|
| 442 |
+
raw[raw_name] = value[0]
|
| 443 |
+
# If this is import_names, we need to special case the empty field
|
| 444 |
+
# case, which converts to an empty list instead of None. We can't let
|
| 445 |
+
# the empty case slip through, as it will fail validation.
|
| 446 |
+
elif raw_name == "import_names" and value == [""]:
|
| 447 |
+
raw[raw_name] = []
|
| 448 |
+
# If this is one of our list of string fields, then we can just assign
|
| 449 |
+
# the value, since email *only* has strings, and our get_all() call
|
| 450 |
+
# above ensures that this is a list.
|
| 451 |
+
elif raw_name in _LIST_FIELDS:
|
| 452 |
+
raw[raw_name] = value
|
| 453 |
+
# Special Case: Keywords
|
| 454 |
+
# The keywords field is implemented in the metadata spec as a str,
|
| 455 |
+
# but it conceptually is a list of strings, and is serialized using
|
| 456 |
+
# ", ".join(keywords), so we'll do some light data massaging to turn
|
| 457 |
+
# this into what it logically is.
|
| 458 |
+
elif raw_name == "keywords" and len(value) == 1:
|
| 459 |
+
raw[raw_name] = _parse_keywords(value[0])
|
| 460 |
+
# Special Case: Project-URL
|
| 461 |
+
# The project urls is implemented in the metadata spec as a list of
|
| 462 |
+
# specially-formatted strings that represent a key and a value, which
|
| 463 |
+
# is fundamentally a mapping, however the email format doesn't support
|
| 464 |
+
# mappings in a sane way, so it was crammed into a list of strings
|
| 465 |
+
# instead.
|
| 466 |
+
#
|
| 467 |
+
# We will do a little light data massaging to turn this into a map as
|
| 468 |
+
# it logically should be.
|
| 469 |
+
elif raw_name == "project_urls":
|
| 470 |
+
try:
|
| 471 |
+
raw[raw_name] = _parse_project_urls(value)
|
| 472 |
+
except KeyError:
|
| 473 |
+
unparsed[name] = value
|
| 474 |
+
# Nothing that we've done has managed to parse this, so it'll just
|
| 475 |
+
# throw it in our unparsable data and move on.
|
| 476 |
+
else:
|
| 477 |
+
unparsed[name] = value
|
| 478 |
+
|
| 479 |
+
# We need to support getting the Description from the message payload in
|
| 480 |
+
# addition to getting it from the the headers. This does mean, though, there
|
| 481 |
+
# is the possibility of it being set both ways, in which case we put both
|
| 482 |
+
# in 'unparsed' since we don't know which is right.
|
| 483 |
+
try:
|
| 484 |
+
payload = _get_payload(parsed, data)
|
| 485 |
+
except ValueError:
|
| 486 |
+
unparsed.setdefault("description", []).append(
|
| 487 |
+
parsed.get_payload(decode=isinstance(data, bytes)) # type: ignore[call-overload]
|
| 488 |
+
)
|
| 489 |
+
else:
|
| 490 |
+
if payload:
|
| 491 |
+
# Check to see if we've already got a description, if so then both
|
| 492 |
+
# it, and this body move to unparsable.
|
| 493 |
+
if "description" in raw:
|
| 494 |
+
description_header = cast("str", raw.pop("description"))
|
| 495 |
+
unparsed.setdefault("description", []).extend(
|
| 496 |
+
[description_header, payload]
|
| 497 |
+
)
|
| 498 |
+
elif "description" in unparsed:
|
| 499 |
+
unparsed["description"].append(payload)
|
| 500 |
+
else:
|
| 501 |
+
raw["description"] = payload
|
| 502 |
+
|
| 503 |
+
# We need to cast our `raw` to a metadata, because a TypedDict only support
|
| 504 |
+
# literal key names, but we're computing our key names on purpose, but the
|
| 505 |
+
# way this function is implemented, our `TypedDict` can only have valid key
|
| 506 |
+
# names.
|
| 507 |
+
return cast("RawMetadata", raw), unparsed
|
| 508 |
+
|
| 509 |
+
|
| 510 |
+
_NOT_FOUND = object()
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
# Keep the two values in sync.
|
| 514 |
+
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
|
| 515 |
+
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
|
| 516 |
+
|
| 517 |
+
_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
|
| 518 |
+
|
| 519 |
+
|
| 520 |
+
class _Validator(Generic[T]):
|
| 521 |
+
"""Validate a metadata field.
|
| 522 |
+
|
| 523 |
+
All _process_*() methods correspond to a core metadata field. The method is
|
| 524 |
+
called with the field's raw value. If the raw value is valid it is returned
|
| 525 |
+
in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field).
|
| 526 |
+
If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause
|
| 527 |
+
as appropriate).
|
| 528 |
+
"""
|
| 529 |
+
|
| 530 |
+
name: str
|
| 531 |
+
raw_name: str
|
| 532 |
+
added: _MetadataVersion
|
| 533 |
+
|
| 534 |
+
def __init__(
|
| 535 |
+
self,
|
| 536 |
+
*,
|
| 537 |
+
added: _MetadataVersion = "1.0",
|
| 538 |
+
) -> None:
|
| 539 |
+
self.added = added
|
| 540 |
+
|
| 541 |
+
def __set_name__(self, _owner: Metadata, name: str) -> None:
|
| 542 |
+
self.name = name
|
| 543 |
+
self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
|
| 544 |
+
|
| 545 |
+
def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T:
|
| 546 |
+
# With Python 3.8, the caching can be replaced with functools.cached_property().
|
| 547 |
+
# No need to check the cache as attribute lookup will resolve into the
|
| 548 |
+
# instance's __dict__ before __get__ is called.
|
| 549 |
+
cache = instance.__dict__
|
| 550 |
+
value = instance._raw.get(self.name)
|
| 551 |
+
|
| 552 |
+
# To make the _process_* methods easier, we'll check if the value is None
|
| 553 |
+
# and if this field is NOT a required attribute, and if both of those
|
| 554 |
+
# things are true, we'll skip the the converter. This will mean that the
|
| 555 |
+
# converters never have to deal with the None union.
|
| 556 |
+
if self.name in _REQUIRED_ATTRS or value is not None:
|
| 557 |
+
try:
|
| 558 |
+
converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}")
|
| 559 |
+
except AttributeError:
|
| 560 |
+
pass
|
| 561 |
+
else:
|
| 562 |
+
value = converter(value)
|
| 563 |
+
|
| 564 |
+
cache[self.name] = value
|
| 565 |
+
try:
|
| 566 |
+
del instance._raw[self.name] # type: ignore[misc]
|
| 567 |
+
except KeyError:
|
| 568 |
+
pass
|
| 569 |
+
|
| 570 |
+
return cast("T", value)
|
| 571 |
+
|
| 572 |
+
def _invalid_metadata(
|
| 573 |
+
self, msg: str, cause: Exception | None = None
|
| 574 |
+
) -> InvalidMetadata:
|
| 575 |
+
exc = InvalidMetadata(
|
| 576 |
+
self.raw_name, msg.format_map({"field": repr(self.raw_name)})
|
| 577 |
+
)
|
| 578 |
+
exc.__cause__ = cause
|
| 579 |
+
return exc
|
| 580 |
+
|
| 581 |
+
def _process_metadata_version(self, value: str) -> _MetadataVersion:
|
| 582 |
+
# Implicitly makes Metadata-Version required.
|
| 583 |
+
if value not in _VALID_METADATA_VERSIONS:
|
| 584 |
+
raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
|
| 585 |
+
return cast("_MetadataVersion", value)
|
| 586 |
+
|
| 587 |
+
def _process_name(self, value: str) -> str:
|
| 588 |
+
if not value:
|
| 589 |
+
raise self._invalid_metadata("{field} is a required field")
|
| 590 |
+
# Validate the name as a side-effect.
|
| 591 |
+
try:
|
| 592 |
+
utils.canonicalize_name(value, validate=True)
|
| 593 |
+
except utils.InvalidName as exc:
|
| 594 |
+
raise self._invalid_metadata(
|
| 595 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 596 |
+
) from exc
|
| 597 |
+
else:
|
| 598 |
+
return value
|
| 599 |
+
|
| 600 |
+
def _process_version(self, value: str) -> version_module.Version:
|
| 601 |
+
if not value:
|
| 602 |
+
raise self._invalid_metadata("{field} is a required field")
|
| 603 |
+
try:
|
| 604 |
+
return version_module.parse(value)
|
| 605 |
+
except version_module.InvalidVersion as exc:
|
| 606 |
+
raise self._invalid_metadata(
|
| 607 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 608 |
+
) from exc
|
| 609 |
+
|
| 610 |
+
def _process_summary(self, value: str) -> str:
|
| 611 |
+
"""Check the field contains no newlines."""
|
| 612 |
+
if "\n" in value:
|
| 613 |
+
raise self._invalid_metadata("{field} must be a single line")
|
| 614 |
+
return value
|
| 615 |
+
|
| 616 |
+
def _process_description_content_type(self, value: str) -> str:
|
| 617 |
+
content_types = {"text/plain", "text/x-rst", "text/markdown"}
|
| 618 |
+
message = email.message.EmailMessage()
|
| 619 |
+
message["content-type"] = value
|
| 620 |
+
|
| 621 |
+
content_type, parameters = (
|
| 622 |
+
# Defaults to `text/plain` if parsing failed.
|
| 623 |
+
message.get_content_type().lower(),
|
| 624 |
+
message["content-type"].params,
|
| 625 |
+
)
|
| 626 |
+
# Check if content-type is valid or defaulted to `text/plain` and thus was
|
| 627 |
+
# not parseable.
|
| 628 |
+
if content_type not in content_types or content_type not in value.lower():
|
| 629 |
+
raise self._invalid_metadata(
|
| 630 |
+
f"{{field}} must be one of {list(content_types)}, not {value!r}"
|
| 631 |
+
)
|
| 632 |
+
|
| 633 |
+
charset = parameters.get("charset", "UTF-8")
|
| 634 |
+
if charset != "UTF-8":
|
| 635 |
+
raise self._invalid_metadata(
|
| 636 |
+
f"{{field}} can only specify the UTF-8 charset, not {charset!r}"
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
markdown_variants = {"GFM", "CommonMark"}
|
| 640 |
+
variant = parameters.get("variant", "GFM") # Use an acceptable default.
|
| 641 |
+
if content_type == "text/markdown" and variant not in markdown_variants:
|
| 642 |
+
raise self._invalid_metadata(
|
| 643 |
+
f"valid Markdown variants for {{field}} are {list(markdown_variants)}, "
|
| 644 |
+
f"not {variant!r}",
|
| 645 |
+
)
|
| 646 |
+
return value
|
| 647 |
+
|
| 648 |
+
def _process_dynamic(self, value: list[str]) -> list[str]:
|
| 649 |
+
for dynamic_field in map(str.lower, value):
|
| 650 |
+
if dynamic_field in {"name", "version", "metadata-version"}:
|
| 651 |
+
raise self._invalid_metadata(
|
| 652 |
+
f"{dynamic_field!r} is not allowed as a dynamic field"
|
| 653 |
+
)
|
| 654 |
+
elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
|
| 655 |
+
raise self._invalid_metadata(
|
| 656 |
+
f"{dynamic_field!r} is not a valid dynamic field"
|
| 657 |
+
)
|
| 658 |
+
return list(map(str.lower, value))
|
| 659 |
+
|
| 660 |
+
def _process_provides_extra(
|
| 661 |
+
self,
|
| 662 |
+
value: list[str],
|
| 663 |
+
) -> list[utils.NormalizedName]:
|
| 664 |
+
normalized_names = []
|
| 665 |
+
try:
|
| 666 |
+
for name in value:
|
| 667 |
+
normalized_names.append(utils.canonicalize_name(name, validate=True))
|
| 668 |
+
except utils.InvalidName as exc:
|
| 669 |
+
raise self._invalid_metadata(
|
| 670 |
+
f"{name!r} is invalid for {{field}}", cause=exc
|
| 671 |
+
) from exc
|
| 672 |
+
else:
|
| 673 |
+
return normalized_names
|
| 674 |
+
|
| 675 |
+
def _process_requires_python(self, value: str) -> specifiers.SpecifierSet:
|
| 676 |
+
try:
|
| 677 |
+
return specifiers.SpecifierSet(value)
|
| 678 |
+
except specifiers.InvalidSpecifier as exc:
|
| 679 |
+
raise self._invalid_metadata(
|
| 680 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 681 |
+
) from exc
|
| 682 |
+
|
| 683 |
+
def _process_requires_dist(
|
| 684 |
+
self,
|
| 685 |
+
value: list[str],
|
| 686 |
+
) -> list[requirements.Requirement]:
|
| 687 |
+
reqs = []
|
| 688 |
+
try:
|
| 689 |
+
for req in value:
|
| 690 |
+
reqs.append(requirements.Requirement(req))
|
| 691 |
+
except requirements.InvalidRequirement as exc:
|
| 692 |
+
raise self._invalid_metadata(
|
| 693 |
+
f"{req!r} is invalid for {{field}}", cause=exc
|
| 694 |
+
) from exc
|
| 695 |
+
else:
|
| 696 |
+
return reqs
|
| 697 |
+
|
| 698 |
+
def _process_license_expression(self, value: str) -> NormalizedLicenseExpression:
|
| 699 |
+
try:
|
| 700 |
+
return licenses.canonicalize_license_expression(value)
|
| 701 |
+
except ValueError as exc:
|
| 702 |
+
raise self._invalid_metadata(
|
| 703 |
+
f"{value!r} is invalid for {{field}}", cause=exc
|
| 704 |
+
) from exc
|
| 705 |
+
|
| 706 |
+
def _process_license_files(self, value: list[str]) -> list[str]:
|
| 707 |
+
paths = []
|
| 708 |
+
for path in value:
|
| 709 |
+
if ".." in path:
|
| 710 |
+
raise self._invalid_metadata(
|
| 711 |
+
f"{path!r} is invalid for {{field}}, "
|
| 712 |
+
"parent directory indicators are not allowed"
|
| 713 |
+
)
|
| 714 |
+
if "*" in path:
|
| 715 |
+
raise self._invalid_metadata(
|
| 716 |
+
f"{path!r} is invalid for {{field}}, paths must be resolved"
|
| 717 |
+
)
|
| 718 |
+
if (
|
| 719 |
+
pathlib.PurePosixPath(path).is_absolute()
|
| 720 |
+
or pathlib.PureWindowsPath(path).is_absolute()
|
| 721 |
+
):
|
| 722 |
+
raise self._invalid_metadata(
|
| 723 |
+
f"{path!r} is invalid for {{field}}, paths must be relative"
|
| 724 |
+
)
|
| 725 |
+
if pathlib.PureWindowsPath(path).as_posix() != path:
|
| 726 |
+
raise self._invalid_metadata(
|
| 727 |
+
f"{path!r} is invalid for {{field}}, paths must use '/' delimiter"
|
| 728 |
+
)
|
| 729 |
+
paths.append(path)
|
| 730 |
+
return paths
|
| 731 |
+
|
| 732 |
+
def _process_import_names(self, value: list[str]) -> list[str]:
|
| 733 |
+
for import_name in value:
|
| 734 |
+
name, semicolon, private = import_name.partition(";")
|
| 735 |
+
name = name.rstrip()
|
| 736 |
+
for identifier in name.split("."):
|
| 737 |
+
if not identifier.isidentifier():
|
| 738 |
+
raise self._invalid_metadata(
|
| 739 |
+
f"{name!r} is invalid for {{field}}; "
|
| 740 |
+
f"{identifier!r} is not a valid identifier"
|
| 741 |
+
)
|
| 742 |
+
elif keyword.iskeyword(identifier):
|
| 743 |
+
raise self._invalid_metadata(
|
| 744 |
+
f"{name!r} is invalid for {{field}}; "
|
| 745 |
+
f"{identifier!r} is a keyword"
|
| 746 |
+
)
|
| 747 |
+
if semicolon and private.lstrip() != "private":
|
| 748 |
+
raise self._invalid_metadata(
|
| 749 |
+
f"{import_name!r} is invalid for {{field}}; "
|
| 750 |
+
"the only valid option is 'private'"
|
| 751 |
+
)
|
| 752 |
+
return value
|
| 753 |
+
|
| 754 |
+
_process_import_namespaces = _process_import_names
|
| 755 |
+
|
| 756 |
+
|
| 757 |
+
class Metadata:
|
| 758 |
+
"""Representation of distribution metadata.
|
| 759 |
+
|
| 760 |
+
Compared to :class:`RawMetadata`, this class provides objects representing
|
| 761 |
+
metadata fields instead of only using built-in types. Any invalid metadata
|
| 762 |
+
will cause :exc:`InvalidMetadata` to be raised (with a
|
| 763 |
+
:py:attr:`~BaseException.__cause__` attribute as appropriate).
|
| 764 |
+
"""
|
| 765 |
+
|
| 766 |
+
_raw: RawMetadata
|
| 767 |
+
|
| 768 |
+
@classmethod
|
| 769 |
+
def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata:
|
| 770 |
+
"""Create an instance from :class:`RawMetadata`.
|
| 771 |
+
|
| 772 |
+
If *validate* is true, all metadata will be validated. All exceptions
|
| 773 |
+
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
|
| 774 |
+
"""
|
| 775 |
+
ins = cls()
|
| 776 |
+
ins._raw = data.copy() # Mutations occur due to caching enriched values.
|
| 777 |
+
|
| 778 |
+
if validate:
|
| 779 |
+
collector = _ErrorCollector()
|
| 780 |
+
metadata_version = None
|
| 781 |
+
with collector.collect(InvalidMetadata):
|
| 782 |
+
metadata_version = ins.metadata_version
|
| 783 |
+
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
|
| 784 |
+
|
| 785 |
+
# Make sure to check for the fields that are present, the required
|
| 786 |
+
# fields (so their absence can be reported).
|
| 787 |
+
fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS
|
| 788 |
+
# Remove fields that have already been checked.
|
| 789 |
+
fields_to_check -= {"metadata_version"}
|
| 790 |
+
|
| 791 |
+
for key in fields_to_check:
|
| 792 |
+
try:
|
| 793 |
+
if metadata_version:
|
| 794 |
+
# Can't use getattr() as that triggers descriptor protocol which
|
| 795 |
+
# will fail due to no value for the instance argument.
|
| 796 |
+
try:
|
| 797 |
+
field_metadata_version = cls.__dict__[key].added
|
| 798 |
+
except KeyError:
|
| 799 |
+
exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
|
| 800 |
+
collector.error(exc)
|
| 801 |
+
continue
|
| 802 |
+
field_age = _VALID_METADATA_VERSIONS.index(
|
| 803 |
+
field_metadata_version
|
| 804 |
+
)
|
| 805 |
+
if field_age > metadata_age:
|
| 806 |
+
field = _RAW_TO_EMAIL_MAPPING[key]
|
| 807 |
+
exc = InvalidMetadata(
|
| 808 |
+
field,
|
| 809 |
+
f"{field} introduced in metadata version "
|
| 810 |
+
f"{field_metadata_version}, not {metadata_version}",
|
| 811 |
+
)
|
| 812 |
+
collector.error(exc)
|
| 813 |
+
continue
|
| 814 |
+
getattr(ins, key)
|
| 815 |
+
except InvalidMetadata as exc:
|
| 816 |
+
collector.error(exc)
|
| 817 |
+
|
| 818 |
+
collector.finalize("invalid metadata")
|
| 819 |
+
|
| 820 |
+
return ins
|
| 821 |
+
|
| 822 |
+
@classmethod
|
| 823 |
+
def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
|
| 824 |
+
"""Parse metadata from email headers.
|
| 825 |
+
|
| 826 |
+
If *validate* is true, the metadata will be validated. All exceptions
|
| 827 |
+
related to validation will be gathered and raised as an :class:`ExceptionGroup`.
|
| 828 |
+
"""
|
| 829 |
+
raw, unparsed = parse_email(data)
|
| 830 |
+
|
| 831 |
+
if validate:
|
| 832 |
+
with _ErrorCollector().on_exit("unparsed") as collector:
|
| 833 |
+
for unparsed_key in unparsed:
|
| 834 |
+
if unparsed_key in _EMAIL_TO_RAW_MAPPING:
|
| 835 |
+
message = f"{unparsed_key!r} has invalid data"
|
| 836 |
+
else:
|
| 837 |
+
message = f"unrecognized field: {unparsed_key!r}"
|
| 838 |
+
collector.error(InvalidMetadata(unparsed_key, message))
|
| 839 |
+
|
| 840 |
+
try:
|
| 841 |
+
return cls.from_raw(raw, validate=validate)
|
| 842 |
+
except ExceptionGroup as exc_group:
|
| 843 |
+
raise ExceptionGroup(
|
| 844 |
+
"invalid or unparsed metadata", exc_group.exceptions
|
| 845 |
+
) from None
|
| 846 |
+
|
| 847 |
+
metadata_version: _Validator[_MetadataVersion] = _Validator()
|
| 848 |
+
""":external:ref:`core-metadata-metadata-version`
|
| 849 |
+
(required; validated to be a valid metadata version)"""
|
| 850 |
+
# `name` is not normalized/typed to NormalizedName so as to provide access to
|
| 851 |
+
# the original/raw name.
|
| 852 |
+
name: _Validator[str] = _Validator()
|
| 853 |
+
""":external:ref:`core-metadata-name`
|
| 854 |
+
(required; validated using :func:`~packaging.utils.canonicalize_name` and its
|
| 855 |
+
*validate* parameter)"""
|
| 856 |
+
version: _Validator[version_module.Version] = _Validator()
|
| 857 |
+
""":external:ref:`core-metadata-version` (required)"""
|
| 858 |
+
dynamic: _Validator[list[str] | None] = _Validator(
|
| 859 |
+
added="2.2",
|
| 860 |
+
)
|
| 861 |
+
""":external:ref:`core-metadata-dynamic`
|
| 862 |
+
(validated against core metadata field names and lowercased)"""
|
| 863 |
+
platforms: _Validator[list[str] | None] = _Validator()
|
| 864 |
+
""":external:ref:`core-metadata-platform`"""
|
| 865 |
+
supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 866 |
+
""":external:ref:`core-metadata-supported-platform`"""
|
| 867 |
+
summary: _Validator[str | None] = _Validator()
|
| 868 |
+
""":external:ref:`core-metadata-summary` (validated to contain no newlines)"""
|
| 869 |
+
description: _Validator[str | None] = _Validator() # TODO 2.1: can be in body
|
| 870 |
+
""":external:ref:`core-metadata-description`"""
|
| 871 |
+
description_content_type: _Validator[str | None] = _Validator(added="2.1")
|
| 872 |
+
""":external:ref:`core-metadata-description-content-type` (validated)"""
|
| 873 |
+
keywords: _Validator[list[str] | None] = _Validator()
|
| 874 |
+
""":external:ref:`core-metadata-keywords`"""
|
| 875 |
+
home_page: _Validator[str | None] = _Validator()
|
| 876 |
+
""":external:ref:`core-metadata-home-page`"""
|
| 877 |
+
download_url: _Validator[str | None] = _Validator(added="1.1")
|
| 878 |
+
""":external:ref:`core-metadata-download-url`"""
|
| 879 |
+
author: _Validator[str | None] = _Validator()
|
| 880 |
+
""":external:ref:`core-metadata-author`"""
|
| 881 |
+
author_email: _Validator[str | None] = _Validator()
|
| 882 |
+
""":external:ref:`core-metadata-author-email`"""
|
| 883 |
+
maintainer: _Validator[str | None] = _Validator(added="1.2")
|
| 884 |
+
""":external:ref:`core-metadata-maintainer`"""
|
| 885 |
+
maintainer_email: _Validator[str | None] = _Validator(added="1.2")
|
| 886 |
+
""":external:ref:`core-metadata-maintainer-email`"""
|
| 887 |
+
license: _Validator[str | None] = _Validator()
|
| 888 |
+
""":external:ref:`core-metadata-license`"""
|
| 889 |
+
license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator(
|
| 890 |
+
added="2.4"
|
| 891 |
+
)
|
| 892 |
+
""":external:ref:`core-metadata-license-expression`"""
|
| 893 |
+
license_files: _Validator[list[str] | None] = _Validator(added="2.4")
|
| 894 |
+
""":external:ref:`core-metadata-license-file`"""
|
| 895 |
+
classifiers: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 896 |
+
""":external:ref:`core-metadata-classifier`"""
|
| 897 |
+
requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator(
|
| 898 |
+
added="1.2"
|
| 899 |
+
)
|
| 900 |
+
""":external:ref:`core-metadata-requires-dist`"""
|
| 901 |
+
requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator(
|
| 902 |
+
added="1.2"
|
| 903 |
+
)
|
| 904 |
+
""":external:ref:`core-metadata-requires-python`"""
|
| 905 |
+
# Because `Requires-External` allows for non-PEP 440 version specifiers, we
|
| 906 |
+
# don't do any processing on the values.
|
| 907 |
+
requires_external: _Validator[list[str] | None] = _Validator(added="1.2")
|
| 908 |
+
""":external:ref:`core-metadata-requires-external`"""
|
| 909 |
+
project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2")
|
| 910 |
+
""":external:ref:`core-metadata-project-url`"""
|
| 911 |
+
# PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
|
| 912 |
+
# regardless of metadata version.
|
| 913 |
+
provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator(
|
| 914 |
+
added="2.1",
|
| 915 |
+
)
|
| 916 |
+
""":external:ref:`core-metadata-provides-extra`"""
|
| 917 |
+
provides_dist: _Validator[list[str] | None] = _Validator(added="1.2")
|
| 918 |
+
""":external:ref:`core-metadata-provides-dist`"""
|
| 919 |
+
obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
|
| 920 |
+
""":external:ref:`core-metadata-obsoletes-dist`"""
|
| 921 |
+
import_names: _Validator[list[str] | None] = _Validator(added="2.5")
|
| 922 |
+
""":external:ref:`core-metadata-import-name`"""
|
| 923 |
+
import_namespaces: _Validator[list[str] | None] = _Validator(added="2.5")
|
| 924 |
+
""":external:ref:`core-metadata-import-namespace`"""
|
| 925 |
+
requires: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 926 |
+
"""``Requires`` (deprecated)"""
|
| 927 |
+
provides: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 928 |
+
"""``Provides`` (deprecated)"""
|
| 929 |
+
obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
|
| 930 |
+
"""``Obsoletes`` (deprecated)"""
|
| 931 |
+
|
| 932 |
+
def as_rfc822(self) -> RFC822Message:
|
| 933 |
+
"""
|
| 934 |
+
Return an RFC822 message with the metadata.
|
| 935 |
+
"""
|
| 936 |
+
message = RFC822Message()
|
| 937 |
+
self._write_metadata(message)
|
| 938 |
+
return message
|
| 939 |
+
|
| 940 |
+
def _write_metadata(self, message: RFC822Message) -> None:
|
| 941 |
+
"""
|
| 942 |
+
Return an RFC822 message with the metadata.
|
| 943 |
+
"""
|
| 944 |
+
for name, validator in self.__class__.__dict__.items():
|
| 945 |
+
if isinstance(validator, _Validator) and name != "description":
|
| 946 |
+
value = getattr(self, name)
|
| 947 |
+
email_name = _RAW_TO_EMAIL_MAPPING[name]
|
| 948 |
+
if value is not None:
|
| 949 |
+
if email_name == "project-url":
|
| 950 |
+
for label, url in value.items():
|
| 951 |
+
message[email_name] = f"{label}, {url}"
|
| 952 |
+
elif email_name == "keywords":
|
| 953 |
+
message[email_name] = ",".join(value)
|
| 954 |
+
elif email_name == "import-name" and value == []:
|
| 955 |
+
message[email_name] = ""
|
| 956 |
+
elif isinstance(value, list):
|
| 957 |
+
for item in value:
|
| 958 |
+
message[email_name] = str(item)
|
| 959 |
+
else:
|
| 960 |
+
message[email_name] = str(value)
|
| 961 |
+
|
| 962 |
+
# The description is a special case because it is in the body of the message.
|
| 963 |
+
if self.description is not None:
|
| 964 |
+
message.set_payload(self.description)
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/py.typed
ADDED
|
File without changes
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/pylock.py
ADDED
|
@@ -0,0 +1,905 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import dataclasses
|
| 4 |
+
import logging
|
| 5 |
+
import re
|
| 6 |
+
from collections.abc import Mapping, Sequence
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from typing import (
|
| 10 |
+
TYPE_CHECKING,
|
| 11 |
+
Any,
|
| 12 |
+
Callable,
|
| 13 |
+
Protocol,
|
| 14 |
+
TypeVar,
|
| 15 |
+
cast,
|
| 16 |
+
)
|
| 17 |
+
from urllib.parse import urlparse
|
| 18 |
+
|
| 19 |
+
from .markers import Environment, Marker, default_environment
|
| 20 |
+
from .specifiers import SpecifierSet
|
| 21 |
+
from .tags import create_compatible_tags_selector, sys_tags
|
| 22 |
+
from .utils import (
|
| 23 |
+
NormalizedName,
|
| 24 |
+
is_normalized_name,
|
| 25 |
+
parse_sdist_filename,
|
| 26 |
+
parse_wheel_filename,
|
| 27 |
+
)
|
| 28 |
+
from .version import Version
|
| 29 |
+
|
| 30 |
+
if TYPE_CHECKING: # pragma: no cover
|
| 31 |
+
from collections.abc import Collection, Iterator
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
|
| 34 |
+
from typing_extensions import Self
|
| 35 |
+
|
| 36 |
+
from .tags import Tag
|
| 37 |
+
|
| 38 |
+
_logger = logging.getLogger(__name__)
|
| 39 |
+
|
| 40 |
+
__all__ = [
|
| 41 |
+
"Package",
|
| 42 |
+
"PackageArchive",
|
| 43 |
+
"PackageDirectory",
|
| 44 |
+
"PackageSdist",
|
| 45 |
+
"PackageVcs",
|
| 46 |
+
"PackageWheel",
|
| 47 |
+
"Pylock",
|
| 48 |
+
"PylockUnsupportedVersionError",
|
| 49 |
+
"PylockValidationError",
|
| 50 |
+
"is_valid_pylock_path",
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def __dir__() -> list[str]:
|
| 55 |
+
return __all__
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
_T = TypeVar("_T")
|
| 59 |
+
_T2 = TypeVar("_T2")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class _FromMappingProtocol(Protocol): # pragma: no cover
|
| 63 |
+
@classmethod
|
| 64 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
_PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def is_valid_pylock_path(path: Path) -> bool:
|
| 74 |
+
"""Check if the given path is a valid pylock file path."""
|
| 75 |
+
return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _toml_key(key: str) -> str:
|
| 79 |
+
return key.replace("_", "-")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _toml_value(key: str, value: Any) -> Any: # noqa: ANN401
|
| 83 |
+
if isinstance(value, (Version, Marker, SpecifierSet)):
|
| 84 |
+
return str(value)
|
| 85 |
+
if isinstance(value, Sequence) and key == "environments":
|
| 86 |
+
return [str(v) for v in value]
|
| 87 |
+
return value
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
|
| 91 |
+
return {
|
| 92 |
+
_toml_key(key): _toml_value(key, value)
|
| 93 |
+
for key, value in data
|
| 94 |
+
if value is not None
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
| 99 |
+
"""Get a value from the dictionary and verify it's the expected type."""
|
| 100 |
+
if (value := d.get(key)) is None:
|
| 101 |
+
return None
|
| 102 |
+
if not isinstance(value, expected_type):
|
| 103 |
+
raise PylockValidationError(
|
| 104 |
+
f"Unexpected type {type(value).__name__} "
|
| 105 |
+
f"(expected {expected_type.__name__})",
|
| 106 |
+
context=key,
|
| 107 |
+
)
|
| 108 |
+
return value
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
|
| 112 |
+
"""Get a required value from the dictionary and verify it's the expected type."""
|
| 113 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 114 |
+
raise _PylockRequiredKeyError(key)
|
| 115 |
+
return value
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def _get_sequence(
|
| 119 |
+
d: Mapping[str, Any], expected_item_type: type[_T], key: str
|
| 120 |
+
) -> Sequence[_T] | None:
|
| 121 |
+
"""Get a list value from the dictionary and verify it's the expected items type."""
|
| 122 |
+
if (value := _get(d, Sequence, key)) is None: # type: ignore[type-abstract]
|
| 123 |
+
return None
|
| 124 |
+
if isinstance(value, (str, bytes)):
|
| 125 |
+
# special case: str and bytes are Sequences, but we want to reject it
|
| 126 |
+
raise PylockValidationError(
|
| 127 |
+
f"Unexpected type {type(value).__name__} (expected Sequence)",
|
| 128 |
+
context=key,
|
| 129 |
+
)
|
| 130 |
+
for i, item in enumerate(value):
|
| 131 |
+
if not isinstance(item, expected_item_type):
|
| 132 |
+
raise PylockValidationError(
|
| 133 |
+
f"Unexpected type {type(item).__name__} "
|
| 134 |
+
f"(expected {expected_item_type.__name__})",
|
| 135 |
+
context=f"{key}[{i}]",
|
| 136 |
+
)
|
| 137 |
+
return value
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _get_as(
|
| 141 |
+
d: Mapping[str, Any],
|
| 142 |
+
expected_type: type[_T],
|
| 143 |
+
target_type: Callable[[_T], _T2],
|
| 144 |
+
key: str,
|
| 145 |
+
) -> _T2 | None:
|
| 146 |
+
"""Get a value from the dictionary, verify it's the expected type,
|
| 147 |
+
and convert to the target type.
|
| 148 |
+
|
| 149 |
+
This assumes the target_type constructor accepts the value.
|
| 150 |
+
"""
|
| 151 |
+
if (value := _get(d, expected_type, key)) is None:
|
| 152 |
+
return None
|
| 153 |
+
try:
|
| 154 |
+
return target_type(value)
|
| 155 |
+
except Exception as e:
|
| 156 |
+
raise PylockValidationError(e, context=key) from e
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _get_required_as(
|
| 160 |
+
d: Mapping[str, Any],
|
| 161 |
+
expected_type: type[_T],
|
| 162 |
+
target_type: Callable[[_T], _T2],
|
| 163 |
+
key: str,
|
| 164 |
+
) -> _T2:
|
| 165 |
+
"""Get a required value from the dict, verify it's the expected type,
|
| 166 |
+
and convert to the target type."""
|
| 167 |
+
if (value := _get_as(d, expected_type, target_type, key)) is None:
|
| 168 |
+
raise _PylockRequiredKeyError(key)
|
| 169 |
+
return value
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def _get_sequence_as(
|
| 173 |
+
d: Mapping[str, Any],
|
| 174 |
+
expected_item_type: type[_T],
|
| 175 |
+
target_item_type: Callable[[_T], _T2],
|
| 176 |
+
key: str,
|
| 177 |
+
) -> list[_T2] | None:
|
| 178 |
+
"""Get list value from dictionary and verify expected items type."""
|
| 179 |
+
if (value := _get_sequence(d, expected_item_type, key)) is None:
|
| 180 |
+
return None
|
| 181 |
+
result = []
|
| 182 |
+
try:
|
| 183 |
+
for item in value:
|
| 184 |
+
typed_item = target_item_type(item)
|
| 185 |
+
result.append(typed_item)
|
| 186 |
+
except Exception as e:
|
| 187 |
+
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
| 188 |
+
return result
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def _get_object(
|
| 192 |
+
d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
|
| 193 |
+
) -> _FromMappingProtocolT | None:
|
| 194 |
+
"""Get a dictionary value from the dictionary and convert it to a dataclass."""
|
| 195 |
+
if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 196 |
+
return None
|
| 197 |
+
try:
|
| 198 |
+
return target_type._from_dict(value)
|
| 199 |
+
except Exception as e:
|
| 200 |
+
raise PylockValidationError(e, context=key) from e
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def _get_sequence_of_objects(
|
| 204 |
+
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
| 205 |
+
) -> list[_FromMappingProtocolT] | None:
|
| 206 |
+
"""Get a list value from the dictionary and convert its items to a dataclass."""
|
| 207 |
+
if (value := _get_sequence(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
| 208 |
+
return None
|
| 209 |
+
result: list[_FromMappingProtocolT] = []
|
| 210 |
+
try:
|
| 211 |
+
for item in value:
|
| 212 |
+
typed_item = target_item_type._from_dict(item)
|
| 213 |
+
result.append(typed_item)
|
| 214 |
+
except Exception as e:
|
| 215 |
+
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
| 216 |
+
return result
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def _get_required_sequence_of_objects(
|
| 220 |
+
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
| 221 |
+
) -> Sequence[_FromMappingProtocolT]:
|
| 222 |
+
"""Get a required list value from the dictionary and convert its items to a
|
| 223 |
+
dataclass."""
|
| 224 |
+
if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:
|
| 225 |
+
raise _PylockRequiredKeyError(key)
|
| 226 |
+
return result
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def _validate_normalized_name(name: str) -> NormalizedName:
|
| 230 |
+
"""Validate that a string is a NormalizedName."""
|
| 231 |
+
if not is_normalized_name(name):
|
| 232 |
+
raise PylockValidationError(f"Name {name!r} is not normalized")
|
| 233 |
+
return NormalizedName(name)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def _validate_path_url(path: str | None, url: str | None) -> None:
|
| 237 |
+
if not path and not url:
|
| 238 |
+
raise PylockValidationError("path or url must be provided")
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def _path_name(path: str | None) -> str | None:
|
| 242 |
+
if not path:
|
| 243 |
+
return None
|
| 244 |
+
# If the path is relative it MAY use POSIX-style path separators explicitly
|
| 245 |
+
# for portability
|
| 246 |
+
if "/" in path:
|
| 247 |
+
return path.rsplit("/", 1)[-1]
|
| 248 |
+
elif "\\" in path:
|
| 249 |
+
return path.rsplit("\\", 1)[-1]
|
| 250 |
+
else:
|
| 251 |
+
return path
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def _url_name(url: str | None) -> str | None:
|
| 255 |
+
if not url:
|
| 256 |
+
return None
|
| 257 |
+
url_path = urlparse(url).path
|
| 258 |
+
return url_path.rsplit("/", 1)[-1]
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
|
| 262 |
+
if not hashes:
|
| 263 |
+
raise PylockValidationError("At least one hash must be provided")
|
| 264 |
+
if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
|
| 265 |
+
raise PylockValidationError("Hash values must be strings")
|
| 266 |
+
return hashes
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class PylockValidationError(Exception):
|
| 270 |
+
"""Raised when when input data is not spec-compliant."""
|
| 271 |
+
|
| 272 |
+
context: str | None = None
|
| 273 |
+
message: str
|
| 274 |
+
|
| 275 |
+
def __init__(
|
| 276 |
+
self,
|
| 277 |
+
cause: str | Exception,
|
| 278 |
+
*,
|
| 279 |
+
context: str | None = None,
|
| 280 |
+
) -> None:
|
| 281 |
+
if isinstance(cause, PylockValidationError):
|
| 282 |
+
if cause.context:
|
| 283 |
+
self.context = (
|
| 284 |
+
f"{context}.{cause.context}" if context else cause.context
|
| 285 |
+
)
|
| 286 |
+
else:
|
| 287 |
+
self.context = context
|
| 288 |
+
self.message = cause.message
|
| 289 |
+
else:
|
| 290 |
+
self.context = context
|
| 291 |
+
self.message = str(cause)
|
| 292 |
+
|
| 293 |
+
def __str__(self) -> str:
|
| 294 |
+
if self.context:
|
| 295 |
+
return f"{self.message} in {self.context!r}"
|
| 296 |
+
return self.message
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
class _PylockRequiredKeyError(PylockValidationError):
|
| 300 |
+
def __init__(self, key: str) -> None:
|
| 301 |
+
super().__init__("Missing required value", context=key)
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
class PylockUnsupportedVersionError(PylockValidationError):
|
| 305 |
+
"""Raised when encountering an unsupported `lock_version`."""
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
class PylockSelectError(Exception):
|
| 309 |
+
"""Base exception for errors raised by :meth:`Pylock.select`."""
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
@dataclass(frozen=True, init=False)
|
| 313 |
+
class PackageVcs:
|
| 314 |
+
type: str
|
| 315 |
+
url: str | None = None
|
| 316 |
+
path: str | None = None
|
| 317 |
+
requested_revision: str | None = None
|
| 318 |
+
commit_id: str # type: ignore[misc]
|
| 319 |
+
subdirectory: str | None = None
|
| 320 |
+
|
| 321 |
+
def __init__(
|
| 322 |
+
self,
|
| 323 |
+
*,
|
| 324 |
+
type: str,
|
| 325 |
+
url: str | None = None,
|
| 326 |
+
path: str | None = None,
|
| 327 |
+
requested_revision: str | None = None,
|
| 328 |
+
commit_id: str,
|
| 329 |
+
subdirectory: str | None = None,
|
| 330 |
+
) -> None:
|
| 331 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 332 |
+
object.__setattr__(self, "type", type)
|
| 333 |
+
object.__setattr__(self, "url", url)
|
| 334 |
+
object.__setattr__(self, "path", path)
|
| 335 |
+
object.__setattr__(self, "requested_revision", requested_revision)
|
| 336 |
+
object.__setattr__(self, "commit_id", commit_id)
|
| 337 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 338 |
+
|
| 339 |
+
@classmethod
|
| 340 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 341 |
+
package_vcs = cls(
|
| 342 |
+
type=_get_required(d, str, "type"),
|
| 343 |
+
url=_get(d, str, "url"),
|
| 344 |
+
path=_get(d, str, "path"),
|
| 345 |
+
requested_revision=_get(d, str, "requested-revision"),
|
| 346 |
+
commit_id=_get_required(d, str, "commit-id"),
|
| 347 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 348 |
+
)
|
| 349 |
+
_validate_path_url(package_vcs.path, package_vcs.url)
|
| 350 |
+
return package_vcs
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
@dataclass(frozen=True, init=False)
|
| 354 |
+
class PackageDirectory:
|
| 355 |
+
path: str
|
| 356 |
+
editable: bool | None = None
|
| 357 |
+
subdirectory: str | None = None
|
| 358 |
+
|
| 359 |
+
def __init__(
|
| 360 |
+
self,
|
| 361 |
+
*,
|
| 362 |
+
path: str,
|
| 363 |
+
editable: bool | None = None,
|
| 364 |
+
subdirectory: str | None = None,
|
| 365 |
+
) -> None:
|
| 366 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 367 |
+
object.__setattr__(self, "path", path)
|
| 368 |
+
object.__setattr__(self, "editable", editable)
|
| 369 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 370 |
+
|
| 371 |
+
@classmethod
|
| 372 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 373 |
+
return cls(
|
| 374 |
+
path=_get_required(d, str, "path"),
|
| 375 |
+
editable=_get(d, bool, "editable"),
|
| 376 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
@dataclass(frozen=True, init=False)
|
| 381 |
+
class PackageArchive:
|
| 382 |
+
url: str | None = None
|
| 383 |
+
path: str | None = None
|
| 384 |
+
size: int | None = None
|
| 385 |
+
upload_time: datetime | None = None
|
| 386 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 387 |
+
subdirectory: str | None = None
|
| 388 |
+
|
| 389 |
+
def __init__(
|
| 390 |
+
self,
|
| 391 |
+
*,
|
| 392 |
+
url: str | None = None,
|
| 393 |
+
path: str | None = None,
|
| 394 |
+
size: int | None = None,
|
| 395 |
+
upload_time: datetime | None = None,
|
| 396 |
+
hashes: Mapping[str, str],
|
| 397 |
+
subdirectory: str | None = None,
|
| 398 |
+
) -> None:
|
| 399 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 400 |
+
object.__setattr__(self, "url", url)
|
| 401 |
+
object.__setattr__(self, "path", path)
|
| 402 |
+
object.__setattr__(self, "size", size)
|
| 403 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 404 |
+
object.__setattr__(self, "hashes", hashes)
|
| 405 |
+
object.__setattr__(self, "subdirectory", subdirectory)
|
| 406 |
+
|
| 407 |
+
@classmethod
|
| 408 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 409 |
+
package_archive = cls(
|
| 410 |
+
url=_get(d, str, "url"),
|
| 411 |
+
path=_get(d, str, "path"),
|
| 412 |
+
size=_get(d, int, "size"),
|
| 413 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 414 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 415 |
+
subdirectory=_get(d, str, "subdirectory"),
|
| 416 |
+
)
|
| 417 |
+
_validate_path_url(package_archive.path, package_archive.url)
|
| 418 |
+
return package_archive
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
@dataclass(frozen=True, init=False)
|
| 422 |
+
class PackageSdist:
|
| 423 |
+
name: str | None = None
|
| 424 |
+
upload_time: datetime | None = None
|
| 425 |
+
url: str | None = None
|
| 426 |
+
path: str | None = None
|
| 427 |
+
size: int | None = None
|
| 428 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 429 |
+
|
| 430 |
+
def __init__(
|
| 431 |
+
self,
|
| 432 |
+
*,
|
| 433 |
+
name: str | None = None,
|
| 434 |
+
upload_time: datetime | None = None,
|
| 435 |
+
url: str | None = None,
|
| 436 |
+
path: str | None = None,
|
| 437 |
+
size: int | None = None,
|
| 438 |
+
hashes: Mapping[str, str],
|
| 439 |
+
) -> None:
|
| 440 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 441 |
+
object.__setattr__(self, "name", name)
|
| 442 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 443 |
+
object.__setattr__(self, "url", url)
|
| 444 |
+
object.__setattr__(self, "path", path)
|
| 445 |
+
object.__setattr__(self, "size", size)
|
| 446 |
+
object.__setattr__(self, "hashes", hashes)
|
| 447 |
+
|
| 448 |
+
@classmethod
|
| 449 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 450 |
+
package_sdist = cls(
|
| 451 |
+
name=_get(d, str, "name"),
|
| 452 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 453 |
+
url=_get(d, str, "url"),
|
| 454 |
+
path=_get(d, str, "path"),
|
| 455 |
+
size=_get(d, int, "size"),
|
| 456 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 457 |
+
)
|
| 458 |
+
_validate_path_url(package_sdist.path, package_sdist.url)
|
| 459 |
+
return package_sdist
|
| 460 |
+
|
| 461 |
+
@property
|
| 462 |
+
def filename(self) -> str:
|
| 463 |
+
"""Get the filename of the sdist."""
|
| 464 |
+
filename = self.name or _path_name(self.path) or _url_name(self.url)
|
| 465 |
+
if not filename:
|
| 466 |
+
raise PylockValidationError("Cannot determine sdist filename")
|
| 467 |
+
return filename
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
@dataclass(frozen=True, init=False)
|
| 471 |
+
class PackageWheel:
|
| 472 |
+
name: str | None = None
|
| 473 |
+
upload_time: datetime | None = None
|
| 474 |
+
url: str | None = None
|
| 475 |
+
path: str | None = None
|
| 476 |
+
size: int | None = None
|
| 477 |
+
hashes: Mapping[str, str] # type: ignore[misc]
|
| 478 |
+
|
| 479 |
+
def __init__(
|
| 480 |
+
self,
|
| 481 |
+
*,
|
| 482 |
+
name: str | None = None,
|
| 483 |
+
upload_time: datetime | None = None,
|
| 484 |
+
url: str | None = None,
|
| 485 |
+
path: str | None = None,
|
| 486 |
+
size: int | None = None,
|
| 487 |
+
hashes: Mapping[str, str],
|
| 488 |
+
) -> None:
|
| 489 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 490 |
+
object.__setattr__(self, "name", name)
|
| 491 |
+
object.__setattr__(self, "upload_time", upload_time)
|
| 492 |
+
object.__setattr__(self, "url", url)
|
| 493 |
+
object.__setattr__(self, "path", path)
|
| 494 |
+
object.__setattr__(self, "size", size)
|
| 495 |
+
object.__setattr__(self, "hashes", hashes)
|
| 496 |
+
|
| 497 |
+
@classmethod
|
| 498 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 499 |
+
package_wheel = cls(
|
| 500 |
+
name=_get(d, str, "name"),
|
| 501 |
+
upload_time=_get(d, datetime, "upload-time"),
|
| 502 |
+
url=_get(d, str, "url"),
|
| 503 |
+
path=_get(d, str, "path"),
|
| 504 |
+
size=_get(d, int, "size"),
|
| 505 |
+
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
| 506 |
+
)
|
| 507 |
+
_validate_path_url(package_wheel.path, package_wheel.url)
|
| 508 |
+
return package_wheel
|
| 509 |
+
|
| 510 |
+
@property
|
| 511 |
+
def filename(self) -> str:
|
| 512 |
+
"""Get the filename of the wheel."""
|
| 513 |
+
filename = self.name or _path_name(self.path) or _url_name(self.url)
|
| 514 |
+
if not filename:
|
| 515 |
+
raise PylockValidationError("Cannot determine wheel filename")
|
| 516 |
+
return filename
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
@dataclass(frozen=True, init=False)
|
| 520 |
+
class Package:
|
| 521 |
+
name: NormalizedName
|
| 522 |
+
version: Version | None = None
|
| 523 |
+
marker: Marker | None = None
|
| 524 |
+
requires_python: SpecifierSet | None = None
|
| 525 |
+
dependencies: Sequence[Mapping[str, Any]] | None = None
|
| 526 |
+
vcs: PackageVcs | None = None
|
| 527 |
+
directory: PackageDirectory | None = None
|
| 528 |
+
archive: PackageArchive | None = None
|
| 529 |
+
index: str | None = None
|
| 530 |
+
sdist: PackageSdist | None = None
|
| 531 |
+
wheels: Sequence[PackageWheel] | None = None
|
| 532 |
+
attestation_identities: Sequence[Mapping[str, Any]] | None = None
|
| 533 |
+
tool: Mapping[str, Any] | None = None
|
| 534 |
+
|
| 535 |
+
def __init__(
|
| 536 |
+
self,
|
| 537 |
+
*,
|
| 538 |
+
name: NormalizedName,
|
| 539 |
+
version: Version | None = None,
|
| 540 |
+
marker: Marker | None = None,
|
| 541 |
+
requires_python: SpecifierSet | None = None,
|
| 542 |
+
dependencies: Sequence[Mapping[str, Any]] | None = None,
|
| 543 |
+
vcs: PackageVcs | None = None,
|
| 544 |
+
directory: PackageDirectory | None = None,
|
| 545 |
+
archive: PackageArchive | None = None,
|
| 546 |
+
index: str | None = None,
|
| 547 |
+
sdist: PackageSdist | None = None,
|
| 548 |
+
wheels: Sequence[PackageWheel] | None = None,
|
| 549 |
+
attestation_identities: Sequence[Mapping[str, Any]] | None = None,
|
| 550 |
+
tool: Mapping[str, Any] | None = None,
|
| 551 |
+
) -> None:
|
| 552 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 553 |
+
object.__setattr__(self, "name", name)
|
| 554 |
+
object.__setattr__(self, "version", version)
|
| 555 |
+
object.__setattr__(self, "marker", marker)
|
| 556 |
+
object.__setattr__(self, "requires_python", requires_python)
|
| 557 |
+
object.__setattr__(self, "dependencies", dependencies)
|
| 558 |
+
object.__setattr__(self, "vcs", vcs)
|
| 559 |
+
object.__setattr__(self, "directory", directory)
|
| 560 |
+
object.__setattr__(self, "archive", archive)
|
| 561 |
+
object.__setattr__(self, "index", index)
|
| 562 |
+
object.__setattr__(self, "sdist", sdist)
|
| 563 |
+
object.__setattr__(self, "wheels", wheels)
|
| 564 |
+
object.__setattr__(self, "attestation_identities", attestation_identities)
|
| 565 |
+
object.__setattr__(self, "tool", tool)
|
| 566 |
+
|
| 567 |
+
@classmethod
|
| 568 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 569 |
+
package = cls(
|
| 570 |
+
name=_get_required_as(d, str, _validate_normalized_name, "name"),
|
| 571 |
+
version=_get_as(d, str, Version, "version"),
|
| 572 |
+
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
| 573 |
+
dependencies=_get_sequence(d, Mapping, "dependencies"), # type: ignore[type-abstract]
|
| 574 |
+
marker=_get_as(d, str, Marker, "marker"),
|
| 575 |
+
vcs=_get_object(d, PackageVcs, "vcs"),
|
| 576 |
+
directory=_get_object(d, PackageDirectory, "directory"),
|
| 577 |
+
archive=_get_object(d, PackageArchive, "archive"),
|
| 578 |
+
index=_get(d, str, "index"),
|
| 579 |
+
sdist=_get_object(d, PackageSdist, "sdist"),
|
| 580 |
+
wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
|
| 581 |
+
attestation_identities=_get_sequence(d, Mapping, "attestation-identities"), # type: ignore[type-abstract]
|
| 582 |
+
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
| 583 |
+
)
|
| 584 |
+
distributions = bool(package.sdist) + len(package.wheels or [])
|
| 585 |
+
direct_urls = (
|
| 586 |
+
bool(package.vcs) + bool(package.directory) + bool(package.archive)
|
| 587 |
+
)
|
| 588 |
+
if distributions > 0 and direct_urls > 0:
|
| 589 |
+
raise PylockValidationError(
|
| 590 |
+
"None of vcs, directory, archive must be set if sdist or wheels are set"
|
| 591 |
+
)
|
| 592 |
+
if distributions == 0 and direct_urls != 1:
|
| 593 |
+
raise PylockValidationError(
|
| 594 |
+
"Exactly one of vcs, directory, archive must be set "
|
| 595 |
+
"if sdist and wheels are not set"
|
| 596 |
+
)
|
| 597 |
+
for i, wheel in enumerate(package.wheels or []):
|
| 598 |
+
try:
|
| 599 |
+
(name, version, _, _) = parse_wheel_filename(wheel.filename)
|
| 600 |
+
except Exception as e:
|
| 601 |
+
raise PylockValidationError(
|
| 602 |
+
f"Invalid wheel filename {wheel.filename!r}",
|
| 603 |
+
context=f"wheels[{i}]",
|
| 604 |
+
) from e
|
| 605 |
+
if name != package.name:
|
| 606 |
+
raise PylockValidationError(
|
| 607 |
+
f"Name in {wheel.filename!r} is not consistent with "
|
| 608 |
+
f"package name {package.name!r}",
|
| 609 |
+
context=f"wheels[{i}]",
|
| 610 |
+
)
|
| 611 |
+
if package.version and version != package.version:
|
| 612 |
+
raise PylockValidationError(
|
| 613 |
+
f"Version in {wheel.filename!r} is not consistent with "
|
| 614 |
+
f"package version {str(package.version)!r}",
|
| 615 |
+
context=f"wheels[{i}]",
|
| 616 |
+
)
|
| 617 |
+
if package.sdist:
|
| 618 |
+
try:
|
| 619 |
+
name, version = parse_sdist_filename(package.sdist.filename)
|
| 620 |
+
except Exception as e:
|
| 621 |
+
raise PylockValidationError(
|
| 622 |
+
f"Invalid sdist filename {package.sdist.filename!r}",
|
| 623 |
+
context="sdist",
|
| 624 |
+
) from e
|
| 625 |
+
if name != package.name:
|
| 626 |
+
raise PylockValidationError(
|
| 627 |
+
f"Name in {package.sdist.filename!r} is not consistent with "
|
| 628 |
+
f"package name {package.name!r}",
|
| 629 |
+
context="sdist",
|
| 630 |
+
)
|
| 631 |
+
if package.version and version != package.version:
|
| 632 |
+
raise PylockValidationError(
|
| 633 |
+
f"Version in {package.sdist.filename!r} is not consistent with "
|
| 634 |
+
f"package version {str(package.version)!r}",
|
| 635 |
+
context="sdist",
|
| 636 |
+
)
|
| 637 |
+
try:
|
| 638 |
+
for i, attestation_identity in enumerate( # noqa: B007
|
| 639 |
+
package.attestation_identities or []
|
| 640 |
+
):
|
| 641 |
+
_get_required(attestation_identity, str, "kind")
|
| 642 |
+
except Exception as e:
|
| 643 |
+
raise PylockValidationError(
|
| 644 |
+
e, context=f"attestation-identities[{i}]"
|
| 645 |
+
) from e
|
| 646 |
+
return package
|
| 647 |
+
|
| 648 |
+
@property
|
| 649 |
+
def is_direct(self) -> bool:
|
| 650 |
+
return not (self.sdist or self.wheels)
|
| 651 |
+
|
| 652 |
+
|
| 653 |
+
@dataclass(frozen=True, init=False)
|
| 654 |
+
class Pylock:
|
| 655 |
+
"""A class representing a pylock file."""
|
| 656 |
+
|
| 657 |
+
lock_version: Version
|
| 658 |
+
environments: Sequence[Marker] | None = None
|
| 659 |
+
requires_python: SpecifierSet | None = None
|
| 660 |
+
extras: Sequence[NormalizedName] | None = None
|
| 661 |
+
dependency_groups: Sequence[str] | None = None
|
| 662 |
+
default_groups: Sequence[str] | None = None
|
| 663 |
+
created_by: str # type: ignore[misc]
|
| 664 |
+
packages: Sequence[Package] # type: ignore[misc]
|
| 665 |
+
tool: Mapping[str, Any] | None = None
|
| 666 |
+
|
| 667 |
+
def __init__(
|
| 668 |
+
self,
|
| 669 |
+
*,
|
| 670 |
+
lock_version: Version,
|
| 671 |
+
environments: Sequence[Marker] | None = None,
|
| 672 |
+
requires_python: SpecifierSet | None = None,
|
| 673 |
+
extras: Sequence[NormalizedName] | None = None,
|
| 674 |
+
dependency_groups: Sequence[str] | None = None,
|
| 675 |
+
default_groups: Sequence[str] | None = None,
|
| 676 |
+
created_by: str,
|
| 677 |
+
packages: Sequence[Package],
|
| 678 |
+
tool: Mapping[str, Any] | None = None,
|
| 679 |
+
) -> None:
|
| 680 |
+
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
| 681 |
+
object.__setattr__(self, "lock_version", lock_version)
|
| 682 |
+
object.__setattr__(self, "environments", environments)
|
| 683 |
+
object.__setattr__(self, "requires_python", requires_python)
|
| 684 |
+
object.__setattr__(self, "extras", extras)
|
| 685 |
+
object.__setattr__(self, "dependency_groups", dependency_groups)
|
| 686 |
+
object.__setattr__(self, "default_groups", default_groups)
|
| 687 |
+
object.__setattr__(self, "created_by", created_by)
|
| 688 |
+
object.__setattr__(self, "packages", packages)
|
| 689 |
+
object.__setattr__(self, "tool", tool)
|
| 690 |
+
|
| 691 |
+
@classmethod
|
| 692 |
+
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
| 693 |
+
pylock = cls(
|
| 694 |
+
lock_version=_get_required_as(d, str, Version, "lock-version"),
|
| 695 |
+
environments=_get_sequence_as(d, str, Marker, "environments"),
|
| 696 |
+
extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),
|
| 697 |
+
dependency_groups=_get_sequence(d, str, "dependency-groups"),
|
| 698 |
+
default_groups=_get_sequence(d, str, "default-groups"),
|
| 699 |
+
created_by=_get_required(d, str, "created-by"),
|
| 700 |
+
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
| 701 |
+
packages=_get_required_sequence_of_objects(d, Package, "packages"),
|
| 702 |
+
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
| 703 |
+
)
|
| 704 |
+
if not Version("1") <= pylock.lock_version < Version("2"):
|
| 705 |
+
raise PylockUnsupportedVersionError(
|
| 706 |
+
f"pylock version {pylock.lock_version} is not supported"
|
| 707 |
+
)
|
| 708 |
+
if pylock.lock_version > Version("1.0"):
|
| 709 |
+
_logger.warning(
|
| 710 |
+
"pylock minor version %s is not supported", pylock.lock_version
|
| 711 |
+
)
|
| 712 |
+
return pylock
|
| 713 |
+
|
| 714 |
+
@classmethod
|
| 715 |
+
def from_dict(cls, d: Mapping[str, Any], /) -> Self:
|
| 716 |
+
"""Create and validate a Pylock instance from a TOML dictionary.
|
| 717 |
+
|
| 718 |
+
Raises :class:`PylockValidationError` if the input data is not
|
| 719 |
+
spec-compliant.
|
| 720 |
+
"""
|
| 721 |
+
return cls._from_dict(d)
|
| 722 |
+
|
| 723 |
+
def to_dict(self) -> Mapping[str, Any]:
|
| 724 |
+
"""Convert the Pylock instance to a TOML dictionary."""
|
| 725 |
+
return dataclasses.asdict(self, dict_factory=_toml_dict_factory)
|
| 726 |
+
|
| 727 |
+
def validate(self) -> None:
|
| 728 |
+
"""Validate the Pylock instance against the specification.
|
| 729 |
+
|
| 730 |
+
Raises :class:`PylockValidationError` otherwise."""
|
| 731 |
+
self.from_dict(self.to_dict())
|
| 732 |
+
|
| 733 |
+
def select(
|
| 734 |
+
self,
|
| 735 |
+
*,
|
| 736 |
+
environment: Environment | None = None,
|
| 737 |
+
tags: Sequence[Tag] | None = None,
|
| 738 |
+
extras: Collection[str] | None = None,
|
| 739 |
+
dependency_groups: Collection[str] | None = None,
|
| 740 |
+
) -> Iterator[
|
| 741 |
+
tuple[
|
| 742 |
+
Package,
|
| 743 |
+
PackageVcs
|
| 744 |
+
| PackageDirectory
|
| 745 |
+
| PackageArchive
|
| 746 |
+
| PackageWheel
|
| 747 |
+
| PackageSdist,
|
| 748 |
+
]
|
| 749 |
+
]:
|
| 750 |
+
"""Select what to install from the lock file.
|
| 751 |
+
|
| 752 |
+
The *environment* and *tags* parameters represent the environment being
|
| 753 |
+
selected for. If unspecified, ``packaging.markers.default_environment()`` and
|
| 754 |
+
``packaging.tags.sys_tags()`` are used.
|
| 755 |
+
|
| 756 |
+
The *extras* parameter represents the extras to install.
|
| 757 |
+
|
| 758 |
+
The *dependency_groups* parameter represents the groups to install. If
|
| 759 |
+
unspecified, the default groups are used.
|
| 760 |
+
|
| 761 |
+
This method must be used on valid Pylock instances (i.e. one obtained
|
| 762 |
+
from :meth:`Pylock.from_dict` or if constructed manually, after calling
|
| 763 |
+
:meth:`Pylock.validate`).
|
| 764 |
+
"""
|
| 765 |
+
compatible_tags_selector = create_compatible_tags_selector(tags or sys_tags())
|
| 766 |
+
|
| 767 |
+
# #. Gather the extras and dependency groups to install and set ``extras`` and
|
| 768 |
+
# ``dependency_groups`` for marker evaluation, respectively.
|
| 769 |
+
#
|
| 770 |
+
# #. ``extras`` SHOULD be set to the empty set by default.
|
| 771 |
+
# #. ``dependency_groups`` SHOULD be the set created from
|
| 772 |
+
# :ref:`pylock-default-groups` by default.
|
| 773 |
+
env = cast(
|
| 774 |
+
"dict[str, str | frozenset[str]]",
|
| 775 |
+
dict(
|
| 776 |
+
environment or {}, # Marker.evaluate will fill-up
|
| 777 |
+
extras=frozenset(extras or []),
|
| 778 |
+
dependency_groups=frozenset(
|
| 779 |
+
(self.default_groups or [])
|
| 780 |
+
if dependency_groups is None # to allow selecting no group
|
| 781 |
+
else dependency_groups
|
| 782 |
+
),
|
| 783 |
+
),
|
| 784 |
+
)
|
| 785 |
+
env_python_full_version = (
|
| 786 |
+
environment["python_full_version"]
|
| 787 |
+
if environment
|
| 788 |
+
else default_environment()["python_full_version"]
|
| 789 |
+
)
|
| 790 |
+
|
| 791 |
+
# #. Check if the metadata version specified by :ref:`pylock-lock-version` is
|
| 792 |
+
# supported; an error or warning MUST be raised as appropriate.
|
| 793 |
+
# Covered by lock.validate() which is a precondition for this method.
|
| 794 |
+
|
| 795 |
+
# #. If :ref:`pylock-requires-python` is specified, check that the environment
|
| 796 |
+
# being installed for meets the requirement; an error MUST be raised if it is
|
| 797 |
+
# not met.
|
| 798 |
+
if self.requires_python and not self.requires_python.contains(
|
| 799 |
+
env_python_full_version,
|
| 800 |
+
):
|
| 801 |
+
raise PylockSelectError(
|
| 802 |
+
f"python_full_version {env_python_full_version!r} "
|
| 803 |
+
f"in provided environment does not satisfy the Python version "
|
| 804 |
+
f"requirement {str(self.requires_python)!r}"
|
| 805 |
+
)
|
| 806 |
+
|
| 807 |
+
# #. If :ref:`pylock-environments` is specified, check that at least one of the
|
| 808 |
+
# environment marker expressions is satisfied; an error MUST be raised if no
|
| 809 |
+
# expression is satisfied.
|
| 810 |
+
if self.environments:
|
| 811 |
+
for env_marker in self.environments:
|
| 812 |
+
if env_marker.evaluate(
|
| 813 |
+
cast("dict[str, str]", environment or {}), context="requirement"
|
| 814 |
+
):
|
| 815 |
+
break
|
| 816 |
+
else:
|
| 817 |
+
raise PylockSelectError(
|
| 818 |
+
"Provided environment does not satisfy any of the "
|
| 819 |
+
"environments specified in the lock file"
|
| 820 |
+
)
|
| 821 |
+
|
| 822 |
+
# #. For each package listed in :ref:`pylock-packages`:
|
| 823 |
+
selected_packages_by_name: dict[str, tuple[int, Package]] = {}
|
| 824 |
+
for package_index, package in enumerate(self.packages):
|
| 825 |
+
# #. If :ref:`pylock-packages-marker` is specified, check if it is
|
| 826 |
+
# satisfied;if it isn't, skip to the next package.
|
| 827 |
+
if package.marker and not package.marker.evaluate(env, context="lock_file"):
|
| 828 |
+
continue
|
| 829 |
+
|
| 830 |
+
# #. If :ref:`pylock-packages-requires-python` is specified, check if it is
|
| 831 |
+
# satisfied; an error MUST be raised if it isn't.
|
| 832 |
+
if package.requires_python and not package.requires_python.contains(
|
| 833 |
+
env_python_full_version,
|
| 834 |
+
):
|
| 835 |
+
raise PylockSelectError(
|
| 836 |
+
f"python_full_version {env_python_full_version!r} "
|
| 837 |
+
f"in provided environment does not satisfy the Python version "
|
| 838 |
+
f"requirement {str(package.requires_python)!r} for package "
|
| 839 |
+
f"{package.name!r} at packages[{package_index}]"
|
| 840 |
+
)
|
| 841 |
+
|
| 842 |
+
# #. Check that no other conflicting instance of the package has been slated
|
| 843 |
+
# to be installed; an error about the ambiguity MUST be raised otherwise.
|
| 844 |
+
if package.name in selected_packages_by_name:
|
| 845 |
+
raise PylockSelectError(
|
| 846 |
+
f"Multiple packages with the name {package.name!r} are "
|
| 847 |
+
f"selected at packages[{package_index}] and "
|
| 848 |
+
f"packages[{selected_packages_by_name[package.name][0]}]"
|
| 849 |
+
)
|
| 850 |
+
|
| 851 |
+
# #. Check that the source of the package is specified appropriately (i.e.
|
| 852 |
+
# there are no conflicting sources in the package entry);
|
| 853 |
+
# an error MUST be raised if any issues are found.
|
| 854 |
+
# Covered by lock.validate() which is a precondition for this method.
|
| 855 |
+
|
| 856 |
+
# #. Add the package to the set of packages to install.
|
| 857 |
+
selected_packages_by_name[package.name] = (package_index, package)
|
| 858 |
+
|
| 859 |
+
# #. For each package to be installed:
|
| 860 |
+
for package_index, package in selected_packages_by_name.values():
|
| 861 |
+
# - If :ref:`pylock-packages-vcs` is set:
|
| 862 |
+
if package.vcs is not None:
|
| 863 |
+
yield package, package.vcs
|
| 864 |
+
|
| 865 |
+
# - Else if :ref:`pylock-packages-directory` is set:
|
| 866 |
+
elif package.directory is not None:
|
| 867 |
+
yield package, package.directory
|
| 868 |
+
|
| 869 |
+
# - Else if :ref:`pylock-packages-archive` is set:
|
| 870 |
+
elif package.archive is not None:
|
| 871 |
+
yield package, package.archive
|
| 872 |
+
|
| 873 |
+
# - Else if there are entries for :ref:`pylock-packages-wheels`:
|
| 874 |
+
elif package.wheels:
|
| 875 |
+
# #. Look for the appropriate wheel file based on
|
| 876 |
+
# :ref:`pylock-packages-wheels-name`; if one is not found then move
|
| 877 |
+
# on to :ref:`pylock-packages-sdist` or an error MUST be raised about
|
| 878 |
+
# a lack of source for the project.
|
| 879 |
+
best_wheel = next(
|
| 880 |
+
compatible_tags_selector(
|
| 881 |
+
(wheel, parse_wheel_filename(wheel.filename)[-1])
|
| 882 |
+
for wheel in package.wheels
|
| 883 |
+
),
|
| 884 |
+
None,
|
| 885 |
+
)
|
| 886 |
+
if best_wheel:
|
| 887 |
+
yield package, best_wheel
|
| 888 |
+
elif package.sdist is not None:
|
| 889 |
+
yield package, package.sdist
|
| 890 |
+
else:
|
| 891 |
+
raise PylockSelectError(
|
| 892 |
+
f"No wheel found matching the provided tags "
|
| 893 |
+
f"for package {package.name!r} "
|
| 894 |
+
f"at packages[{package_index}], "
|
| 895 |
+
f"and no sdist available as a fallback"
|
| 896 |
+
)
|
| 897 |
+
|
| 898 |
+
# - Else if no :ref:`pylock-packages-wheels` file is found or
|
| 899 |
+
# :ref:`pylock-packages-sdist` is solely set:
|
| 900 |
+
elif package.sdist is not None:
|
| 901 |
+
yield package, package.sdist
|
| 902 |
+
|
| 903 |
+
else:
|
| 904 |
+
# Covered by lock.validate() which is a precondition for this method.
|
| 905 |
+
raise NotImplementedError # pragma: no cover
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/requirements.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
from typing import Iterator
|
| 7 |
+
|
| 8 |
+
from ._parser import parse_requirement as _parse_requirement
|
| 9 |
+
from ._tokenizer import ParserSyntaxError
|
| 10 |
+
from .markers import Marker, _normalize_extra_values
|
| 11 |
+
from .specifiers import SpecifierSet
|
| 12 |
+
from .utils import canonicalize_name
|
| 13 |
+
|
| 14 |
+
__all__ = [
|
| 15 |
+
"InvalidRequirement",
|
| 16 |
+
"Requirement",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def __dir__() -> list[str]:
|
| 21 |
+
return __all__
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class InvalidRequirement(ValueError):
|
| 25 |
+
"""
|
| 26 |
+
An invalid requirement was found, users should refer to PEP 508.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class Requirement:
|
| 31 |
+
"""Parse a requirement.
|
| 32 |
+
|
| 33 |
+
Parse a given requirement string into its parts, such as name, specifier,
|
| 34 |
+
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
|
| 35 |
+
string.
|
| 36 |
+
|
| 37 |
+
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
| 38 |
+
format so the same pickle can be loaded in future packaging releases.
|
| 39 |
+
|
| 40 |
+
.. versionchanged:: 26.2
|
| 41 |
+
|
| 42 |
+
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
| 43 |
+
be unpickled with future releases. Backward compatibility with pickles
|
| 44 |
+
from packaging < 26.2 is supported but may be removed in a future
|
| 45 |
+
release.
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
# TODO: Can we test whether something is contained within a requirement?
|
| 49 |
+
# If so how do we do that? Do we need to test against the _name_ of
|
| 50 |
+
# the thing as well as the version? What about the markers?
|
| 51 |
+
# TODO: Can we normalize the name and extra name?
|
| 52 |
+
|
| 53 |
+
def __init__(self, requirement_string: str) -> None:
|
| 54 |
+
try:
|
| 55 |
+
parsed = _parse_requirement(requirement_string)
|
| 56 |
+
except ParserSyntaxError as e:
|
| 57 |
+
raise InvalidRequirement(str(e)) from e
|
| 58 |
+
|
| 59 |
+
self.name: str = parsed.name
|
| 60 |
+
self.url: str | None = parsed.url or None
|
| 61 |
+
self.extras: set[str] = set(parsed.extras or [])
|
| 62 |
+
self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
|
| 63 |
+
self.marker: Marker | None = None
|
| 64 |
+
if parsed.marker is not None:
|
| 65 |
+
self.marker = Marker.__new__(Marker)
|
| 66 |
+
self.marker._markers = _normalize_extra_values(parsed.marker)
|
| 67 |
+
|
| 68 |
+
def _iter_parts(self, name: str) -> Iterator[str]:
|
| 69 |
+
yield name
|
| 70 |
+
|
| 71 |
+
if self.extras:
|
| 72 |
+
formatted_extras = ",".join(sorted(self.extras))
|
| 73 |
+
yield f"[{formatted_extras}]"
|
| 74 |
+
|
| 75 |
+
if self.specifier:
|
| 76 |
+
yield str(self.specifier)
|
| 77 |
+
|
| 78 |
+
if self.url:
|
| 79 |
+
yield f" @ {self.url}"
|
| 80 |
+
if self.marker:
|
| 81 |
+
yield " "
|
| 82 |
+
|
| 83 |
+
if self.marker:
|
| 84 |
+
yield f"; {self.marker}"
|
| 85 |
+
|
| 86 |
+
def __getstate__(self) -> str:
|
| 87 |
+
# Return the requirement string for compactness and stability.
|
| 88 |
+
# Re-parsed on load to reconstruct all fields.
|
| 89 |
+
return str(self)
|
| 90 |
+
|
| 91 |
+
def __setstate__(self, state: object) -> None:
|
| 92 |
+
if isinstance(state, str):
|
| 93 |
+
# New format (26.2+): just the requirement string.
|
| 94 |
+
try:
|
| 95 |
+
tmp = Requirement(state)
|
| 96 |
+
except InvalidRequirement as exc:
|
| 97 |
+
raise TypeError(f"Cannot restore Requirement from {state!r}") from exc
|
| 98 |
+
self.name = tmp.name
|
| 99 |
+
self.url = tmp.url
|
| 100 |
+
self.extras = tmp.extras
|
| 101 |
+
self.specifier = tmp.specifier
|
| 102 |
+
self.marker = tmp.marker
|
| 103 |
+
return
|
| 104 |
+
if isinstance(state, dict):
|
| 105 |
+
# Old format (packaging <= 26.1, no __slots__): plain __dict__.
|
| 106 |
+
self.__dict__.update(state)
|
| 107 |
+
return
|
| 108 |
+
raise TypeError(f"Cannot restore Requirement from {state!r}")
|
| 109 |
+
|
| 110 |
+
def __str__(self) -> str:
|
| 111 |
+
return "".join(self._iter_parts(self.name))
|
| 112 |
+
|
| 113 |
+
def __repr__(self) -> str:
|
| 114 |
+
return f"<{self.__class__.__name__}({str(self)!r})>"
|
| 115 |
+
|
| 116 |
+
def __hash__(self) -> int:
|
| 117 |
+
return hash(tuple(self._iter_parts(canonicalize_name(self.name))))
|
| 118 |
+
|
| 119 |
+
def __eq__(self, other: object) -> bool:
|
| 120 |
+
if not isinstance(other, Requirement):
|
| 121 |
+
return NotImplemented
|
| 122 |
+
|
| 123 |
+
return (
|
| 124 |
+
canonicalize_name(self.name) == canonicalize_name(other.name)
|
| 125 |
+
and self.extras == other.extras
|
| 126 |
+
and self.specifier == other.specifier
|
| 127 |
+
and self.url == other.url
|
| 128 |
+
and self.marker == other.marker
|
| 129 |
+
)
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/specifiers.py
ADDED
|
@@ -0,0 +1,1943 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
"""
|
| 5 |
+
.. testsetup::
|
| 6 |
+
|
| 7 |
+
from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
|
| 8 |
+
from packaging.version import Version
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import abc
|
| 14 |
+
import enum
|
| 15 |
+
import functools
|
| 16 |
+
import itertools
|
| 17 |
+
import re
|
| 18 |
+
import sys
|
| 19 |
+
import typing
|
| 20 |
+
from typing import (
|
| 21 |
+
TYPE_CHECKING,
|
| 22 |
+
Any,
|
| 23 |
+
Callable,
|
| 24 |
+
Final,
|
| 25 |
+
Iterable,
|
| 26 |
+
Iterator,
|
| 27 |
+
Sequence,
|
| 28 |
+
TypeVar,
|
| 29 |
+
Union,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
from .utils import canonicalize_version
|
| 33 |
+
from .version import InvalidVersion, Version
|
| 34 |
+
|
| 35 |
+
if sys.version_info >= (3, 10):
|
| 36 |
+
from typing import TypeGuard # pragma: no cover
|
| 37 |
+
elif TYPE_CHECKING:
|
| 38 |
+
from typing_extensions import TypeGuard
|
| 39 |
+
|
| 40 |
+
__all__ = [
|
| 41 |
+
"BaseSpecifier",
|
| 42 |
+
"InvalidSpecifier",
|
| 43 |
+
"Specifier",
|
| 44 |
+
"SpecifierSet",
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def __dir__() -> list[str]:
|
| 49 |
+
return __all__
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]:
|
| 53 |
+
return (
|
| 54 |
+
isinstance(spec, tuple)
|
| 55 |
+
and len(spec) == 2
|
| 56 |
+
and isinstance(spec[0], str)
|
| 57 |
+
and isinstance(spec[1], str)
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _validate_pre(pre: object, /) -> TypeGuard[bool | None]:
|
| 62 |
+
return pre is None or isinstance(pre, bool)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
T = TypeVar("T")
|
| 66 |
+
UnparsedVersion = Union[Version, str]
|
| 67 |
+
UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
|
| 68 |
+
CallableOperator = Callable[[Version, str], bool]
|
| 69 |
+
|
| 70 |
+
# The smallest possible PEP 440 version. No valid version is less than this.
|
| 71 |
+
_MIN_VERSION: Final[Version] = Version("0.dev0")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def _trim_release(release: tuple[int, ...]) -> tuple[int, ...]:
|
| 75 |
+
"""Strip trailing zeros from a release tuple for normalized comparison."""
|
| 76 |
+
end = len(release)
|
| 77 |
+
while end > 1 and release[end - 1] == 0:
|
| 78 |
+
end -= 1
|
| 79 |
+
return release if end == len(release) else release[:end]
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class _BoundaryKind(enum.Enum):
|
| 83 |
+
"""Where a boundary marker sits in the version ordering."""
|
| 84 |
+
|
| 85 |
+
AFTER_LOCALS = enum.auto() # after V+local, before V.post0
|
| 86 |
+
AFTER_POSTS = enum.auto() # after V.postN, before next release
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
@functools.total_ordering
|
| 90 |
+
class _BoundaryVersion:
|
| 91 |
+
"""A point on the version line between two real PEP 440 versions.
|
| 92 |
+
|
| 93 |
+
Some specifier semantics imply boundaries between real versions:
|
| 94 |
+
``<=1.0`` includes ``1.0+local`` and ``>1.0`` excludes
|
| 95 |
+
``1.0.post0``. No real :class:`Version` falls on those boundaries,
|
| 96 |
+
so this class creates values that sort between the real versions
|
| 97 |
+
on either side.
|
| 98 |
+
|
| 99 |
+
Two kinds exist, shown relative to a base version V::
|
| 100 |
+
|
| 101 |
+
V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V)
|
| 102 |
+
|
| 103 |
+
``AFTER_LOCALS`` sits after V and every V+local, but before
|
| 104 |
+
V.post0. Upper bound of ``<=V``, ``==V``, ``!=V``.
|
| 105 |
+
|
| 106 |
+
``AFTER_POSTS`` sits after every V.postN, but before the next
|
| 107 |
+
release segment. Lower bound of ``>V`` (final or pre-release V)
|
| 108 |
+
to exclude post-releases per PEP 440.
|
| 109 |
+
"""
|
| 110 |
+
|
| 111 |
+
__slots__ = ("_kind", "_trimmed_release", "version")
|
| 112 |
+
|
| 113 |
+
def __init__(self, version: Version, kind: _BoundaryKind) -> None:
|
| 114 |
+
self.version = version
|
| 115 |
+
self._kind = kind
|
| 116 |
+
self._trimmed_release = _trim_release(version.release)
|
| 117 |
+
|
| 118 |
+
def _is_family(self, other: Version) -> bool:
|
| 119 |
+
"""Is ``other`` a version that this boundary sorts above?"""
|
| 120 |
+
v = self.version
|
| 121 |
+
if not (
|
| 122 |
+
other.epoch == v.epoch
|
| 123 |
+
and _trim_release(other.release) == self._trimmed_release
|
| 124 |
+
and other.pre == v.pre
|
| 125 |
+
):
|
| 126 |
+
return False
|
| 127 |
+
if self._kind == _BoundaryKind.AFTER_LOCALS:
|
| 128 |
+
# Local family: exact same public version (any local label).
|
| 129 |
+
return other.post == v.post and other.dev == v.dev
|
| 130 |
+
# Post family: same base + any post-release (or identical).
|
| 131 |
+
return other.dev == v.dev or other.post is not None
|
| 132 |
+
|
| 133 |
+
def __eq__(self, other: object) -> bool:
|
| 134 |
+
if isinstance(other, _BoundaryVersion):
|
| 135 |
+
return self.version == other.version and self._kind == other._kind
|
| 136 |
+
return NotImplemented
|
| 137 |
+
|
| 138 |
+
def __lt__(self, other: _BoundaryVersion | Version) -> bool:
|
| 139 |
+
if isinstance(other, _BoundaryVersion):
|
| 140 |
+
if self.version != other.version:
|
| 141 |
+
return self.version < other.version
|
| 142 |
+
return self._kind.value < other._kind.value
|
| 143 |
+
return not self._is_family(other) and self.version < other
|
| 144 |
+
|
| 145 |
+
def __hash__(self) -> int:
|
| 146 |
+
return hash((self.version, self._kind))
|
| 147 |
+
|
| 148 |
+
def __repr__(self) -> str:
|
| 149 |
+
return f"{self.__class__.__name__}({self.version!r}, {self._kind.name})"
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
@functools.total_ordering
|
| 153 |
+
class _LowerBound:
|
| 154 |
+
"""Lower bound of a version range.
|
| 155 |
+
|
| 156 |
+
A version *v* of ``None`` means unbounded below (-inf).
|
| 157 |
+
At equal versions, ``[v`` sorts before ``(v`` because an inclusive
|
| 158 |
+
bound starts earlier.
|
| 159 |
+
"""
|
| 160 |
+
|
| 161 |
+
__slots__ = ("inclusive", "version")
|
| 162 |
+
|
| 163 |
+
def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
|
| 164 |
+
self.version = version
|
| 165 |
+
self.inclusive = inclusive
|
| 166 |
+
|
| 167 |
+
def __eq__(self, other: object) -> bool:
|
| 168 |
+
if not isinstance(other, _LowerBound):
|
| 169 |
+
return NotImplemented # pragma: no cover
|
| 170 |
+
return self.version == other.version and self.inclusive == other.inclusive
|
| 171 |
+
|
| 172 |
+
def __lt__(self, other: _LowerBound) -> bool:
|
| 173 |
+
if not isinstance(other, _LowerBound): # pragma: no cover
|
| 174 |
+
return NotImplemented
|
| 175 |
+
# -inf < anything (except -inf).
|
| 176 |
+
if self.version is None:
|
| 177 |
+
return other.version is not None
|
| 178 |
+
if other.version is None:
|
| 179 |
+
return False
|
| 180 |
+
if self.version != other.version:
|
| 181 |
+
return self.version < other.version
|
| 182 |
+
# [v < (v: inclusive starts earlier.
|
| 183 |
+
return self.inclusive and not other.inclusive
|
| 184 |
+
|
| 185 |
+
def __hash__(self) -> int:
|
| 186 |
+
return hash((self.version, self.inclusive))
|
| 187 |
+
|
| 188 |
+
def __repr__(self) -> str:
|
| 189 |
+
bracket = "[" if self.inclusive else "("
|
| 190 |
+
return f"<{self.__class__.__name__} {bracket}{self.version!r}>"
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
@functools.total_ordering
|
| 194 |
+
class _UpperBound:
|
| 195 |
+
"""Upper bound of a version range.
|
| 196 |
+
|
| 197 |
+
A version *v* of ``None`` means unbounded above (+inf).
|
| 198 |
+
At equal versions, ``v)`` sorts before ``v]`` because an exclusive
|
| 199 |
+
bound ends earlier.
|
| 200 |
+
"""
|
| 201 |
+
|
| 202 |
+
__slots__ = ("inclusive", "version")
|
| 203 |
+
|
| 204 |
+
def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
|
| 205 |
+
self.version = version
|
| 206 |
+
self.inclusive = inclusive
|
| 207 |
+
|
| 208 |
+
def __eq__(self, other: object) -> bool:
|
| 209 |
+
if not isinstance(other, _UpperBound):
|
| 210 |
+
return NotImplemented # pragma: no cover
|
| 211 |
+
return self.version == other.version and self.inclusive == other.inclusive
|
| 212 |
+
|
| 213 |
+
def __lt__(self, other: _UpperBound) -> bool:
|
| 214 |
+
if not isinstance(other, _UpperBound): # pragma: no cover
|
| 215 |
+
return NotImplemented
|
| 216 |
+
# Nothing < +inf (except +inf itself).
|
| 217 |
+
if self.version is None:
|
| 218 |
+
return False
|
| 219 |
+
if other.version is None:
|
| 220 |
+
return True
|
| 221 |
+
if self.version != other.version:
|
| 222 |
+
return self.version < other.version
|
| 223 |
+
# v) < v]: exclusive ends earlier.
|
| 224 |
+
return not self.inclusive and other.inclusive
|
| 225 |
+
|
| 226 |
+
def __hash__(self) -> int:
|
| 227 |
+
return hash((self.version, self.inclusive))
|
| 228 |
+
|
| 229 |
+
def __repr__(self) -> str:
|
| 230 |
+
bracket = "]" if self.inclusive else ")"
|
| 231 |
+
return f"<{self.__class__.__name__} {self.version!r}{bracket}>"
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
if typing.TYPE_CHECKING:
|
| 235 |
+
_VersionOrBoundary = Union[Version, _BoundaryVersion, None]
|
| 236 |
+
|
| 237 |
+
#: A single contiguous version range, represented as a
|
| 238 |
+
#: (lower bound, upper bound) pair.
|
| 239 |
+
_VersionRange = tuple[_LowerBound, _UpperBound]
|
| 240 |
+
|
| 241 |
+
_NEG_INF = _LowerBound(None, False)
|
| 242 |
+
_POS_INF = _UpperBound(None, False)
|
| 243 |
+
_FULL_RANGE: tuple[_VersionRange] = ((_NEG_INF, _POS_INF),)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def _range_is_empty(lower: _LowerBound, upper: _UpperBound) -> bool:
|
| 247 |
+
"""True when the range defined by *lower* and *upper* contains no versions."""
|
| 248 |
+
if lower.version is None or upper.version is None:
|
| 249 |
+
return False
|
| 250 |
+
if lower.version == upper.version:
|
| 251 |
+
return not (lower.inclusive and upper.inclusive)
|
| 252 |
+
return lower.version > upper.version
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def _intersect_ranges(
|
| 256 |
+
left: Sequence[_VersionRange],
|
| 257 |
+
right: Sequence[_VersionRange],
|
| 258 |
+
) -> list[_VersionRange]:
|
| 259 |
+
"""Intersect two sorted, non-overlapping range lists (two-pointer merge)."""
|
| 260 |
+
result: list[_VersionRange] = []
|
| 261 |
+
left_index = right_index = 0
|
| 262 |
+
while left_index < len(left) and right_index < len(right):
|
| 263 |
+
left_lower, left_upper = left[left_index]
|
| 264 |
+
right_lower, right_upper = right[right_index]
|
| 265 |
+
|
| 266 |
+
lower = max(left_lower, right_lower)
|
| 267 |
+
upper = min(left_upper, right_upper)
|
| 268 |
+
|
| 269 |
+
if not _range_is_empty(lower, upper):
|
| 270 |
+
result.append((lower, upper))
|
| 271 |
+
|
| 272 |
+
# Advance whichever side has the smaller upper bound.
|
| 273 |
+
if left_upper < right_upper:
|
| 274 |
+
left_index += 1
|
| 275 |
+
else:
|
| 276 |
+
right_index += 1
|
| 277 |
+
|
| 278 |
+
return result
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _next_prefix_dev0(version: Version) -> Version:
|
| 282 |
+
"""Smallest version in the next prefix: 1.2 -> 1.3.dev0."""
|
| 283 |
+
release = (*version.release[:-1], version.release[-1] + 1)
|
| 284 |
+
return Version.from_parts(epoch=version.epoch, release=release, dev=0)
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def _base_dev0(version: Version) -> Version:
|
| 288 |
+
"""The .dev0 of a version's base release: 1.2 -> 1.2.dev0."""
|
| 289 |
+
return Version.from_parts(epoch=version.epoch, release=version.release, dev=0)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def _coerce_version(version: UnparsedVersion) -> Version | None:
|
| 293 |
+
if not isinstance(version, Version):
|
| 294 |
+
try:
|
| 295 |
+
version = Version(version)
|
| 296 |
+
except InvalidVersion:
|
| 297 |
+
return None
|
| 298 |
+
return version
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def _public_version(version: Version) -> Version:
|
| 302 |
+
if version.local is None:
|
| 303 |
+
return version
|
| 304 |
+
return version.__replace__(local=None)
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def _post_base(version: Version) -> Version:
|
| 308 |
+
"""The version that *version* is a post-release of.
|
| 309 |
+
|
| 310 |
+
1.0.post1 -> 1.0, 1.0a1.post0 -> 1.0a1, 1.0.post0.dev1 -> 1.0.
|
| 311 |
+
"""
|
| 312 |
+
return version.__replace__(post=None, dev=None, local=None)
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def _earliest_prerelease(version: Version) -> Version:
|
| 316 |
+
"""Earliest pre-release of *version*.
|
| 317 |
+
|
| 318 |
+
1.2 -> 1.2.dev0, 1.2.post1 -> 1.2.post1.dev0.
|
| 319 |
+
"""
|
| 320 |
+
return version.__replace__(dev=0, local=None)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def _nearest_non_prerelease(
|
| 324 |
+
v: _VersionOrBoundary,
|
| 325 |
+
) -> Version | None:
|
| 326 |
+
"""Smallest non-pre-release version at or above *v*, or None."""
|
| 327 |
+
if v is None:
|
| 328 |
+
return None
|
| 329 |
+
if isinstance(v, _BoundaryVersion):
|
| 330 |
+
inner = v.version
|
| 331 |
+
if inner.is_prerelease:
|
| 332 |
+
# AFTER_LOCALS(1.0a1) -> nearest non-pre is 1.0
|
| 333 |
+
return inner.__replace__(pre=None, dev=None, local=None)
|
| 334 |
+
# AFTER_LOCALS(1.0) -> nearest non-pre is 1.0.post0
|
| 335 |
+
# AFTER_LOCALS(1.0.post0) -> nearest non-pre is 1.0.post1
|
| 336 |
+
k = (inner.post + 1) if inner.post is not None else 0
|
| 337 |
+
return inner.__replace__(post=k, local=None)
|
| 338 |
+
if not v.is_prerelease:
|
| 339 |
+
return v
|
| 340 |
+
# Strip pre/dev to get the final or post-release form.
|
| 341 |
+
return v.__replace__(pre=None, dev=None, local=None)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
class InvalidSpecifier(ValueError):
|
| 345 |
+
"""
|
| 346 |
+
Raised when attempting to create a :class:`Specifier` with a specifier
|
| 347 |
+
string that is invalid.
|
| 348 |
+
|
| 349 |
+
>>> Specifier("lolwat")
|
| 350 |
+
Traceback (most recent call last):
|
| 351 |
+
...
|
| 352 |
+
packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
|
| 353 |
+
"""
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
class BaseSpecifier(metaclass=abc.ABCMeta):
|
| 357 |
+
__slots__ = ()
|
| 358 |
+
__match_args__ = ("_str",)
|
| 359 |
+
|
| 360 |
+
@property
|
| 361 |
+
def _str(self) -> str:
|
| 362 |
+
"""Internal property for match_args"""
|
| 363 |
+
return str(self)
|
| 364 |
+
|
| 365 |
+
@abc.abstractmethod
|
| 366 |
+
def __str__(self) -> str:
|
| 367 |
+
"""
|
| 368 |
+
Returns the str representation of this Specifier-like object. This
|
| 369 |
+
should be representative of the Specifier itself.
|
| 370 |
+
"""
|
| 371 |
+
|
| 372 |
+
@abc.abstractmethod
|
| 373 |
+
def __hash__(self) -> int:
|
| 374 |
+
"""
|
| 375 |
+
Returns a hash value for this Specifier-like object.
|
| 376 |
+
"""
|
| 377 |
+
|
| 378 |
+
@abc.abstractmethod
|
| 379 |
+
def __eq__(self, other: object) -> bool:
|
| 380 |
+
"""
|
| 381 |
+
Returns a boolean representing whether or not the two Specifier-like
|
| 382 |
+
objects are equal.
|
| 383 |
+
|
| 384 |
+
:param other: The other object to check against.
|
| 385 |
+
"""
|
| 386 |
+
|
| 387 |
+
@property
|
| 388 |
+
@abc.abstractmethod
|
| 389 |
+
def prereleases(self) -> bool | None:
|
| 390 |
+
"""Whether or not pre-releases as a whole are allowed.
|
| 391 |
+
|
| 392 |
+
This can be set to either ``True`` or ``False`` to explicitly enable or disable
|
| 393 |
+
prereleases or it can be set to ``None`` (the default) to use default semantics.
|
| 394 |
+
"""
|
| 395 |
+
|
| 396 |
+
@prereleases.setter # noqa: B027
|
| 397 |
+
def prereleases(self, value: bool) -> None:
|
| 398 |
+
"""Setter for :attr:`prereleases`.
|
| 399 |
+
|
| 400 |
+
:param value: The value to set.
|
| 401 |
+
"""
|
| 402 |
+
|
| 403 |
+
@abc.abstractmethod
|
| 404 |
+
def contains(self, item: str, prereleases: bool | None = None) -> bool:
|
| 405 |
+
"""
|
| 406 |
+
Determines if the given item is contained within this specifier.
|
| 407 |
+
"""
|
| 408 |
+
|
| 409 |
+
@typing.overload
|
| 410 |
+
def filter(
|
| 411 |
+
self,
|
| 412 |
+
iterable: Iterable[UnparsedVersionVar],
|
| 413 |
+
prereleases: bool | None = None,
|
| 414 |
+
key: None = ...,
|
| 415 |
+
) -> Iterator[UnparsedVersionVar]: ...
|
| 416 |
+
|
| 417 |
+
@typing.overload
|
| 418 |
+
def filter(
|
| 419 |
+
self,
|
| 420 |
+
iterable: Iterable[T],
|
| 421 |
+
prereleases: bool | None = None,
|
| 422 |
+
key: Callable[[T], UnparsedVersion] = ...,
|
| 423 |
+
) -> Iterator[T]: ...
|
| 424 |
+
|
| 425 |
+
@abc.abstractmethod
|
| 426 |
+
def filter(
|
| 427 |
+
self,
|
| 428 |
+
iterable: Iterable[Any],
|
| 429 |
+
prereleases: bool | None = None,
|
| 430 |
+
key: Callable[[Any], UnparsedVersion] | None = None,
|
| 431 |
+
) -> Iterator[Any]:
|
| 432 |
+
"""
|
| 433 |
+
Takes an iterable of items and filters them so that only items which
|
| 434 |
+
are contained within this specifier are allowed in it.
|
| 435 |
+
"""
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
class Specifier(BaseSpecifier):
|
| 439 |
+
"""This class abstracts handling of version specifiers.
|
| 440 |
+
|
| 441 |
+
.. tip::
|
| 442 |
+
|
| 443 |
+
It is generally not required to instantiate this manually. You should instead
|
| 444 |
+
prefer to work with :class:`SpecifierSet` instead, which can parse
|
| 445 |
+
comma-separated version specifiers (which is what package metadata contains).
|
| 446 |
+
|
| 447 |
+
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
| 448 |
+
format so the same pickle can be loaded in future packaging releases.
|
| 449 |
+
|
| 450 |
+
.. versionchanged:: 26.2
|
| 451 |
+
|
| 452 |
+
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
| 453 |
+
be unpickled with future releases. Backward compatibility with pickles
|
| 454 |
+
from packaging < 26.2 is supported but may be removed in a future
|
| 455 |
+
release.
|
| 456 |
+
"""
|
| 457 |
+
|
| 458 |
+
__slots__ = (
|
| 459 |
+
"_prereleases",
|
| 460 |
+
"_ranges",
|
| 461 |
+
"_spec",
|
| 462 |
+
"_spec_version",
|
| 463 |
+
"_wildcard_split",
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
_specifier_regex_str = r"""
|
| 467 |
+
(?:
|
| 468 |
+
(?:
|
| 469 |
+
# The identity operators allow for an escape hatch that will
|
| 470 |
+
# do an exact string match of the version you wish to install.
|
| 471 |
+
# This will not be parsed by PEP 440 and we cannot determine
|
| 472 |
+
# any semantic meaning from it. This operator is discouraged
|
| 473 |
+
# but included entirely as an escape hatch.
|
| 474 |
+
=== # Only match for the identity operator
|
| 475 |
+
\s*
|
| 476 |
+
[^\s;)]* # The arbitrary version can be just about anything,
|
| 477 |
+
# we match everything except for whitespace, a
|
| 478 |
+
# semi-colon for marker support, and a closing paren
|
| 479 |
+
# since versions can be enclosed in them.
|
| 480 |
+
)
|
| 481 |
+
|
|
| 482 |
+
(?:
|
| 483 |
+
# The (non)equality operators allow for wild card and local
|
| 484 |
+
# versions to be specified so we have to define these two
|
| 485 |
+
# operators separately to enable that.
|
| 486 |
+
(?:==|!=) # Only match for equals and not equals
|
| 487 |
+
|
| 488 |
+
\s*
|
| 489 |
+
v?
|
| 490 |
+
(?:[0-9]+!)? # epoch
|
| 491 |
+
[0-9]+(?:\.[0-9]+)* # release
|
| 492 |
+
|
| 493 |
+
# You cannot use a wild card and a pre-release, post-release, a dev or
|
| 494 |
+
# local version together so group them with a | and make them optional.
|
| 495 |
+
(?:
|
| 496 |
+
\.\* # Wild card syntax of .*
|
| 497 |
+
|
|
| 498 |
+
(?a: # pre release
|
| 499 |
+
[-_\.]?
|
| 500 |
+
(alpha|beta|preview|pre|a|b|c|rc)
|
| 501 |
+
[-_\.]?
|
| 502 |
+
[0-9]*
|
| 503 |
+
)?
|
| 504 |
+
(?a: # post release
|
| 505 |
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
| 506 |
+
)?
|
| 507 |
+
(?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
| 508 |
+
(?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
|
| 509 |
+
)?
|
| 510 |
+
)
|
| 511 |
+
|
|
| 512 |
+
(?:
|
| 513 |
+
# The compatible operator requires at least two digits in the
|
| 514 |
+
# release segment.
|
| 515 |
+
(?:~=) # Only match for the compatible operator
|
| 516 |
+
|
| 517 |
+
\s*
|
| 518 |
+
v?
|
| 519 |
+
(?:[0-9]+!)? # epoch
|
| 520 |
+
[0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *)
|
| 521 |
+
(?: # pre release
|
| 522 |
+
[-_\.]?
|
| 523 |
+
(alpha|beta|preview|pre|a|b|c|rc)
|
| 524 |
+
[-_\.]?
|
| 525 |
+
[0-9]*
|
| 526 |
+
)?
|
| 527 |
+
(?: # post release
|
| 528 |
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
| 529 |
+
)?
|
| 530 |
+
(?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
| 531 |
+
)
|
| 532 |
+
|
|
| 533 |
+
(?:
|
| 534 |
+
# All other operators only allow a sub set of what the
|
| 535 |
+
# (non)equality operators do. Specifically they do not allow
|
| 536 |
+
# local versions to be specified nor do they allow the prefix
|
| 537 |
+
# matching wild cards.
|
| 538 |
+
(?:<=|>=|<|>)
|
| 539 |
+
|
| 540 |
+
\s*
|
| 541 |
+
v?
|
| 542 |
+
(?:[0-9]+!)? # epoch
|
| 543 |
+
[0-9]+(?:\.[0-9]+)* # release
|
| 544 |
+
(?a: # pre release
|
| 545 |
+
[-_\.]?
|
| 546 |
+
(alpha|beta|preview|pre|a|b|c|rc)
|
| 547 |
+
[-_\.]?
|
| 548 |
+
[0-9]*
|
| 549 |
+
)?
|
| 550 |
+
(?a: # post release
|
| 551 |
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
| 552 |
+
)?
|
| 553 |
+
(?a:[-_\.]?dev[-_\.]?[0-9]*)? # dev release
|
| 554 |
+
)
|
| 555 |
+
)
|
| 556 |
+
"""
|
| 557 |
+
|
| 558 |
+
_regex = re.compile(
|
| 559 |
+
r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
|
| 560 |
+
)
|
| 561 |
+
|
| 562 |
+
_operators: Final = {
|
| 563 |
+
"~=": "compatible",
|
| 564 |
+
"==": "equal",
|
| 565 |
+
"!=": "not_equal",
|
| 566 |
+
"<=": "less_than_equal",
|
| 567 |
+
">=": "greater_than_equal",
|
| 568 |
+
"<": "less_than",
|
| 569 |
+
">": "greater_than",
|
| 570 |
+
"===": "arbitrary",
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
|
| 574 |
+
"""Initialize a Specifier instance.
|
| 575 |
+
|
| 576 |
+
:param spec:
|
| 577 |
+
The string representation of a specifier which will be parsed and
|
| 578 |
+
normalized before use.
|
| 579 |
+
:param prereleases:
|
| 580 |
+
This tells the specifier if it should accept prerelease versions if
|
| 581 |
+
applicable or not. The default of ``None`` will autodetect it from the
|
| 582 |
+
given specifiers.
|
| 583 |
+
:raises InvalidSpecifier:
|
| 584 |
+
If the given specifier is invalid (i.e. bad syntax).
|
| 585 |
+
"""
|
| 586 |
+
if not self._regex.fullmatch(spec):
|
| 587 |
+
raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
|
| 588 |
+
|
| 589 |
+
spec = spec.strip()
|
| 590 |
+
if spec.startswith("==="):
|
| 591 |
+
operator, version = spec[:3], spec[3:].strip()
|
| 592 |
+
elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
|
| 593 |
+
operator, version = spec[:2], spec[2:].strip()
|
| 594 |
+
else:
|
| 595 |
+
operator, version = spec[:1], spec[1:].strip()
|
| 596 |
+
|
| 597 |
+
self._spec: tuple[str, str] = (operator, version)
|
| 598 |
+
|
| 599 |
+
# Store whether or not this Specifier should accept prereleases
|
| 600 |
+
self._prereleases = prereleases
|
| 601 |
+
|
| 602 |
+
# Specifier version cache
|
| 603 |
+
self._spec_version: tuple[str, Version] | None = None
|
| 604 |
+
|
| 605 |
+
# Populated on first wildcard (==X.*) comparison
|
| 606 |
+
self._wildcard_split: tuple[list[str], int] | None = None
|
| 607 |
+
|
| 608 |
+
# Version range cache (populated by _to_ranges)
|
| 609 |
+
self._ranges: Sequence[_VersionRange] | None = None
|
| 610 |
+
|
| 611 |
+
def _get_spec_version(self, version: str) -> Version | None:
|
| 612 |
+
"""One element cache, as only one spec Version is needed per Specifier."""
|
| 613 |
+
if self._spec_version is not None and self._spec_version[0] == version:
|
| 614 |
+
return self._spec_version[1]
|
| 615 |
+
|
| 616 |
+
version_specifier = _coerce_version(version)
|
| 617 |
+
if version_specifier is None:
|
| 618 |
+
return None
|
| 619 |
+
|
| 620 |
+
self._spec_version = (version, version_specifier)
|
| 621 |
+
return version_specifier
|
| 622 |
+
|
| 623 |
+
def _require_spec_version(self, version: str) -> Version:
|
| 624 |
+
"""Get spec version, asserting it's valid (not for === operator).
|
| 625 |
+
|
| 626 |
+
This method should only be called for operators where version
|
| 627 |
+
strings are guaranteed to be valid PEP 440 versions (not ===).
|
| 628 |
+
"""
|
| 629 |
+
spec_version = self._get_spec_version(version)
|
| 630 |
+
assert spec_version is not None
|
| 631 |
+
return spec_version
|
| 632 |
+
|
| 633 |
+
def _to_ranges(self) -> Sequence[_VersionRange]:
|
| 634 |
+
"""Convert this specifier to sorted, non-overlapping version ranges.
|
| 635 |
+
|
| 636 |
+
Each standard operator maps to one or two ranges. ``===`` is
|
| 637 |
+
modeled as full range (actual check done separately). Cached.
|
| 638 |
+
"""
|
| 639 |
+
if self._ranges is not None:
|
| 640 |
+
return self._ranges
|
| 641 |
+
|
| 642 |
+
op = self.operator
|
| 643 |
+
ver_str = self.version
|
| 644 |
+
|
| 645 |
+
if op == "===":
|
| 646 |
+
self._ranges = _FULL_RANGE
|
| 647 |
+
return _FULL_RANGE
|
| 648 |
+
|
| 649 |
+
if ver_str.endswith(".*"):
|
| 650 |
+
result = self._wildcard_ranges(op, ver_str)
|
| 651 |
+
else:
|
| 652 |
+
result = self._standard_ranges(op, ver_str)
|
| 653 |
+
|
| 654 |
+
self._ranges = result
|
| 655 |
+
return result
|
| 656 |
+
|
| 657 |
+
def _wildcard_ranges(self, op: str, ver_str: str) -> list[_VersionRange]:
|
| 658 |
+
# ==1.2.* -> [1.2.dev0, 1.3.dev0); !=1.2.* -> complement.
|
| 659 |
+
base = self._require_spec_version(ver_str[:-2])
|
| 660 |
+
lower = _base_dev0(base)
|
| 661 |
+
upper = _next_prefix_dev0(base)
|
| 662 |
+
if op == "==":
|
| 663 |
+
return [(_LowerBound(lower, True), _UpperBound(upper, False))]
|
| 664 |
+
# !=
|
| 665 |
+
return [
|
| 666 |
+
(_NEG_INF, _UpperBound(lower, False)),
|
| 667 |
+
(_LowerBound(upper, True), _POS_INF),
|
| 668 |
+
]
|
| 669 |
+
|
| 670 |
+
def _standard_ranges(self, op: str, ver_str: str) -> list[_VersionRange]:
|
| 671 |
+
v = self._require_spec_version(ver_str)
|
| 672 |
+
|
| 673 |
+
if op == ">=":
|
| 674 |
+
return [(_LowerBound(v, True), _POS_INF)]
|
| 675 |
+
|
| 676 |
+
if op == "<=":
|
| 677 |
+
return [
|
| 678 |
+
(
|
| 679 |
+
_NEG_INF,
|
| 680 |
+
_UpperBound(_BoundaryVersion(v, _BoundaryKind.AFTER_LOCALS), True),
|
| 681 |
+
)
|
| 682 |
+
]
|
| 683 |
+
|
| 684 |
+
if op == ">":
|
| 685 |
+
if v.dev is not None:
|
| 686 |
+
# >V.devN: dev versions have no post-releases, so the
|
| 687 |
+
# next real version is V.dev(N+1).
|
| 688 |
+
lower_ver = v.__replace__(dev=v.dev + 1, local=None)
|
| 689 |
+
return [(_LowerBound(lower_ver, True), _POS_INF)]
|
| 690 |
+
if v.post is not None:
|
| 691 |
+
# >V.postN: next real version is V.post(N+1).dev0.
|
| 692 |
+
lower_ver = v.__replace__(post=v.post + 1, dev=0, local=None)
|
| 693 |
+
return [(_LowerBound(lower_ver, True), _POS_INF)]
|
| 694 |
+
# >V (final or pre-release): skip V+local and all V.postN.
|
| 695 |
+
return [
|
| 696 |
+
(
|
| 697 |
+
_LowerBound(_BoundaryVersion(v, _BoundaryKind.AFTER_POSTS), False),
|
| 698 |
+
_POS_INF,
|
| 699 |
+
)
|
| 700 |
+
]
|
| 701 |
+
|
| 702 |
+
if op == "<":
|
| 703 |
+
# <V excludes prereleases of V when V is not a prerelease.
|
| 704 |
+
# V.dev0 is the earliest prerelease of V (final, post, etc.).
|
| 705 |
+
bound = v if v.is_prerelease else v.__replace__(dev=0, local=None)
|
| 706 |
+
if bound <= _MIN_VERSION:
|
| 707 |
+
return []
|
| 708 |
+
return [(_NEG_INF, _UpperBound(bound, False))]
|
| 709 |
+
|
| 710 |
+
# ==, !=: local versions of V match when spec has no local segment.
|
| 711 |
+
has_local = "+" in ver_str
|
| 712 |
+
after_locals = _BoundaryVersion(v, _BoundaryKind.AFTER_LOCALS)
|
| 713 |
+
upper = v if has_local else after_locals
|
| 714 |
+
|
| 715 |
+
if op == "==":
|
| 716 |
+
return [(_LowerBound(v, True), _UpperBound(upper, True))]
|
| 717 |
+
|
| 718 |
+
if op == "!=":
|
| 719 |
+
return [
|
| 720 |
+
(_NEG_INF, _UpperBound(v, False)),
|
| 721 |
+
(_LowerBound(upper, False), _POS_INF),
|
| 722 |
+
]
|
| 723 |
+
|
| 724 |
+
if op == "~=":
|
| 725 |
+
prefix = v.__replace__(release=v.release[:-1])
|
| 726 |
+
return [
|
| 727 |
+
(_LowerBound(v, True), _UpperBound(_next_prefix_dev0(prefix), False))
|
| 728 |
+
]
|
| 729 |
+
|
| 730 |
+
raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
|
| 731 |
+
|
| 732 |
+
@property
|
| 733 |
+
def prereleases(self) -> bool | None:
|
| 734 |
+
# If there is an explicit prereleases set for this, then we'll just
|
| 735 |
+
# blindly use that.
|
| 736 |
+
if self._prereleases is not None:
|
| 737 |
+
return self._prereleases
|
| 738 |
+
|
| 739 |
+
# Only the "!=" operator does not imply prereleases when
|
| 740 |
+
# the version in the specifier is a prerelease.
|
| 741 |
+
operator, version_str = self._spec
|
| 742 |
+
if operator == "!=":
|
| 743 |
+
return False
|
| 744 |
+
|
| 745 |
+
# The == specifier with trailing .* cannot include prereleases
|
| 746 |
+
# e.g. "==1.0a1.*" is not valid.
|
| 747 |
+
if operator == "==" and version_str.endswith(".*"):
|
| 748 |
+
return False
|
| 749 |
+
|
| 750 |
+
# "===" can have arbitrary string versions, so we cannot parse
|
| 751 |
+
# those, we take prereleases as unknown (None) for those.
|
| 752 |
+
version = self._get_spec_version(version_str)
|
| 753 |
+
if version is None:
|
| 754 |
+
return None
|
| 755 |
+
|
| 756 |
+
# For all other operators, use the check if spec Version
|
| 757 |
+
# object implies pre-releases.
|
| 758 |
+
return version.is_prerelease
|
| 759 |
+
|
| 760 |
+
@prereleases.setter
|
| 761 |
+
def prereleases(self, value: bool | None) -> None:
|
| 762 |
+
self._prereleases = value
|
| 763 |
+
|
| 764 |
+
def __getstate__(self) -> tuple[tuple[str, str], bool | None]:
|
| 765 |
+
# Return state as a 2-item tuple for compactness:
|
| 766 |
+
# ((operator, version), prereleases)
|
| 767 |
+
# Cache members are excluded and will be recomputed on demand.
|
| 768 |
+
return (self._spec, self._prereleases)
|
| 769 |
+
|
| 770 |
+
def __setstate__(self, state: object) -> None:
|
| 771 |
+
# Always discard cached values - they will be recomputed on demand.
|
| 772 |
+
self._spec_version = None
|
| 773 |
+
self._wildcard_split = None
|
| 774 |
+
self._ranges = None
|
| 775 |
+
|
| 776 |
+
if isinstance(state, tuple):
|
| 777 |
+
if len(state) == 2:
|
| 778 |
+
# New format (26.2+): ((operator, version), prereleases)
|
| 779 |
+
spec, prereleases = state
|
| 780 |
+
if _validate_spec(spec) and _validate_pre(prereleases):
|
| 781 |
+
self._spec = spec
|
| 782 |
+
self._prereleases = prereleases
|
| 783 |
+
return
|
| 784 |
+
if len(state) == 2 and isinstance(state[1], dict):
|
| 785 |
+
# Format (packaging 26.0-26.1): (None, {slot: value}).
|
| 786 |
+
_, slot_dict = state
|
| 787 |
+
spec = slot_dict.get("_spec")
|
| 788 |
+
prereleases = slot_dict.get("_prereleases", "invalid")
|
| 789 |
+
if _validate_spec(spec) and _validate_pre(prereleases):
|
| 790 |
+
self._spec = spec
|
| 791 |
+
self._prereleases = prereleases
|
| 792 |
+
return
|
| 793 |
+
if isinstance(state, dict):
|
| 794 |
+
# Old format (packaging <= 25.x, no __slots__): state is a plain dict.
|
| 795 |
+
spec = state.get("_spec")
|
| 796 |
+
prereleases = state.get("_prereleases", "invalid")
|
| 797 |
+
if _validate_spec(spec) and _validate_pre(prereleases):
|
| 798 |
+
self._spec = spec
|
| 799 |
+
self._prereleases = prereleases
|
| 800 |
+
return
|
| 801 |
+
|
| 802 |
+
raise TypeError(f"Cannot restore Specifier from {state!r}")
|
| 803 |
+
|
| 804 |
+
@property
|
| 805 |
+
def operator(self) -> str:
|
| 806 |
+
"""The operator of this specifier.
|
| 807 |
+
|
| 808 |
+
>>> Specifier("==1.2.3").operator
|
| 809 |
+
'=='
|
| 810 |
+
"""
|
| 811 |
+
return self._spec[0]
|
| 812 |
+
|
| 813 |
+
@property
|
| 814 |
+
def version(self) -> str:
|
| 815 |
+
"""The version of this specifier.
|
| 816 |
+
|
| 817 |
+
>>> Specifier("==1.2.3").version
|
| 818 |
+
'1.2.3'
|
| 819 |
+
"""
|
| 820 |
+
return self._spec[1]
|
| 821 |
+
|
| 822 |
+
def __repr__(self) -> str:
|
| 823 |
+
"""A representation of the Specifier that shows all internal state.
|
| 824 |
+
|
| 825 |
+
>>> Specifier('>=1.0.0')
|
| 826 |
+
<Specifier('>=1.0.0')>
|
| 827 |
+
>>> Specifier('>=1.0.0', prereleases=False)
|
| 828 |
+
<Specifier('>=1.0.0', prereleases=False)>
|
| 829 |
+
>>> Specifier('>=1.0.0', prereleases=True)
|
| 830 |
+
<Specifier('>=1.0.0', prereleases=True)>
|
| 831 |
+
"""
|
| 832 |
+
pre = (
|
| 833 |
+
f", prereleases={self.prereleases!r}"
|
| 834 |
+
if self._prereleases is not None
|
| 835 |
+
else ""
|
| 836 |
+
)
|
| 837 |
+
|
| 838 |
+
return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
|
| 839 |
+
|
| 840 |
+
def __str__(self) -> str:
|
| 841 |
+
"""A string representation of the Specifier that can be round-tripped.
|
| 842 |
+
|
| 843 |
+
>>> str(Specifier('>=1.0.0'))
|
| 844 |
+
'>=1.0.0'
|
| 845 |
+
>>> str(Specifier('>=1.0.0', prereleases=False))
|
| 846 |
+
'>=1.0.0'
|
| 847 |
+
"""
|
| 848 |
+
return "{}{}".format(*self._spec)
|
| 849 |
+
|
| 850 |
+
@property
|
| 851 |
+
def _canonical_spec(self) -> tuple[str, str]:
|
| 852 |
+
operator, version = self._spec
|
| 853 |
+
if operator == "===" or version.endswith(".*"):
|
| 854 |
+
return operator, version
|
| 855 |
+
|
| 856 |
+
spec_version = self._require_spec_version(version)
|
| 857 |
+
|
| 858 |
+
canonical_version = canonicalize_version(
|
| 859 |
+
spec_version, strip_trailing_zero=(operator != "~=")
|
| 860 |
+
)
|
| 861 |
+
|
| 862 |
+
return operator, canonical_version
|
| 863 |
+
|
| 864 |
+
def __hash__(self) -> int:
|
| 865 |
+
return hash(self._canonical_spec)
|
| 866 |
+
|
| 867 |
+
def __eq__(self, other: object) -> bool:
|
| 868 |
+
"""Whether or not the two Specifier-like objects are equal.
|
| 869 |
+
|
| 870 |
+
:param other: The other object to check against.
|
| 871 |
+
|
| 872 |
+
The value of :attr:`prereleases` is ignored.
|
| 873 |
+
|
| 874 |
+
>>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
|
| 875 |
+
True
|
| 876 |
+
>>> (Specifier("==1.2.3", prereleases=False) ==
|
| 877 |
+
... Specifier("==1.2.3", prereleases=True))
|
| 878 |
+
True
|
| 879 |
+
>>> Specifier("==1.2.3") == "==1.2.3"
|
| 880 |
+
True
|
| 881 |
+
>>> Specifier("==1.2.3") == Specifier("==1.2.4")
|
| 882 |
+
False
|
| 883 |
+
>>> Specifier("==1.2.3") == Specifier("~=1.2.3")
|
| 884 |
+
False
|
| 885 |
+
"""
|
| 886 |
+
if isinstance(other, str):
|
| 887 |
+
try:
|
| 888 |
+
other = self.__class__(str(other))
|
| 889 |
+
except InvalidSpecifier:
|
| 890 |
+
return NotImplemented
|
| 891 |
+
elif not isinstance(other, self.__class__):
|
| 892 |
+
return NotImplemented
|
| 893 |
+
|
| 894 |
+
return self._canonical_spec == other._canonical_spec
|
| 895 |
+
|
| 896 |
+
def _get_operator(self, op: str) -> CallableOperator:
|
| 897 |
+
operator_callable: CallableOperator = getattr(
|
| 898 |
+
self, f"_compare_{self._operators[op]}"
|
| 899 |
+
)
|
| 900 |
+
return operator_callable
|
| 901 |
+
|
| 902 |
+
def _compare_compatible(self, prospective: Version, spec: str) -> bool:
|
| 903 |
+
# Compatible releases have an equivalent combination of >= and ==. That
|
| 904 |
+
# is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
|
| 905 |
+
# implement this in terms of the other specifiers instead of
|
| 906 |
+
# implementing it ourselves. The only thing we need to do is construct
|
| 907 |
+
# the other specifiers.
|
| 908 |
+
|
| 909 |
+
# We want everything but the last item in the version, but we want to
|
| 910 |
+
# ignore suffix segments.
|
| 911 |
+
prefix = _version_join(
|
| 912 |
+
list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
|
| 913 |
+
)
|
| 914 |
+
|
| 915 |
+
# Add the prefix notation to the end of our string
|
| 916 |
+
prefix += ".*"
|
| 917 |
+
|
| 918 |
+
return (self._compare_greater_than_equal(prospective, spec)) and (
|
| 919 |
+
self._compare_equal(prospective, prefix)
|
| 920 |
+
)
|
| 921 |
+
|
| 922 |
+
def _get_wildcard_split(self, spec: str) -> tuple[list[str], int]:
|
| 923 |
+
"""Cached split of a wildcard spec into components and numeric length.
|
| 924 |
+
|
| 925 |
+
>>> Specifier("==1.*")._get_wildcard_split("1.*")
|
| 926 |
+
(['0', '1'], 2)
|
| 927 |
+
>>> Specifier("==3.10.*")._get_wildcard_split("3.10.*")
|
| 928 |
+
(['0', '3', '10'], 3)
|
| 929 |
+
"""
|
| 930 |
+
wildcard_split = self._wildcard_split
|
| 931 |
+
if wildcard_split is None:
|
| 932 |
+
normalized = canonicalize_version(spec[:-2], strip_trailing_zero=False)
|
| 933 |
+
split_spec = _version_split(normalized)
|
| 934 |
+
wildcard_split = (split_spec, _numeric_prefix_len(split_spec))
|
| 935 |
+
self._wildcard_split = wildcard_split
|
| 936 |
+
return wildcard_split
|
| 937 |
+
|
| 938 |
+
def _compare_equal(self, prospective: Version, spec: str) -> bool:
|
| 939 |
+
# We need special logic to handle prefix matching
|
| 940 |
+
if spec.endswith(".*"):
|
| 941 |
+
split_spec, spec_numeric_len = self._get_wildcard_split(spec)
|
| 942 |
+
|
| 943 |
+
# In the case of prefix matching we want to ignore local segment.
|
| 944 |
+
normalized_prospective = canonicalize_version(
|
| 945 |
+
_public_version(prospective), strip_trailing_zero=False
|
| 946 |
+
)
|
| 947 |
+
# Split the prospective version out by bangs and dots, and pretend
|
| 948 |
+
# that there is an implicit dot in between a release segment and
|
| 949 |
+
# a pre-release segment.
|
| 950 |
+
split_prospective = _version_split(normalized_prospective)
|
| 951 |
+
|
| 952 |
+
# 0-pad the prospective version before shortening it to get the correct
|
| 953 |
+
# shortened version.
|
| 954 |
+
padded_prospective = _left_pad(split_prospective, spec_numeric_len)
|
| 955 |
+
|
| 956 |
+
# Shorten the prospective version to be the same length as the spec
|
| 957 |
+
# so that we can determine if the specifier is a prefix of the
|
| 958 |
+
# prospective version or not.
|
| 959 |
+
shortened_prospective = padded_prospective[: len(split_spec)]
|
| 960 |
+
|
| 961 |
+
return shortened_prospective == split_spec
|
| 962 |
+
else:
|
| 963 |
+
# Convert our spec string into a Version
|
| 964 |
+
spec_version = self._require_spec_version(spec)
|
| 965 |
+
|
| 966 |
+
# If the specifier does not have a local segment, then we want to
|
| 967 |
+
# act as if the prospective version also does not have a local
|
| 968 |
+
# segment.
|
| 969 |
+
if not spec_version.local:
|
| 970 |
+
prospective = _public_version(prospective)
|
| 971 |
+
|
| 972 |
+
return prospective == spec_version
|
| 973 |
+
|
| 974 |
+
def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
|
| 975 |
+
return not self._compare_equal(prospective, spec)
|
| 976 |
+
|
| 977 |
+
def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
|
| 978 |
+
# NB: Local version identifiers are NOT permitted in the version
|
| 979 |
+
# specifier, so local version labels can be universally removed from
|
| 980 |
+
# the prospective version.
|
| 981 |
+
return _public_version(prospective) <= self._require_spec_version(spec)
|
| 982 |
+
|
| 983 |
+
def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
|
| 984 |
+
# NB: Local version identifiers are NOT permitted in the version
|
| 985 |
+
# specifier, so local version labels can be universally removed from
|
| 986 |
+
# the prospective version.
|
| 987 |
+
return _public_version(prospective) >= self._require_spec_version(spec)
|
| 988 |
+
|
| 989 |
+
def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
|
| 990 |
+
# Convert our spec to a Version instance, since we'll want to work with
|
| 991 |
+
# it as a version.
|
| 992 |
+
spec = self._require_spec_version(spec_str)
|
| 993 |
+
|
| 994 |
+
# Check to see if the prospective version is less than the spec
|
| 995 |
+
# version. If it's not we can short circuit and just return False now
|
| 996 |
+
# instead of doing extra unneeded work.
|
| 997 |
+
if not prospective < spec:
|
| 998 |
+
return False
|
| 999 |
+
|
| 1000 |
+
# The spec says: "<V MUST NOT allow a pre-release of the specified
|
| 1001 |
+
# version unless the specified version is itself a pre-release."
|
| 1002 |
+
if (
|
| 1003 |
+
not spec.is_prerelease
|
| 1004 |
+
and prospective.is_prerelease
|
| 1005 |
+
and prospective >= _earliest_prerelease(spec)
|
| 1006 |
+
):
|
| 1007 |
+
return False
|
| 1008 |
+
|
| 1009 |
+
# If we've gotten to here, it means that prospective version is both
|
| 1010 |
+
# less than the spec version *and* it's not a pre-release of the same
|
| 1011 |
+
# version in the spec.
|
| 1012 |
+
return True
|
| 1013 |
+
|
| 1014 |
+
def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
|
| 1015 |
+
# Convert our spec to a Version instance, since we'll want to work with
|
| 1016 |
+
# it as a version.
|
| 1017 |
+
spec = self._require_spec_version(spec_str)
|
| 1018 |
+
|
| 1019 |
+
# Check to see if the prospective version is greater than the spec
|
| 1020 |
+
# version. If it's not we can short circuit and just return False now
|
| 1021 |
+
# instead of doing extra unneeded work.
|
| 1022 |
+
if not prospective > spec:
|
| 1023 |
+
return False
|
| 1024 |
+
|
| 1025 |
+
# The spec says: ">V MUST NOT allow a post-release of the specified
|
| 1026 |
+
# version unless the specified version is itself a post-release."
|
| 1027 |
+
if (
|
| 1028 |
+
not spec.is_postrelease
|
| 1029 |
+
and prospective.is_postrelease
|
| 1030 |
+
and _post_base(prospective) == spec
|
| 1031 |
+
):
|
| 1032 |
+
return False
|
| 1033 |
+
|
| 1034 |
+
# Per the spec: ">V MUST NOT match a local version of the specified
|
| 1035 |
+
# version". A "local version of V" is any version whose public part
|
| 1036 |
+
# equals V. So >1.0a1 must not match 1.0a1+local, but must still
|
| 1037 |
+
# match 1.0a2+local.
|
| 1038 |
+
if prospective.local is not None and _public_version(prospective) == spec:
|
| 1039 |
+
return False
|
| 1040 |
+
|
| 1041 |
+
# If we've gotten to here, it means that prospective version is both
|
| 1042 |
+
# greater than the spec version *and* it's not a pre-release of the
|
| 1043 |
+
# same version in the spec.
|
| 1044 |
+
return True
|
| 1045 |
+
|
| 1046 |
+
def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
|
| 1047 |
+
return str(prospective).lower() == str(spec).lower()
|
| 1048 |
+
|
| 1049 |
+
def __contains__(self, item: str | Version) -> bool:
|
| 1050 |
+
"""Return whether or not the item is contained in this specifier.
|
| 1051 |
+
|
| 1052 |
+
:param item: The item to check for.
|
| 1053 |
+
|
| 1054 |
+
This is used for the ``in`` operator and behaves the same as
|
| 1055 |
+
:meth:`contains` with no ``prereleases`` argument passed.
|
| 1056 |
+
|
| 1057 |
+
>>> "1.2.3" in Specifier(">=1.2.3")
|
| 1058 |
+
True
|
| 1059 |
+
>>> Version("1.2.3") in Specifier(">=1.2.3")
|
| 1060 |
+
True
|
| 1061 |
+
>>> "1.0.0" in Specifier(">=1.2.3")
|
| 1062 |
+
False
|
| 1063 |
+
>>> "1.3.0a1" in Specifier(">=1.2.3")
|
| 1064 |
+
True
|
| 1065 |
+
>>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
|
| 1066 |
+
True
|
| 1067 |
+
"""
|
| 1068 |
+
return self.contains(item)
|
| 1069 |
+
|
| 1070 |
+
def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
|
| 1071 |
+
"""Return whether or not the item is contained in this specifier.
|
| 1072 |
+
|
| 1073 |
+
:param item:
|
| 1074 |
+
The item to check for, which can be a version string or a
|
| 1075 |
+
:class:`Version` instance.
|
| 1076 |
+
:param prereleases:
|
| 1077 |
+
Whether or not to match prereleases with this Specifier. If set to
|
| 1078 |
+
``None`` (the default), it will follow the recommendation from
|
| 1079 |
+
:pep:`440` and match prereleases, as there are no other versions.
|
| 1080 |
+
|
| 1081 |
+
>>> Specifier(">=1.2.3").contains("1.2.3")
|
| 1082 |
+
True
|
| 1083 |
+
>>> Specifier(">=1.2.3").contains(Version("1.2.3"))
|
| 1084 |
+
True
|
| 1085 |
+
>>> Specifier(">=1.2.3").contains("1.0.0")
|
| 1086 |
+
False
|
| 1087 |
+
>>> Specifier(">=1.2.3").contains("1.3.0a1")
|
| 1088 |
+
True
|
| 1089 |
+
>>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
|
| 1090 |
+
False
|
| 1091 |
+
>>> Specifier(">=1.2.3").contains("1.3.0a1")
|
| 1092 |
+
True
|
| 1093 |
+
"""
|
| 1094 |
+
|
| 1095 |
+
return bool(list(self.filter([item], prereleases=prereleases)))
|
| 1096 |
+
|
| 1097 |
+
@typing.overload
|
| 1098 |
+
def filter(
|
| 1099 |
+
self,
|
| 1100 |
+
iterable: Iterable[UnparsedVersionVar],
|
| 1101 |
+
prereleases: bool | None = None,
|
| 1102 |
+
key: None = ...,
|
| 1103 |
+
) -> Iterator[UnparsedVersionVar]: ...
|
| 1104 |
+
|
| 1105 |
+
@typing.overload
|
| 1106 |
+
def filter(
|
| 1107 |
+
self,
|
| 1108 |
+
iterable: Iterable[T],
|
| 1109 |
+
prereleases: bool | None = None,
|
| 1110 |
+
key: Callable[[T], UnparsedVersion] = ...,
|
| 1111 |
+
) -> Iterator[T]: ...
|
| 1112 |
+
|
| 1113 |
+
def filter(
|
| 1114 |
+
self,
|
| 1115 |
+
iterable: Iterable[Any],
|
| 1116 |
+
prereleases: bool | None = None,
|
| 1117 |
+
key: Callable[[Any], UnparsedVersion] | None = None,
|
| 1118 |
+
) -> Iterator[Any]:
|
| 1119 |
+
"""Filter items in the given iterable, that match the specifier.
|
| 1120 |
+
|
| 1121 |
+
:param iterable:
|
| 1122 |
+
An iterable that can contain version strings and :class:`Version` instances.
|
| 1123 |
+
The items in the iterable will be filtered according to the specifier.
|
| 1124 |
+
:param prereleases:
|
| 1125 |
+
Whether or not to allow prereleases in the returned iterator. If set to
|
| 1126 |
+
``None`` (the default), it will follow the recommendation from :pep:`440`
|
| 1127 |
+
and match prereleases if there are no other versions.
|
| 1128 |
+
:param key:
|
| 1129 |
+
A callable that takes a single argument (an item from the iterable) and
|
| 1130 |
+
returns a version string or :class:`Version` instance to be used for
|
| 1131 |
+
filtering.
|
| 1132 |
+
|
| 1133 |
+
>>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
|
| 1134 |
+
['1.3']
|
| 1135 |
+
>>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
|
| 1136 |
+
['1.2.3', '1.3', <Version('1.4')>]
|
| 1137 |
+
>>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
|
| 1138 |
+
['1.5a1']
|
| 1139 |
+
>>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
|
| 1140 |
+
['1.3', '1.5a1']
|
| 1141 |
+
>>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
|
| 1142 |
+
['1.3', '1.5a1']
|
| 1143 |
+
>>> list(Specifier(">=1.2.3").filter(
|
| 1144 |
+
... [{"ver": "1.2"}, {"ver": "1.3"}],
|
| 1145 |
+
... key=lambda x: x["ver"]))
|
| 1146 |
+
[{'ver': '1.3'}]
|
| 1147 |
+
"""
|
| 1148 |
+
prereleases_versions = []
|
| 1149 |
+
found_non_prereleases = False
|
| 1150 |
+
|
| 1151 |
+
# Determine if to include prereleases by default
|
| 1152 |
+
include_prereleases = (
|
| 1153 |
+
prereleases if prereleases is not None else self.prereleases
|
| 1154 |
+
)
|
| 1155 |
+
|
| 1156 |
+
# Get the matching operator
|
| 1157 |
+
operator_callable = self._get_operator(self.operator)
|
| 1158 |
+
|
| 1159 |
+
# Filter versions
|
| 1160 |
+
for version in iterable:
|
| 1161 |
+
parsed_version = _coerce_version(version if key is None else key(version))
|
| 1162 |
+
match = False
|
| 1163 |
+
if parsed_version is None:
|
| 1164 |
+
# === operator can match arbitrary (non-version) strings
|
| 1165 |
+
if self.operator == "===" and self._compare_arbitrary(
|
| 1166 |
+
version, self.version
|
| 1167 |
+
):
|
| 1168 |
+
yield version
|
| 1169 |
+
elif self.operator == "===":
|
| 1170 |
+
match = self._compare_arbitrary(
|
| 1171 |
+
version if key is None else key(version), self.version
|
| 1172 |
+
)
|
| 1173 |
+
else:
|
| 1174 |
+
match = operator_callable(parsed_version, self.version)
|
| 1175 |
+
|
| 1176 |
+
if match and parsed_version is not None:
|
| 1177 |
+
# If it's not a prerelease or prereleases are allowed, yield it directly
|
| 1178 |
+
if not parsed_version.is_prerelease or include_prereleases:
|
| 1179 |
+
found_non_prereleases = True
|
| 1180 |
+
yield version
|
| 1181 |
+
# Otherwise collect prereleases for potential later use
|
| 1182 |
+
elif prereleases is None and self._prereleases is not False:
|
| 1183 |
+
prereleases_versions.append(version)
|
| 1184 |
+
|
| 1185 |
+
# If no non-prereleases were found and prereleases weren't
|
| 1186 |
+
# explicitly forbidden, yield the collected prereleases
|
| 1187 |
+
if (
|
| 1188 |
+
not found_non_prereleases
|
| 1189 |
+
and prereleases is None
|
| 1190 |
+
and self._prereleases is not False
|
| 1191 |
+
):
|
| 1192 |
+
yield from prereleases_versions
|
| 1193 |
+
|
| 1194 |
+
|
| 1195 |
+
_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
|
| 1196 |
+
|
| 1197 |
+
|
| 1198 |
+
def _pep440_filter_prereleases(
|
| 1199 |
+
iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
|
| 1200 |
+
) -> Iterator[Any]:
|
| 1201 |
+
"""Filter per PEP 440: exclude prereleases unless no finals exist."""
|
| 1202 |
+
# Two lists used:
|
| 1203 |
+
# * all_nonfinal to preserve order if no finals exist
|
| 1204 |
+
# * arbitrary_strings for streaming when first final found
|
| 1205 |
+
all_nonfinal: list[Any] = []
|
| 1206 |
+
arbitrary_strings: list[Any] = []
|
| 1207 |
+
|
| 1208 |
+
found_final = False
|
| 1209 |
+
for item in iterable:
|
| 1210 |
+
parsed = _coerce_version(item if key is None else key(item))
|
| 1211 |
+
|
| 1212 |
+
if parsed is None:
|
| 1213 |
+
# Arbitrary strings are always included as it is not
|
| 1214 |
+
# possible to determine if they are prereleases,
|
| 1215 |
+
# and they have already passed all specifiers.
|
| 1216 |
+
if found_final:
|
| 1217 |
+
yield item
|
| 1218 |
+
else:
|
| 1219 |
+
arbitrary_strings.append(item)
|
| 1220 |
+
all_nonfinal.append(item)
|
| 1221 |
+
continue
|
| 1222 |
+
|
| 1223 |
+
if not parsed.is_prerelease:
|
| 1224 |
+
# Final release found - flush arbitrary strings, then yield
|
| 1225 |
+
if not found_final:
|
| 1226 |
+
yield from arbitrary_strings
|
| 1227 |
+
found_final = True
|
| 1228 |
+
yield item
|
| 1229 |
+
continue
|
| 1230 |
+
|
| 1231 |
+
# Prerelease - buffer if no finals yet, otherwise skip
|
| 1232 |
+
if not found_final:
|
| 1233 |
+
all_nonfinal.append(item)
|
| 1234 |
+
|
| 1235 |
+
# No finals found - yield all buffered items
|
| 1236 |
+
if not found_final:
|
| 1237 |
+
yield from all_nonfinal
|
| 1238 |
+
|
| 1239 |
+
|
| 1240 |
+
def _version_split(version: str) -> list[str]:
|
| 1241 |
+
"""Split version into components.
|
| 1242 |
+
|
| 1243 |
+
The split components are intended for version comparison. The logic does
|
| 1244 |
+
not attempt to retain the original version string, so joining the
|
| 1245 |
+
components back with :func:`_version_join` may not produce the original
|
| 1246 |
+
version string.
|
| 1247 |
+
"""
|
| 1248 |
+
result: list[str] = []
|
| 1249 |
+
|
| 1250 |
+
epoch, _, rest = version.rpartition("!")
|
| 1251 |
+
result.append(epoch or "0")
|
| 1252 |
+
|
| 1253 |
+
for item in rest.split("."):
|
| 1254 |
+
match = _prefix_regex.fullmatch(item)
|
| 1255 |
+
if match:
|
| 1256 |
+
result.extend(match.groups())
|
| 1257 |
+
else:
|
| 1258 |
+
result.append(item)
|
| 1259 |
+
return result
|
| 1260 |
+
|
| 1261 |
+
|
| 1262 |
+
def _version_join(components: list[str]) -> str:
|
| 1263 |
+
"""Join split version components into a version string.
|
| 1264 |
+
|
| 1265 |
+
This function assumes the input came from :func:`_version_split`, where the
|
| 1266 |
+
first component must be the epoch (either empty or numeric), and all other
|
| 1267 |
+
components numeric.
|
| 1268 |
+
"""
|
| 1269 |
+
epoch, *rest = components
|
| 1270 |
+
return f"{epoch}!{'.'.join(rest)}"
|
| 1271 |
+
|
| 1272 |
+
|
| 1273 |
+
def _is_not_suffix(segment: str) -> bool:
|
| 1274 |
+
return not any(
|
| 1275 |
+
segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
|
| 1276 |
+
)
|
| 1277 |
+
|
| 1278 |
+
|
| 1279 |
+
def _numeric_prefix_len(split: list[str]) -> int:
|
| 1280 |
+
"""Count leading numeric components in a :func:`_version_split` result.
|
| 1281 |
+
|
| 1282 |
+
>>> _numeric_prefix_len(["0", "1", "2", "a1"])
|
| 1283 |
+
3
|
| 1284 |
+
"""
|
| 1285 |
+
count = 0
|
| 1286 |
+
for segment in split:
|
| 1287 |
+
if not segment.isdigit():
|
| 1288 |
+
break
|
| 1289 |
+
count += 1
|
| 1290 |
+
return count
|
| 1291 |
+
|
| 1292 |
+
|
| 1293 |
+
def _left_pad(split: list[str], target_numeric_len: int) -> list[str]:
|
| 1294 |
+
"""Pad a :func:`_version_split` result with ``"0"`` segments to reach
|
| 1295 |
+
``target_numeric_len`` numeric components. Suffix segments are preserved.
|
| 1296 |
+
|
| 1297 |
+
>>> _left_pad(["0", "1", "a1"], 4)
|
| 1298 |
+
['0', '1', '0', '0', 'a1']
|
| 1299 |
+
"""
|
| 1300 |
+
numeric_len = _numeric_prefix_len(split)
|
| 1301 |
+
pad_needed = target_numeric_len - numeric_len
|
| 1302 |
+
if pad_needed <= 0:
|
| 1303 |
+
return split
|
| 1304 |
+
return [*split[:numeric_len], *(["0"] * pad_needed), *split[numeric_len:]]
|
| 1305 |
+
|
| 1306 |
+
|
| 1307 |
+
def _operator_cost(op_entry: tuple[CallableOperator, str, str]) -> int:
|
| 1308 |
+
"""Sort key for Cost Based Ordering of specifier operators in _filter_versions.
|
| 1309 |
+
|
| 1310 |
+
Operators run sequentially on a shrinking candidate set, so operators that
|
| 1311 |
+
reject the most versions should run first to minimize work for later ones.
|
| 1312 |
+
|
| 1313 |
+
Tier 0: Exact equality (==, ===), likely to narrow candidates to one version
|
| 1314 |
+
Tier 1: Range checks (>=, <=, >, <), cheap and usually reject a large portion
|
| 1315 |
+
Tier 2: Wildcard equality (==.*) and compatible release (~=), more expensive
|
| 1316 |
+
Tier 3: Exact !=, cheap but rarely rejects
|
| 1317 |
+
Tier 4: Wildcard !=.*, expensive and rarely rejects
|
| 1318 |
+
"""
|
| 1319 |
+
_, ver, op = op_entry
|
| 1320 |
+
if op == "==":
|
| 1321 |
+
return 0 if not ver.endswith(".*") else 2
|
| 1322 |
+
if op in (">=", "<=", ">", "<"):
|
| 1323 |
+
return 1
|
| 1324 |
+
if op == "~=":
|
| 1325 |
+
return 2
|
| 1326 |
+
if op == "!=":
|
| 1327 |
+
return 3 if not ver.endswith(".*") else 4
|
| 1328 |
+
if op == "===":
|
| 1329 |
+
return 0
|
| 1330 |
+
|
| 1331 |
+
raise ValueError(f"Unknown operator: {op!r}") # pragma: no cover
|
| 1332 |
+
|
| 1333 |
+
|
| 1334 |
+
class SpecifierSet(BaseSpecifier):
|
| 1335 |
+
"""This class abstracts handling of a set of version specifiers.
|
| 1336 |
+
|
| 1337 |
+
It can be passed a single specifier (``>=3.0``), a comma-separated list of
|
| 1338 |
+
specifiers (``>=3.0,!=3.1``), or no specifier at all.
|
| 1339 |
+
|
| 1340 |
+
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
| 1341 |
+
format so the same pickle can be loaded in future packaging
|
| 1342 |
+
releases.
|
| 1343 |
+
|
| 1344 |
+
.. versionchanged:: 26.2
|
| 1345 |
+
|
| 1346 |
+
Added a stable pickle format. Pickles created with
|
| 1347 |
+
packaging 26.2+ can be unpickled with future releases.
|
| 1348 |
+
Backward compatibility with pickles from
|
| 1349 |
+
packaging < 26.2 is supported but may be removed in a future
|
| 1350 |
+
release.
|
| 1351 |
+
"""
|
| 1352 |
+
|
| 1353 |
+
__slots__ = (
|
| 1354 |
+
"_canonicalized",
|
| 1355 |
+
"_has_arbitrary",
|
| 1356 |
+
"_is_unsatisfiable",
|
| 1357 |
+
"_prereleases",
|
| 1358 |
+
"_resolved_ops",
|
| 1359 |
+
"_specs",
|
| 1360 |
+
)
|
| 1361 |
+
|
| 1362 |
+
def __init__(
|
| 1363 |
+
self,
|
| 1364 |
+
specifiers: str | Iterable[Specifier] = "",
|
| 1365 |
+
prereleases: bool | None = None,
|
| 1366 |
+
) -> None:
|
| 1367 |
+
"""Initialize a SpecifierSet instance.
|
| 1368 |
+
|
| 1369 |
+
:param specifiers:
|
| 1370 |
+
The string representation of a specifier or a comma-separated list of
|
| 1371 |
+
specifiers which will be parsed and normalized before use.
|
| 1372 |
+
May also be an iterable of ``Specifier`` instances, which will be used
|
| 1373 |
+
as is.
|
| 1374 |
+
:param prereleases:
|
| 1375 |
+
This tells the SpecifierSet if it should accept prerelease versions if
|
| 1376 |
+
applicable or not. The default of ``None`` will autodetect it from the
|
| 1377 |
+
given specifiers.
|
| 1378 |
+
|
| 1379 |
+
:raises InvalidSpecifier:
|
| 1380 |
+
If the given ``specifiers`` are not parseable than this exception will be
|
| 1381 |
+
raised.
|
| 1382 |
+
"""
|
| 1383 |
+
|
| 1384 |
+
if isinstance(specifiers, str):
|
| 1385 |
+
# Split on `,` to break each individual specifier into its own item, and
|
| 1386 |
+
# strip each item to remove leading/trailing whitespace.
|
| 1387 |
+
split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
|
| 1388 |
+
|
| 1389 |
+
self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers))
|
| 1390 |
+
# Fast substring check; avoids iterating parsed specs.
|
| 1391 |
+
self._has_arbitrary = "===" in specifiers
|
| 1392 |
+
else:
|
| 1393 |
+
self._specs = tuple(specifiers)
|
| 1394 |
+
# Substring check works for both Specifier objects and plain
|
| 1395 |
+
# strings (setuptools passes lists of strings).
|
| 1396 |
+
self._has_arbitrary = any("===" in str(s) for s in self._specs)
|
| 1397 |
+
|
| 1398 |
+
self._canonicalized = len(self._specs) <= 1
|
| 1399 |
+
self._resolved_ops: list[tuple[CallableOperator, str, str]] | None = None
|
| 1400 |
+
|
| 1401 |
+
# Store our prereleases value so we can use it later to determine if
|
| 1402 |
+
# we accept prereleases or not.
|
| 1403 |
+
self._prereleases = prereleases
|
| 1404 |
+
|
| 1405 |
+
self._is_unsatisfiable: bool | None = None
|
| 1406 |
+
|
| 1407 |
+
def _canonical_specs(self) -> tuple[Specifier, ...]:
|
| 1408 |
+
"""Deduplicate, sort, and cache specs for order-sensitive operations."""
|
| 1409 |
+
if not self._canonicalized:
|
| 1410 |
+
self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str)))
|
| 1411 |
+
self._canonicalized = True
|
| 1412 |
+
self._resolved_ops = None
|
| 1413 |
+
self._is_unsatisfiable = None
|
| 1414 |
+
return self._specs
|
| 1415 |
+
|
| 1416 |
+
@property
|
| 1417 |
+
def prereleases(self) -> bool | None:
|
| 1418 |
+
# If we have been given an explicit prerelease modifier, then we'll
|
| 1419 |
+
# pass that through here.
|
| 1420 |
+
if self._prereleases is not None:
|
| 1421 |
+
return self._prereleases
|
| 1422 |
+
|
| 1423 |
+
# If we don't have any specifiers, and we don't have a forced value,
|
| 1424 |
+
# then we'll just return None since we don't know if this should have
|
| 1425 |
+
# pre-releases or not.
|
| 1426 |
+
if not self._specs:
|
| 1427 |
+
return None
|
| 1428 |
+
|
| 1429 |
+
# Otherwise we'll see if any of the given specifiers accept
|
| 1430 |
+
# prereleases, if any of them do we'll return True, otherwise False.
|
| 1431 |
+
if any(s.prereleases for s in self._specs):
|
| 1432 |
+
return True
|
| 1433 |
+
|
| 1434 |
+
return None
|
| 1435 |
+
|
| 1436 |
+
@prereleases.setter
|
| 1437 |
+
def prereleases(self, value: bool | None) -> None:
|
| 1438 |
+
self._prereleases = value
|
| 1439 |
+
self._is_unsatisfiable = None
|
| 1440 |
+
|
| 1441 |
+
def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]:
|
| 1442 |
+
# Return state as a 2-item tuple for compactness:
|
| 1443 |
+
# (specs, prereleases)
|
| 1444 |
+
# Cache members are excluded and will be recomputed on demand.
|
| 1445 |
+
return (self._specs, self._prereleases)
|
| 1446 |
+
|
| 1447 |
+
def __setstate__(self, state: object) -> None:
|
| 1448 |
+
# Always discard cached values - they will be recomputed on demand.
|
| 1449 |
+
self._resolved_ops = None
|
| 1450 |
+
self._is_unsatisfiable = None
|
| 1451 |
+
|
| 1452 |
+
if isinstance(state, tuple):
|
| 1453 |
+
if len(state) == 2:
|
| 1454 |
+
# New format (26.2+): (specs, prereleases)
|
| 1455 |
+
specs, prereleases = state
|
| 1456 |
+
if (
|
| 1457 |
+
isinstance(specs, tuple)
|
| 1458 |
+
and all(isinstance(s, Specifier) for s in specs)
|
| 1459 |
+
and _validate_pre(prereleases)
|
| 1460 |
+
):
|
| 1461 |
+
self._specs = specs
|
| 1462 |
+
self._prereleases = prereleases
|
| 1463 |
+
self._canonicalized = len(specs) <= 1
|
| 1464 |
+
self._has_arbitrary = any("===" in str(s) for s in specs)
|
| 1465 |
+
return
|
| 1466 |
+
if len(state) == 2 and isinstance(state[1], dict):
|
| 1467 |
+
# Format (packaging 26.0-26.1): (None, {slot: value}).
|
| 1468 |
+
_, slot_dict = state
|
| 1469 |
+
specs = slot_dict.get("_specs", ())
|
| 1470 |
+
prereleases = slot_dict.get("_prereleases")
|
| 1471 |
+
# Convert frozenset to tuple (26.0 stored as frozenset)
|
| 1472 |
+
if isinstance(specs, frozenset):
|
| 1473 |
+
specs = tuple(sorted(specs, key=str))
|
| 1474 |
+
if (
|
| 1475 |
+
isinstance(specs, tuple)
|
| 1476 |
+
and all(isinstance(s, Specifier) for s in specs)
|
| 1477 |
+
and _validate_pre(prereleases)
|
| 1478 |
+
):
|
| 1479 |
+
self._specs = specs
|
| 1480 |
+
self._prereleases = prereleases
|
| 1481 |
+
self._canonicalized = len(self._specs) <= 1
|
| 1482 |
+
self._has_arbitrary = any("===" in str(s) for s in self._specs)
|
| 1483 |
+
return
|
| 1484 |
+
if isinstance(state, dict):
|
| 1485 |
+
# Old format (packaging <= 25.x, no __slots__): state is a plain dict.
|
| 1486 |
+
specs = state.get("_specs", ())
|
| 1487 |
+
prereleases = state.get("_prereleases")
|
| 1488 |
+
# Convert frozenset to tuple (26.0 stored as frozenset)
|
| 1489 |
+
if isinstance(specs, frozenset):
|
| 1490 |
+
specs = tuple(sorted(specs, key=str))
|
| 1491 |
+
if (
|
| 1492 |
+
isinstance(specs, tuple)
|
| 1493 |
+
and all(isinstance(s, Specifier) for s in specs)
|
| 1494 |
+
and _validate_pre(prereleases)
|
| 1495 |
+
):
|
| 1496 |
+
self._specs = specs
|
| 1497 |
+
self._prereleases = prereleases
|
| 1498 |
+
self._canonicalized = len(self._specs) <= 1
|
| 1499 |
+
self._has_arbitrary = any("===" in str(s) for s in self._specs)
|
| 1500 |
+
return
|
| 1501 |
+
|
| 1502 |
+
raise TypeError(f"Cannot restore SpecifierSet from {state!r}")
|
| 1503 |
+
|
| 1504 |
+
def __repr__(self) -> str:
|
| 1505 |
+
"""A representation of the specifier set that shows all internal state.
|
| 1506 |
+
|
| 1507 |
+
Note that the ordering of the individual specifiers within the set may not
|
| 1508 |
+
match the input string.
|
| 1509 |
+
|
| 1510 |
+
>>> SpecifierSet('>=1.0.0,!=2.0.0')
|
| 1511 |
+
<SpecifierSet('!=2.0.0,>=1.0.0')>
|
| 1512 |
+
>>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
|
| 1513 |
+
<SpecifierSet('!=2.0.0,>=1.0.0', prereleases=False)>
|
| 1514 |
+
>>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
|
| 1515 |
+
<SpecifierSet('!=2.0.0,>=1.0.0', prereleases=True)>
|
| 1516 |
+
"""
|
| 1517 |
+
pre = (
|
| 1518 |
+
f", prereleases={self.prereleases!r}"
|
| 1519 |
+
if self._prereleases is not None
|
| 1520 |
+
else ""
|
| 1521 |
+
)
|
| 1522 |
+
|
| 1523 |
+
return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
|
| 1524 |
+
|
| 1525 |
+
def __str__(self) -> str:
|
| 1526 |
+
"""A string representation of the specifier set that can be round-tripped.
|
| 1527 |
+
|
| 1528 |
+
Note that the ordering of the individual specifiers within the set may not
|
| 1529 |
+
match the input string.
|
| 1530 |
+
|
| 1531 |
+
>>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
|
| 1532 |
+
'!=1.0.1,>=1.0.0'
|
| 1533 |
+
>>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
|
| 1534 |
+
'!=1.0.1,>=1.0.0'
|
| 1535 |
+
"""
|
| 1536 |
+
return ",".join(str(s) for s in self._canonical_specs())
|
| 1537 |
+
|
| 1538 |
+
def __hash__(self) -> int:
|
| 1539 |
+
return hash(self._canonical_specs())
|
| 1540 |
+
|
| 1541 |
+
def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
|
| 1542 |
+
"""Return a SpecifierSet which is a combination of the two sets.
|
| 1543 |
+
|
| 1544 |
+
:param other: The other object to combine with.
|
| 1545 |
+
|
| 1546 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
|
| 1547 |
+
<SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
|
| 1548 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
|
| 1549 |
+
<SpecifierSet('!=1.0.1,!=2.0.1,<=2.0.0,>=1.0.0')>
|
| 1550 |
+
"""
|
| 1551 |
+
if isinstance(other, str):
|
| 1552 |
+
other = SpecifierSet(other)
|
| 1553 |
+
elif not isinstance(other, SpecifierSet):
|
| 1554 |
+
return NotImplemented
|
| 1555 |
+
|
| 1556 |
+
specifier = SpecifierSet()
|
| 1557 |
+
specifier._specs = self._specs + other._specs
|
| 1558 |
+
specifier._canonicalized = len(specifier._specs) <= 1
|
| 1559 |
+
specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
|
| 1560 |
+
specifier._resolved_ops = None
|
| 1561 |
+
|
| 1562 |
+
# Combine prerelease settings: use common or non-None value
|
| 1563 |
+
if self._prereleases is None or self._prereleases == other._prereleases:
|
| 1564 |
+
specifier._prereleases = other._prereleases
|
| 1565 |
+
elif other._prereleases is None:
|
| 1566 |
+
specifier._prereleases = self._prereleases
|
| 1567 |
+
else:
|
| 1568 |
+
raise ValueError(
|
| 1569 |
+
"Cannot combine SpecifierSets with True and False prerelease overrides."
|
| 1570 |
+
)
|
| 1571 |
+
|
| 1572 |
+
return specifier
|
| 1573 |
+
|
| 1574 |
+
def __eq__(self, other: object) -> bool:
|
| 1575 |
+
"""Whether or not the two SpecifierSet-like objects are equal.
|
| 1576 |
+
|
| 1577 |
+
:param other: The other object to check against.
|
| 1578 |
+
|
| 1579 |
+
The value of :attr:`prereleases` is ignored.
|
| 1580 |
+
|
| 1581 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
|
| 1582 |
+
True
|
| 1583 |
+
>>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
|
| 1584 |
+
... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
|
| 1585 |
+
True
|
| 1586 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
|
| 1587 |
+
True
|
| 1588 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
|
| 1589 |
+
False
|
| 1590 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
|
| 1591 |
+
False
|
| 1592 |
+
"""
|
| 1593 |
+
if isinstance(other, (str, Specifier)):
|
| 1594 |
+
other = SpecifierSet(str(other))
|
| 1595 |
+
elif not isinstance(other, SpecifierSet):
|
| 1596 |
+
return NotImplemented
|
| 1597 |
+
|
| 1598 |
+
return self._canonical_specs() == other._canonical_specs()
|
| 1599 |
+
|
| 1600 |
+
def __len__(self) -> int:
|
| 1601 |
+
"""Returns the number of specifiers in this specifier set."""
|
| 1602 |
+
return len(self._specs)
|
| 1603 |
+
|
| 1604 |
+
def __iter__(self) -> Iterator[Specifier]:
|
| 1605 |
+
"""
|
| 1606 |
+
Returns an iterator over all the underlying :class:`Specifier` instances
|
| 1607 |
+
in this specifier set.
|
| 1608 |
+
|
| 1609 |
+
>>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
|
| 1610 |
+
[<Specifier('!=1.0.1')>, <Specifier('>=1.0.0')>]
|
| 1611 |
+
"""
|
| 1612 |
+
return iter(self._specs)
|
| 1613 |
+
|
| 1614 |
+
def _get_ranges(self) -> Sequence[_VersionRange]:
|
| 1615 |
+
"""Intersect all specifiers into a single list of version ranges.
|
| 1616 |
+
|
| 1617 |
+
Returns an empty list when unsatisfiable. ``===`` specs are
|
| 1618 |
+
modeled as full range; string matching is checked separately
|
| 1619 |
+
by :meth:`_check_arbitrary_unsatisfiable`.
|
| 1620 |
+
"""
|
| 1621 |
+
specs = self._specs
|
| 1622 |
+
|
| 1623 |
+
result: Sequence[_VersionRange] | None = None
|
| 1624 |
+
for s in specs:
|
| 1625 |
+
if result is None:
|
| 1626 |
+
result = s._to_ranges()
|
| 1627 |
+
else:
|
| 1628 |
+
result = _intersect_ranges(result, s._to_ranges())
|
| 1629 |
+
if not result:
|
| 1630 |
+
break
|
| 1631 |
+
|
| 1632 |
+
if result is None: # pragma: no cover
|
| 1633 |
+
raise RuntimeError("_get_ranges called with no specs")
|
| 1634 |
+
return result
|
| 1635 |
+
|
| 1636 |
+
def is_unsatisfiable(self) -> bool:
|
| 1637 |
+
"""Check whether this specifier set can never be satisfied.
|
| 1638 |
+
|
| 1639 |
+
Returns True if no version can satisfy all specifiers simultaneously.
|
| 1640 |
+
|
| 1641 |
+
>>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable()
|
| 1642 |
+
True
|
| 1643 |
+
>>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable()
|
| 1644 |
+
False
|
| 1645 |
+
>>> SpecifierSet("").is_unsatisfiable()
|
| 1646 |
+
False
|
| 1647 |
+
>>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable()
|
| 1648 |
+
True
|
| 1649 |
+
"""
|
| 1650 |
+
cached = self._is_unsatisfiable
|
| 1651 |
+
if cached is not None:
|
| 1652 |
+
return cached
|
| 1653 |
+
|
| 1654 |
+
if not self._specs:
|
| 1655 |
+
self._is_unsatisfiable = False
|
| 1656 |
+
return False
|
| 1657 |
+
|
| 1658 |
+
result = not self._get_ranges()
|
| 1659 |
+
|
| 1660 |
+
if not result:
|
| 1661 |
+
result = self._check_arbitrary_unsatisfiable()
|
| 1662 |
+
|
| 1663 |
+
if not result and self.prereleases is False:
|
| 1664 |
+
result = self._check_prerelease_only_ranges()
|
| 1665 |
+
|
| 1666 |
+
self._is_unsatisfiable = result
|
| 1667 |
+
return result
|
| 1668 |
+
|
| 1669 |
+
def _check_prerelease_only_ranges(self) -> bool:
|
| 1670 |
+
"""With prereleases=False, check if every range contains only
|
| 1671 |
+
pre-release versions (which would be excluded from matching)."""
|
| 1672 |
+
for lower, upper in self._get_ranges():
|
| 1673 |
+
nearest = _nearest_non_prerelease(lower.version)
|
| 1674 |
+
if nearest is None:
|
| 1675 |
+
return False
|
| 1676 |
+
if upper.version is None or nearest < upper.version:
|
| 1677 |
+
return False
|
| 1678 |
+
if nearest == upper.version and upper.inclusive:
|
| 1679 |
+
return False
|
| 1680 |
+
return True
|
| 1681 |
+
|
| 1682 |
+
def _check_arbitrary_unsatisfiable(self) -> bool:
|
| 1683 |
+
"""Check === (arbitrary equality) specs for unsatisfiability.
|
| 1684 |
+
|
| 1685 |
+
=== uses case-insensitive string comparison, so the only candidate
|
| 1686 |
+
that can match ``===V`` is the literal string V. This method
|
| 1687 |
+
checks whether that candidate is excluded by other specifiers.
|
| 1688 |
+
"""
|
| 1689 |
+
arbitrary = [s for s in self._specs if s.operator == "==="]
|
| 1690 |
+
if not arbitrary:
|
| 1691 |
+
return False
|
| 1692 |
+
|
| 1693 |
+
# Multiple === must agree on the same string (case-insensitive).
|
| 1694 |
+
first = arbitrary[0].version.lower()
|
| 1695 |
+
if any(s.version.lower() != first for s in arbitrary[1:]):
|
| 1696 |
+
return True
|
| 1697 |
+
|
| 1698 |
+
# The sole candidate is the === version string. Check whether
|
| 1699 |
+
# it can satisfy every standard spec.
|
| 1700 |
+
candidate = _coerce_version(arbitrary[0].version)
|
| 1701 |
+
|
| 1702 |
+
# With prereleases=False, a prerelease candidate is excluded
|
| 1703 |
+
# by contains() before the === string check even runs.
|
| 1704 |
+
if (
|
| 1705 |
+
self.prereleases is False
|
| 1706 |
+
and candidate is not None
|
| 1707 |
+
and candidate.is_prerelease
|
| 1708 |
+
):
|
| 1709 |
+
return True
|
| 1710 |
+
|
| 1711 |
+
standard = [s for s in self._specs if s.operator != "==="]
|
| 1712 |
+
if not standard:
|
| 1713 |
+
return False
|
| 1714 |
+
|
| 1715 |
+
if candidate is None:
|
| 1716 |
+
# Unparsable string cannot satisfy any standard spec.
|
| 1717 |
+
return True
|
| 1718 |
+
|
| 1719 |
+
return not all(s.contains(candidate) for s in standard)
|
| 1720 |
+
|
| 1721 |
+
def __contains__(self, item: UnparsedVersion) -> bool:
|
| 1722 |
+
"""Return whether or not the item is contained in this specifier.
|
| 1723 |
+
|
| 1724 |
+
:param item: The item to check for.
|
| 1725 |
+
|
| 1726 |
+
This is used for the ``in`` operator and behaves the same as
|
| 1727 |
+
:meth:`contains` with no ``prereleases`` argument passed.
|
| 1728 |
+
|
| 1729 |
+
>>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
|
| 1730 |
+
True
|
| 1731 |
+
>>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
|
| 1732 |
+
True
|
| 1733 |
+
>>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
|
| 1734 |
+
False
|
| 1735 |
+
>>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
|
| 1736 |
+
True
|
| 1737 |
+
>>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
|
| 1738 |
+
True
|
| 1739 |
+
"""
|
| 1740 |
+
return self.contains(item)
|
| 1741 |
+
|
| 1742 |
+
def contains(
|
| 1743 |
+
self,
|
| 1744 |
+
item: UnparsedVersion,
|
| 1745 |
+
prereleases: bool | None = None,
|
| 1746 |
+
installed: bool | None = None,
|
| 1747 |
+
) -> bool:
|
| 1748 |
+
"""Return whether or not the item is contained in this SpecifierSet.
|
| 1749 |
+
|
| 1750 |
+
:param item:
|
| 1751 |
+
The item to check for, which can be a version string or a
|
| 1752 |
+
:class:`Version` instance.
|
| 1753 |
+
:param prereleases:
|
| 1754 |
+
Whether or not to match prereleases with this SpecifierSet. If set to
|
| 1755 |
+
``None`` (the default), it will follow the recommendation from :pep:`440`
|
| 1756 |
+
and match prereleases, as there are no other versions.
|
| 1757 |
+
:param installed:
|
| 1758 |
+
Whether or not the item is installed. If set to ``True``, it will
|
| 1759 |
+
accept prerelease versions even if the specifier does not allow them.
|
| 1760 |
+
|
| 1761 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
|
| 1762 |
+
True
|
| 1763 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
|
| 1764 |
+
True
|
| 1765 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
|
| 1766 |
+
False
|
| 1767 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
|
| 1768 |
+
True
|
| 1769 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
|
| 1770 |
+
False
|
| 1771 |
+
>>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
|
| 1772 |
+
True
|
| 1773 |
+
"""
|
| 1774 |
+
version = _coerce_version(item)
|
| 1775 |
+
|
| 1776 |
+
if version is not None and installed and version.is_prerelease:
|
| 1777 |
+
prereleases = True
|
| 1778 |
+
|
| 1779 |
+
# When item is a string and === is involved, keep it as-is
|
| 1780 |
+
# so the comparison isn't done against the normalized form.
|
| 1781 |
+
if version is None or (self._has_arbitrary and not isinstance(item, Version)):
|
| 1782 |
+
check_item = item
|
| 1783 |
+
else:
|
| 1784 |
+
check_item = version
|
| 1785 |
+
return bool(list(self.filter([check_item], prereleases=prereleases)))
|
| 1786 |
+
|
| 1787 |
+
@typing.overload
|
| 1788 |
+
def filter(
|
| 1789 |
+
self,
|
| 1790 |
+
iterable: Iterable[UnparsedVersionVar],
|
| 1791 |
+
prereleases: bool | None = None,
|
| 1792 |
+
key: None = ...,
|
| 1793 |
+
) -> Iterator[UnparsedVersionVar]: ...
|
| 1794 |
+
|
| 1795 |
+
@typing.overload
|
| 1796 |
+
def filter(
|
| 1797 |
+
self,
|
| 1798 |
+
iterable: Iterable[T],
|
| 1799 |
+
prereleases: bool | None = None,
|
| 1800 |
+
key: Callable[[T], UnparsedVersion] = ...,
|
| 1801 |
+
) -> Iterator[T]: ...
|
| 1802 |
+
|
| 1803 |
+
def filter(
|
| 1804 |
+
self,
|
| 1805 |
+
iterable: Iterable[Any],
|
| 1806 |
+
prereleases: bool | None = None,
|
| 1807 |
+
key: Callable[[Any], UnparsedVersion] | None = None,
|
| 1808 |
+
) -> Iterator[Any]:
|
| 1809 |
+
"""Filter items in the given iterable, that match the specifiers in this set.
|
| 1810 |
+
|
| 1811 |
+
:param iterable:
|
| 1812 |
+
An iterable that can contain version strings and :class:`Version` instances.
|
| 1813 |
+
The items in the iterable will be filtered according to the specifier.
|
| 1814 |
+
:param prereleases:
|
| 1815 |
+
Whether or not to allow prereleases in the returned iterator. If set to
|
| 1816 |
+
``None`` (the default), it will follow the recommendation from :pep:`440`
|
| 1817 |
+
and match prereleases if there are no other versions.
|
| 1818 |
+
:param key:
|
| 1819 |
+
A callable that takes a single argument (an item from the iterable) and
|
| 1820 |
+
returns a version string or :class:`Version` instance to be used for
|
| 1821 |
+
filtering.
|
| 1822 |
+
|
| 1823 |
+
>>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
|
| 1824 |
+
['1.3']
|
| 1825 |
+
>>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
|
| 1826 |
+
['1.3', <Version('1.4')>]
|
| 1827 |
+
>>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
|
| 1828 |
+
['1.5a1']
|
| 1829 |
+
>>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
|
| 1830 |
+
['1.3', '1.5a1']
|
| 1831 |
+
>>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
|
| 1832 |
+
['1.3', '1.5a1']
|
| 1833 |
+
>>> list(SpecifierSet(">=1.2.3").filter(
|
| 1834 |
+
... [{"ver": "1.2"}, {"ver": "1.3"}],
|
| 1835 |
+
... key=lambda x: x["ver"]))
|
| 1836 |
+
[{'ver': '1.3'}]
|
| 1837 |
+
|
| 1838 |
+
An "empty" SpecifierSet will filter items based on the presence of prerelease
|
| 1839 |
+
versions in the set.
|
| 1840 |
+
|
| 1841 |
+
>>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
|
| 1842 |
+
['1.3']
|
| 1843 |
+
>>> list(SpecifierSet("").filter(["1.5a1"]))
|
| 1844 |
+
['1.5a1']
|
| 1845 |
+
>>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
|
| 1846 |
+
['1.3', '1.5a1']
|
| 1847 |
+
>>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
|
| 1848 |
+
['1.3', '1.5a1']
|
| 1849 |
+
"""
|
| 1850 |
+
# Determine if we're forcing a prerelease or not, if we're not forcing
|
| 1851 |
+
# one for this particular filter call, then we'll use whatever the
|
| 1852 |
+
# SpecifierSet thinks for whether or not we should support prereleases.
|
| 1853 |
+
if prereleases is None and self.prereleases is not None:
|
| 1854 |
+
prereleases = self.prereleases
|
| 1855 |
+
|
| 1856 |
+
# Filter versions that match all specifiers using Cost Based Ordering.
|
| 1857 |
+
if self._specs:
|
| 1858 |
+
# When prereleases is None, we need to let all versions through
|
| 1859 |
+
# the individual filters, then decide about prereleases at the end
|
| 1860 |
+
# based on whether any non-prereleases matched ALL specs.
|
| 1861 |
+
|
| 1862 |
+
# Fast path: single specifier, delegate directly.
|
| 1863 |
+
if len(self._specs) == 1:
|
| 1864 |
+
filtered = self._specs[0].filter(
|
| 1865 |
+
iterable,
|
| 1866 |
+
prereleases=True if prereleases is None else prereleases,
|
| 1867 |
+
key=key,
|
| 1868 |
+
)
|
| 1869 |
+
else:
|
| 1870 |
+
filtered = self._filter_versions(
|
| 1871 |
+
iterable,
|
| 1872 |
+
key,
|
| 1873 |
+
prereleases=True if prereleases is None else prereleases,
|
| 1874 |
+
)
|
| 1875 |
+
|
| 1876 |
+
if prereleases is not None:
|
| 1877 |
+
return filtered
|
| 1878 |
+
|
| 1879 |
+
return _pep440_filter_prereleases(filtered, key)
|
| 1880 |
+
|
| 1881 |
+
# Handle Empty SpecifierSet.
|
| 1882 |
+
if prereleases is True:
|
| 1883 |
+
return iter(iterable)
|
| 1884 |
+
|
| 1885 |
+
if prereleases is False:
|
| 1886 |
+
return (
|
| 1887 |
+
item
|
| 1888 |
+
for item in iterable
|
| 1889 |
+
if (
|
| 1890 |
+
(version := _coerce_version(item if key is None else key(item)))
|
| 1891 |
+
is None
|
| 1892 |
+
or not version.is_prerelease
|
| 1893 |
+
)
|
| 1894 |
+
)
|
| 1895 |
+
|
| 1896 |
+
# PEP 440: exclude prereleases unless no final releases matched
|
| 1897 |
+
return _pep440_filter_prereleases(iterable, key)
|
| 1898 |
+
|
| 1899 |
+
def _filter_versions(
|
| 1900 |
+
self,
|
| 1901 |
+
iterable: Iterable[Any],
|
| 1902 |
+
key: Callable[[Any], UnparsedVersion] | None,
|
| 1903 |
+
prereleases: bool | None = None,
|
| 1904 |
+
) -> Iterator[Any]:
|
| 1905 |
+
"""Filter versions against all specifiers in a single pass.
|
| 1906 |
+
|
| 1907 |
+
Uses Cost Based Ordering: specifiers are sorted by _operator_cost so
|
| 1908 |
+
that cheap range operators reject versions early, avoiding expensive
|
| 1909 |
+
wildcard or compatible operators on versions that would have been
|
| 1910 |
+
rejected anyway.
|
| 1911 |
+
"""
|
| 1912 |
+
# Pre-resolve operators and sort (cached after first call).
|
| 1913 |
+
if self._resolved_ops is None:
|
| 1914 |
+
self._resolved_ops = sorted(
|
| 1915 |
+
(
|
| 1916 |
+
(spec._get_operator(spec.operator), spec.version, spec.operator)
|
| 1917 |
+
for spec in self._specs
|
| 1918 |
+
),
|
| 1919 |
+
key=_operator_cost,
|
| 1920 |
+
)
|
| 1921 |
+
ops = self._resolved_ops
|
| 1922 |
+
exclude_prereleases = prereleases is False
|
| 1923 |
+
|
| 1924 |
+
for item in iterable:
|
| 1925 |
+
parsed = _coerce_version(item if key is None else key(item))
|
| 1926 |
+
|
| 1927 |
+
if parsed is None:
|
| 1928 |
+
# Only === can match non-parseable versions.
|
| 1929 |
+
if all(
|
| 1930 |
+
op == "===" and str(item).lower() == ver.lower()
|
| 1931 |
+
for _, ver, op in ops
|
| 1932 |
+
):
|
| 1933 |
+
yield item
|
| 1934 |
+
elif exclude_prereleases and parsed.is_prerelease:
|
| 1935 |
+
pass
|
| 1936 |
+
elif all(
|
| 1937 |
+
str(item if key is None else key(item)).lower() == ver.lower()
|
| 1938 |
+
if op == "==="
|
| 1939 |
+
else op_fn(parsed, ver)
|
| 1940 |
+
for op_fn, ver, op in ops
|
| 1941 |
+
):
|
| 1942 |
+
# Short-circuits on the first failing operator.
|
| 1943 |
+
yield item
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/tags.py
ADDED
|
@@ -0,0 +1,932 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import operator
|
| 9 |
+
import platform
|
| 10 |
+
import re
|
| 11 |
+
import struct
|
| 12 |
+
import subprocess
|
| 13 |
+
import sys
|
| 14 |
+
import sysconfig
|
| 15 |
+
from importlib.machinery import EXTENSION_SUFFIXES
|
| 16 |
+
from typing import (
|
| 17 |
+
TYPE_CHECKING,
|
| 18 |
+
Iterable,
|
| 19 |
+
Iterator,
|
| 20 |
+
Sequence,
|
| 21 |
+
Tuple,
|
| 22 |
+
TypeVar,
|
| 23 |
+
cast,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
from . import _manylinux, _musllinux
|
| 27 |
+
|
| 28 |
+
if TYPE_CHECKING:
|
| 29 |
+
from collections.abc import Callable, Iterable
|
| 30 |
+
from typing import AbstractSet
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"INTERPRETER_SHORT_NAMES",
|
| 35 |
+
"AppleVersion",
|
| 36 |
+
"PythonVersion",
|
| 37 |
+
"Tag",
|
| 38 |
+
"UnsortedTagsError",
|
| 39 |
+
"android_platforms",
|
| 40 |
+
"compatible_tags",
|
| 41 |
+
"cpython_tags",
|
| 42 |
+
"create_compatible_tags_selector",
|
| 43 |
+
"generic_tags",
|
| 44 |
+
"interpreter_name",
|
| 45 |
+
"interpreter_version",
|
| 46 |
+
"ios_platforms",
|
| 47 |
+
"mac_platforms",
|
| 48 |
+
"parse_tag",
|
| 49 |
+
"platform_tags",
|
| 50 |
+
"sys_tags",
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def __dir__() -> list[str]:
|
| 55 |
+
return __all__
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
logger = logging.getLogger(__name__)
|
| 59 |
+
|
| 60 |
+
PythonVersion = Sequence[int]
|
| 61 |
+
AppleVersion = Tuple[int, int]
|
| 62 |
+
_T = TypeVar("_T")
|
| 63 |
+
|
| 64 |
+
INTERPRETER_SHORT_NAMES: dict[str, str] = {
|
| 65 |
+
"python": "py", # Generic.
|
| 66 |
+
"cpython": "cp",
|
| 67 |
+
"pypy": "pp",
|
| 68 |
+
"ironpython": "ip",
|
| 69 |
+
"jython": "jy",
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# This function can be unit tested without reloading the module
|
| 74 |
+
# (Unlike _32_BIT_INTERPRETER)
|
| 75 |
+
def _compute_32_bit_interpreter() -> bool:
|
| 76 |
+
return struct.calcsize("P") == 4
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
_32_BIT_INTERPRETER = _compute_32_bit_interpreter()
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class UnsortedTagsError(ValueError):
|
| 83 |
+
"""
|
| 84 |
+
Raised when a tag component is not in sorted order per PEP 425.
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class Tag:
|
| 89 |
+
"""
|
| 90 |
+
A representation of the tag triple for a wheel.
|
| 91 |
+
|
| 92 |
+
Instances are considered immutable and thus are hashable. Equality checking
|
| 93 |
+
is also supported.
|
| 94 |
+
|
| 95 |
+
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
| 96 |
+
format so the same pickle can be loaded in future packaging releases.
|
| 97 |
+
|
| 98 |
+
.. versionchanged:: 26.2
|
| 99 |
+
|
| 100 |
+
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
| 101 |
+
be unpickled with future releases. Backward compatibility with pickles
|
| 102 |
+
from packaging < 26.2 is supported but may be removed in a future
|
| 103 |
+
release.
|
| 104 |
+
"""
|
| 105 |
+
|
| 106 |
+
__slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
|
| 107 |
+
|
| 108 |
+
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
|
| 109 |
+
"""
|
| 110 |
+
:param str interpreter: The interpreter name, e.g. ``"py"``
|
| 111 |
+
(see :attr:`INTERPRETER_SHORT_NAMES` for mapping
|
| 112 |
+
well-known interpreter names to their short names).
|
| 113 |
+
:param str abi: The ABI that a wheel supports, e.g. ``"cp37m"``.
|
| 114 |
+
:param str platform: The OS/platform the wheel supports,
|
| 115 |
+
e.g. ``"win_amd64"``.
|
| 116 |
+
"""
|
| 117 |
+
self._interpreter = interpreter.lower()
|
| 118 |
+
self._abi = abi.lower()
|
| 119 |
+
self._platform = platform.lower()
|
| 120 |
+
# The __hash__ of every single element in a Set[Tag] will be evaluated each time
|
| 121 |
+
# that a set calls its `.disjoint()` method, which may be called hundreds of
|
| 122 |
+
# times when scanning a page of links for packages with tags matching that
|
| 123 |
+
# Set[Tag]. Pre-computing the value here produces significant speedups for
|
| 124 |
+
# downstream consumers.
|
| 125 |
+
self._hash = hash((self._interpreter, self._abi, self._platform))
|
| 126 |
+
|
| 127 |
+
@property
|
| 128 |
+
def interpreter(self) -> str:
|
| 129 |
+
"""
|
| 130 |
+
The interpreter name, e.g. ``"py"`` (see
|
| 131 |
+
:attr:`INTERPRETER_SHORT_NAMES` for mapping well-known interpreter
|
| 132 |
+
names to their short names).
|
| 133 |
+
"""
|
| 134 |
+
return self._interpreter
|
| 135 |
+
|
| 136 |
+
@property
|
| 137 |
+
def abi(self) -> str:
|
| 138 |
+
"""
|
| 139 |
+
The supported ABI.
|
| 140 |
+
"""
|
| 141 |
+
return self._abi
|
| 142 |
+
|
| 143 |
+
@property
|
| 144 |
+
def platform(self) -> str:
|
| 145 |
+
"""
|
| 146 |
+
The OS/platform.
|
| 147 |
+
"""
|
| 148 |
+
return self._platform
|
| 149 |
+
|
| 150 |
+
def __eq__(self, other: object) -> bool:
|
| 151 |
+
if not isinstance(other, Tag):
|
| 152 |
+
return NotImplemented
|
| 153 |
+
|
| 154 |
+
return (
|
| 155 |
+
(self._hash == other._hash) # Short-circuit ASAP for perf reasons.
|
| 156 |
+
and (self._platform == other._platform)
|
| 157 |
+
and (self._abi == other._abi)
|
| 158 |
+
and (self._interpreter == other._interpreter)
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
def __hash__(self) -> int:
|
| 162 |
+
return self._hash
|
| 163 |
+
|
| 164 |
+
def __str__(self) -> str:
|
| 165 |
+
return f"{self._interpreter}-{self._abi}-{self._platform}"
|
| 166 |
+
|
| 167 |
+
def __repr__(self) -> str:
|
| 168 |
+
return f"<{self} @ {id(self)}>"
|
| 169 |
+
|
| 170 |
+
def __getstate__(self) -> tuple[str, str, str]:
|
| 171 |
+
# Return state as a 3-item tuple: (interpreter, abi, platform).
|
| 172 |
+
# Cache member _hash is excluded and will be recomputed.
|
| 173 |
+
return (self._interpreter, self._abi, self._platform)
|
| 174 |
+
|
| 175 |
+
def __setstate__(self, state: object) -> None:
|
| 176 |
+
if isinstance(state, tuple):
|
| 177 |
+
if len(state) == 3 and all(isinstance(s, str) for s in state):
|
| 178 |
+
# New format (26.2+): (interpreter, abi, platform)
|
| 179 |
+
self._interpreter, self._abi, self._platform = state
|
| 180 |
+
self._hash = hash((self._interpreter, self._abi, self._platform))
|
| 181 |
+
return
|
| 182 |
+
if len(state) == 2 and isinstance(state[1], dict):
|
| 183 |
+
# Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
|
| 184 |
+
_, slots = state
|
| 185 |
+
try:
|
| 186 |
+
interpreter = slots["_interpreter"]
|
| 187 |
+
abi = slots["_abi"]
|
| 188 |
+
platform = slots["_platform"]
|
| 189 |
+
except KeyError:
|
| 190 |
+
raise TypeError(f"Cannot restore Tag from {state!r}") from None
|
| 191 |
+
if not all(
|
| 192 |
+
isinstance(value, str) for value in (interpreter, abi, platform)
|
| 193 |
+
):
|
| 194 |
+
raise TypeError(f"Cannot restore Tag from {state!r}")
|
| 195 |
+
self._interpreter = interpreter.lower()
|
| 196 |
+
self._abi = abi.lower()
|
| 197 |
+
self._platform = platform.lower()
|
| 198 |
+
self._hash = hash((self._interpreter, self._abi, self._platform))
|
| 199 |
+
return
|
| 200 |
+
raise TypeError(f"Cannot restore Tag from {state!r}")
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]:
|
| 204 |
+
"""
|
| 205 |
+
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of
|
| 206 |
+
:class:`Tag` instances.
|
| 207 |
+
|
| 208 |
+
Returning a set is required due to the possibility that the tag is a
|
| 209 |
+
`compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both
|
| 210 |
+
Python 2 and Python 3.
|
| 211 |
+
|
| 212 |
+
If **validate_order** is true, compressed tag set components are checked
|
| 213 |
+
to be in sorted order as required by PEP 425.
|
| 214 |
+
|
| 215 |
+
:param str tag: The tag to parse, e.g. ``"py3-none-any"``.
|
| 216 |
+
:param bool validate_order: Check whether compressed tag set components
|
| 217 |
+
are in sorted order.
|
| 218 |
+
:raises UnsortedTagsError: If **validate_order** is true and any compressed tag
|
| 219 |
+
set component is not in sorted order.
|
| 220 |
+
|
| 221 |
+
.. versionadded:: 26.1
|
| 222 |
+
The *validate_order* parameter.
|
| 223 |
+
"""
|
| 224 |
+
tags = set()
|
| 225 |
+
interpreters, abis, platforms = tag.split("-")
|
| 226 |
+
if validate_order:
|
| 227 |
+
for component in (interpreters, abis, platforms):
|
| 228 |
+
parts = component.split(".")
|
| 229 |
+
if parts != sorted(parts):
|
| 230 |
+
raise UnsortedTagsError(
|
| 231 |
+
f"Tag component {component!r} is not in sorted order per PEP 425"
|
| 232 |
+
)
|
| 233 |
+
for interpreter in interpreters.split("."):
|
| 234 |
+
for abi in abis.split("."):
|
| 235 |
+
for platform_ in platforms.split("."):
|
| 236 |
+
tags.add(Tag(interpreter, abi, platform_))
|
| 237 |
+
return frozenset(tags)
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def _get_config_var(name: str, warn: bool = False) -> int | str | None:
|
| 241 |
+
value: int | str | None = sysconfig.get_config_var(name)
|
| 242 |
+
if value is None and warn:
|
| 243 |
+
logger.debug(
|
| 244 |
+
"Config variable '%s' is unset, Python ABI tag may be incorrect", name
|
| 245 |
+
)
|
| 246 |
+
return value
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def _normalize_string(string: str) -> str:
|
| 250 |
+
return string.replace(".", "_").replace("-", "_").replace(" ", "_")
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def _is_threaded_cpython(abis: list[str]) -> bool:
|
| 254 |
+
"""
|
| 255 |
+
Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
|
| 256 |
+
|
| 257 |
+
The threaded builds are indicated by a "t" in the abiflags.
|
| 258 |
+
"""
|
| 259 |
+
if len(abis) == 0:
|
| 260 |
+
return False
|
| 261 |
+
# expect e.g., cp313
|
| 262 |
+
m = re.match(r"cp\d+(.*)", abis[0])
|
| 263 |
+
if not m:
|
| 264 |
+
return False
|
| 265 |
+
abiflags = m.group(1)
|
| 266 |
+
return "t" in abiflags
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
|
| 270 |
+
"""
|
| 271 |
+
Determine if the Python version supports abi3.
|
| 272 |
+
|
| 273 |
+
PEP 384 was first implemented in Python 3.2. The free-threaded
|
| 274 |
+
builds do not support abi3.
|
| 275 |
+
"""
|
| 276 |
+
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def _abi3t_applies(python_version: PythonVersion, threading: bool) -> bool:
|
| 280 |
+
"""
|
| 281 |
+
Determine if the Python version supports abi3t.
|
| 282 |
+
|
| 283 |
+
PEP 803 was first implemented in Python 3.15 but, per PEP 803, this
|
| 284 |
+
returns tags going back to Python 3.2 to mirror the abi3
|
| 285 |
+
implementation and leave open the possibility of abi3t wheels
|
| 286 |
+
supporting older Python versions.
|
| 287 |
+
|
| 288 |
+
"""
|
| 289 |
+
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and threading
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
|
| 293 |
+
py_version = tuple(py_version) # To allow for version comparison.
|
| 294 |
+
abis = []
|
| 295 |
+
version = _version_nodot(py_version[:2])
|
| 296 |
+
threading = debug = pymalloc = ucs4 = ""
|
| 297 |
+
with_debug = _get_config_var("Py_DEBUG", warn)
|
| 298 |
+
has_refcount = hasattr(sys, "gettotalrefcount")
|
| 299 |
+
# Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
|
| 300 |
+
# extension modules is the best option.
|
| 301 |
+
# https://github.com/pypa/pip/issues/3383#issuecomment-173267692
|
| 302 |
+
has_ext = "_d.pyd" in EXTENSION_SUFFIXES
|
| 303 |
+
if with_debug or (with_debug is None and (has_refcount or has_ext)):
|
| 304 |
+
debug = "d"
|
| 305 |
+
if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
|
| 306 |
+
threading = "t"
|
| 307 |
+
if py_version < (3, 8):
|
| 308 |
+
with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
|
| 309 |
+
if with_pymalloc or with_pymalloc is None:
|
| 310 |
+
pymalloc = "m"
|
| 311 |
+
if py_version < (3, 3):
|
| 312 |
+
unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
|
| 313 |
+
if unicode_size == 4 or (
|
| 314 |
+
unicode_size is None and sys.maxunicode == 0x10FFFF
|
| 315 |
+
):
|
| 316 |
+
ucs4 = "u"
|
| 317 |
+
elif debug:
|
| 318 |
+
# Debug builds can also load "normal" extension modules.
|
| 319 |
+
# We can also assume no UCS-4 or pymalloc requirement.
|
| 320 |
+
abis.append(f"cp{version}{threading}")
|
| 321 |
+
abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
|
| 322 |
+
return abis
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def cpython_tags(
|
| 326 |
+
python_version: PythonVersion | None = None,
|
| 327 |
+
abis: Iterable[str] | None = None,
|
| 328 |
+
platforms: Iterable[str] | None = None,
|
| 329 |
+
*,
|
| 330 |
+
warn: bool = False,
|
| 331 |
+
) -> Iterator[Tag]:
|
| 332 |
+
"""
|
| 333 |
+
Yields the tags for the CPython interpreter.
|
| 334 |
+
|
| 335 |
+
The specific tags generated are:
|
| 336 |
+
|
| 337 |
+
- ``cp<python_version>-<abi>-<platform>``
|
| 338 |
+
- ``cp<python_version>-<stable_abi>-<platform>``
|
| 339 |
+
- ``cp<python_version>-none-<platform>``
|
| 340 |
+
- ``cp<older version>-<stable_abi>-<platform>`` where "older version" is all older
|
| 341 |
+
minor versions down to Python 3.2 (when ``abi3`` was introduced)
|
| 342 |
+
|
| 343 |
+
If ``python_version`` only provides a major-only version then only
|
| 344 |
+
user-provided ABIs via ``abis`` and the ``none`` ABI will be used.
|
| 345 |
+
|
| 346 |
+
The ``stable_abi`` will be either ``abi3`` or ``abi3t`` if `abi` is a
|
| 347 |
+
GIL-enabled ABI like `"cp315"` or a free-threaded ABI like `"cp315t"`,
|
| 348 |
+
respectively.
|
| 349 |
+
|
| 350 |
+
:param Sequence python_version: A one- or two-item sequence representing the
|
| 351 |
+
targeted Python version. Defaults to
|
| 352 |
+
``sys.version_info[:2]``.
|
| 353 |
+
:param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
|
| 354 |
+
compatible with the current system.
|
| 355 |
+
:param Iterable platforms: Iterable of compatible platforms. Defaults to the
|
| 356 |
+
platforms compatible with the current system.
|
| 357 |
+
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
| 358 |
+
"""
|
| 359 |
+
if not python_version:
|
| 360 |
+
python_version = sys.version_info[:2]
|
| 361 |
+
|
| 362 |
+
interpreter = f"cp{_version_nodot(python_version[:2])}"
|
| 363 |
+
|
| 364 |
+
if abis is None:
|
| 365 |
+
abis = _cpython_abis(python_version, warn) if len(python_version) > 1 else []
|
| 366 |
+
abis = list(abis)
|
| 367 |
+
# 'abi3' and 'none' are explicitly handled later.
|
| 368 |
+
for explicit_abi in ("abi3", "none"):
|
| 369 |
+
try:
|
| 370 |
+
abis.remove(explicit_abi)
|
| 371 |
+
except ValueError: # noqa: PERF203
|
| 372 |
+
pass
|
| 373 |
+
|
| 374 |
+
platforms = list(platforms or platform_tags())
|
| 375 |
+
for abi in abis:
|
| 376 |
+
for platform_ in platforms:
|
| 377 |
+
yield Tag(interpreter, abi, platform_)
|
| 378 |
+
|
| 379 |
+
threading = _is_threaded_cpython(abis)
|
| 380 |
+
use_abi3 = _abi3_applies(python_version, threading)
|
| 381 |
+
use_abi3t = _abi3t_applies(python_version, threading)
|
| 382 |
+
|
| 383 |
+
if use_abi3:
|
| 384 |
+
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
| 385 |
+
if use_abi3t:
|
| 386 |
+
yield from (Tag(interpreter, "abi3t", platform_) for platform_ in platforms)
|
| 387 |
+
|
| 388 |
+
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
| 389 |
+
|
| 390 |
+
if use_abi3 or use_abi3t:
|
| 391 |
+
for minor_version in range(python_version[1] - 1, 1, -1):
|
| 392 |
+
for platform_ in platforms:
|
| 393 |
+
version = _version_nodot((python_version[0], minor_version))
|
| 394 |
+
interpreter = f"cp{version}"
|
| 395 |
+
if use_abi3:
|
| 396 |
+
yield Tag(interpreter, "abi3", platform_)
|
| 397 |
+
if use_abi3t:
|
| 398 |
+
# Support for abi3t was introduced in Python 3.15, but in
|
| 399 |
+
# principle abi3t wheels are possible for older limited API
|
| 400 |
+
# versions, so allow things like ("cp37", "abi3t", "platform")
|
| 401 |
+
yield Tag(interpreter, "abi3t", platform_)
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def _generic_abi() -> list[str]:
|
| 405 |
+
"""
|
| 406 |
+
Return the ABI tag based on EXT_SUFFIX.
|
| 407 |
+
"""
|
| 408 |
+
# The following are examples of `EXT_SUFFIX`.
|
| 409 |
+
# We want to keep the parts which are related to the ABI and remove the
|
| 410 |
+
# parts which are related to the platform:
|
| 411 |
+
# - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310
|
| 412 |
+
# - mac: '.cpython-310-darwin.so' => cp310
|
| 413 |
+
# - win: '.cp310-win_amd64.pyd' => cp310
|
| 414 |
+
# - win: '.pyd' => cp37 (uses _cpython_abis())
|
| 415 |
+
# - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
|
| 416 |
+
# - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
|
| 417 |
+
# => graalpy_38_native
|
| 418 |
+
|
| 419 |
+
ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
|
| 420 |
+
if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
|
| 421 |
+
raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
|
| 422 |
+
parts = ext_suffix.split(".")
|
| 423 |
+
if len(parts) < 3:
|
| 424 |
+
# CPython3.7 and earlier uses ".pyd" on Windows.
|
| 425 |
+
return _cpython_abis(sys.version_info[:2])
|
| 426 |
+
soabi = parts[1]
|
| 427 |
+
if soabi.startswith("cpython"):
|
| 428 |
+
# non-windows
|
| 429 |
+
abi = "cp" + soabi.split("-")[1]
|
| 430 |
+
elif soabi.startswith("cp"):
|
| 431 |
+
# windows
|
| 432 |
+
abi = soabi.split("-")[0]
|
| 433 |
+
elif soabi.startswith("pypy"):
|
| 434 |
+
abi = "-".join(soabi.split("-")[:2])
|
| 435 |
+
elif soabi.startswith("graalpy"):
|
| 436 |
+
abi = "-".join(soabi.split("-")[:3])
|
| 437 |
+
elif soabi:
|
| 438 |
+
# pyston, ironpython, others?
|
| 439 |
+
abi = soabi
|
| 440 |
+
else:
|
| 441 |
+
return []
|
| 442 |
+
return [_normalize_string(abi)]
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def generic_tags(
|
| 446 |
+
interpreter: str | None = None,
|
| 447 |
+
abis: Iterable[str] | None = None,
|
| 448 |
+
platforms: Iterable[str] | None = None,
|
| 449 |
+
*,
|
| 450 |
+
warn: bool = False,
|
| 451 |
+
) -> Iterator[Tag]:
|
| 452 |
+
"""
|
| 453 |
+
Yields the tags for an interpreter which requires no specialization.
|
| 454 |
+
|
| 455 |
+
This function should be used if one of the other interpreter-specific
|
| 456 |
+
functions provided by this module is not appropriate (i.e. not calculating
|
| 457 |
+
tags for a CPython interpreter).
|
| 458 |
+
|
| 459 |
+
The specific tags generated are:
|
| 460 |
+
|
| 461 |
+
- ``<interpreter>-<abi>-<platform>``
|
| 462 |
+
|
| 463 |
+
The ``"none"`` ABI will be added if it was not explicitly provided.
|
| 464 |
+
|
| 465 |
+
:param str interpreter: The name of the interpreter. Defaults to being
|
| 466 |
+
calculated.
|
| 467 |
+
:param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
|
| 468 |
+
compatible with the current system.
|
| 469 |
+
:param Iterable platforms: Iterable of compatible platforms. Defaults to the
|
| 470 |
+
platforms compatible with the current system.
|
| 471 |
+
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
| 472 |
+
"""
|
| 473 |
+
if not interpreter:
|
| 474 |
+
interp_name = interpreter_name()
|
| 475 |
+
interp_version = interpreter_version(warn=warn)
|
| 476 |
+
interpreter = f"{interp_name}{interp_version}"
|
| 477 |
+
abis = _generic_abi() if abis is None else list(abis)
|
| 478 |
+
platforms = list(platforms or platform_tags())
|
| 479 |
+
if "none" not in abis:
|
| 480 |
+
abis.append("none")
|
| 481 |
+
for abi in abis:
|
| 482 |
+
for platform_ in platforms:
|
| 483 |
+
yield Tag(interpreter, abi, platform_)
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
|
| 487 |
+
"""
|
| 488 |
+
Yields Python versions in descending order.
|
| 489 |
+
|
| 490 |
+
After the latest version, the major-only version will be yielded, and then
|
| 491 |
+
all previous versions of that major version.
|
| 492 |
+
"""
|
| 493 |
+
if len(py_version) > 1:
|
| 494 |
+
yield f"py{_version_nodot(py_version[:2])}"
|
| 495 |
+
yield f"py{py_version[0]}"
|
| 496 |
+
if len(py_version) > 1:
|
| 497 |
+
for minor in range(py_version[1] - 1, -1, -1):
|
| 498 |
+
yield f"py{_version_nodot((py_version[0], minor))}"
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def compatible_tags(
|
| 502 |
+
python_version: PythonVersion | None = None,
|
| 503 |
+
interpreter: str | None = None,
|
| 504 |
+
platforms: Iterable[str] | None = None,
|
| 505 |
+
) -> Iterator[Tag]:
|
| 506 |
+
"""
|
| 507 |
+
Yields the tags for an interpreter compatible with the Python version
|
| 508 |
+
specified by ``python_version``.
|
| 509 |
+
|
| 510 |
+
The specific tags generated are:
|
| 511 |
+
|
| 512 |
+
- ``py*-none-<platform>``
|
| 513 |
+
- ``<interpreter>-none-any`` if ``interpreter`` is provided
|
| 514 |
+
- ``py*-none-any``
|
| 515 |
+
|
| 516 |
+
:param Sequence python_version: A one- or two-item sequence representing the
|
| 517 |
+
compatible version of Python. Defaults to
|
| 518 |
+
``sys.version_info[:2]``.
|
| 519 |
+
:param str interpreter: The name of the interpreter (if known), e.g.
|
| 520 |
+
``"cp38"``. Defaults to the current interpreter.
|
| 521 |
+
:param Iterable platforms: Iterable of compatible platforms. Defaults to the
|
| 522 |
+
platforms compatible with the current system.
|
| 523 |
+
"""
|
| 524 |
+
if not python_version:
|
| 525 |
+
python_version = sys.version_info[:2]
|
| 526 |
+
platforms = list(platforms or platform_tags())
|
| 527 |
+
for version in _py_interpreter_range(python_version):
|
| 528 |
+
for platform_ in platforms:
|
| 529 |
+
yield Tag(version, "none", platform_)
|
| 530 |
+
if interpreter:
|
| 531 |
+
yield Tag(interpreter, "none", "any")
|
| 532 |
+
for version in _py_interpreter_range(python_version):
|
| 533 |
+
yield Tag(version, "none", "any")
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
|
| 537 |
+
if not is_32bit:
|
| 538 |
+
return arch
|
| 539 |
+
|
| 540 |
+
if arch.startswith("ppc"):
|
| 541 |
+
return "ppc"
|
| 542 |
+
|
| 543 |
+
return "i386"
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
|
| 547 |
+
formats = [cpu_arch]
|
| 548 |
+
if cpu_arch == "x86_64":
|
| 549 |
+
if version < (10, 4):
|
| 550 |
+
return []
|
| 551 |
+
formats.extend(["intel", "fat64", "fat32"])
|
| 552 |
+
|
| 553 |
+
elif cpu_arch == "i386":
|
| 554 |
+
if version < (10, 4):
|
| 555 |
+
return []
|
| 556 |
+
formats.extend(["intel", "fat32", "fat"])
|
| 557 |
+
|
| 558 |
+
elif cpu_arch == "ppc64":
|
| 559 |
+
# TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
|
| 560 |
+
if version > (10, 5) or version < (10, 4):
|
| 561 |
+
return []
|
| 562 |
+
formats.append("fat64")
|
| 563 |
+
|
| 564 |
+
elif cpu_arch == "ppc":
|
| 565 |
+
if version > (10, 6):
|
| 566 |
+
return []
|
| 567 |
+
formats.extend(["fat32", "fat"])
|
| 568 |
+
|
| 569 |
+
if cpu_arch in {"arm64", "x86_64"}:
|
| 570 |
+
formats.append("universal2")
|
| 571 |
+
|
| 572 |
+
if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
|
| 573 |
+
formats.append("universal")
|
| 574 |
+
|
| 575 |
+
return formats
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
def mac_platforms(
|
| 579 |
+
version: AppleVersion | None = None, arch: str | None = None
|
| 580 |
+
) -> Iterator[str]:
|
| 581 |
+
"""
|
| 582 |
+
Yields the :attr:`~Tag.platform` tags for macOS.
|
| 583 |
+
|
| 584 |
+
The `version` parameter is a two-item tuple specifying the macOS version to
|
| 585 |
+
generate platform tags for. The `arch` parameter is the CPU architecture to
|
| 586 |
+
generate platform tags for. Both parameters default to the appropriate value
|
| 587 |
+
for the current system.
|
| 588 |
+
|
| 589 |
+
:param tuple version: A two-item tuple representing the version of macOS.
|
| 590 |
+
Defaults to the current system's version.
|
| 591 |
+
:param str arch: The CPU architecture. Defaults to the architecture of the
|
| 592 |
+
current system, e.g. ``"x86_64"``.
|
| 593 |
+
|
| 594 |
+
.. note::
|
| 595 |
+
Equivalent support for the other major platforms is purposefully not
|
| 596 |
+
provided:
|
| 597 |
+
|
| 598 |
+
- On Windows, platform compatibility is statically specified
|
| 599 |
+
- On Linux, code must be run on the system itself to determine
|
| 600 |
+
compatibility
|
| 601 |
+
"""
|
| 602 |
+
version_str, _, cpu_arch = platform.mac_ver()
|
| 603 |
+
if version is None:
|
| 604 |
+
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
| 605 |
+
if version == (10, 16):
|
| 606 |
+
# When built against an older macOS SDK, Python will report macOS 10.16
|
| 607 |
+
# instead of the real version.
|
| 608 |
+
version_str = subprocess.run(
|
| 609 |
+
[
|
| 610 |
+
sys.executable,
|
| 611 |
+
"-sS",
|
| 612 |
+
"-c",
|
| 613 |
+
"import platform; print(platform.mac_ver()[0])",
|
| 614 |
+
],
|
| 615 |
+
check=True,
|
| 616 |
+
env={"SYSTEM_VERSION_COMPAT": "0"},
|
| 617 |
+
stdout=subprocess.PIPE,
|
| 618 |
+
text=True,
|
| 619 |
+
).stdout
|
| 620 |
+
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
| 621 |
+
|
| 622 |
+
if arch is None:
|
| 623 |
+
arch = _mac_arch(cpu_arch)
|
| 624 |
+
|
| 625 |
+
if (10, 0) <= version < (11, 0):
|
| 626 |
+
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
|
| 627 |
+
# "minor" version number. The major version was always 10.
|
| 628 |
+
major_version = 10
|
| 629 |
+
for minor_version in range(version[1], -1, -1):
|
| 630 |
+
compat_version = major_version, minor_version
|
| 631 |
+
binary_formats = _mac_binary_formats(compat_version, arch)
|
| 632 |
+
for binary_format in binary_formats:
|
| 633 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 634 |
+
|
| 635 |
+
if version >= (11, 0):
|
| 636 |
+
# Starting with Mac OS 11, each yearly release bumps the major version
|
| 637 |
+
# number. The minor versions are now the midyear updates.
|
| 638 |
+
minor_version = 0
|
| 639 |
+
for major_version in range(version[0], 10, -1):
|
| 640 |
+
compat_version = major_version, minor_version
|
| 641 |
+
binary_formats = _mac_binary_formats(compat_version, arch)
|
| 642 |
+
for binary_format in binary_formats:
|
| 643 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 644 |
+
|
| 645 |
+
if version >= (11, 0):
|
| 646 |
+
# Mac OS 11 on x86_64 is compatible with binaries from previous releases.
|
| 647 |
+
# Arm64 support was introduced in 11.0, so no Arm binaries from previous
|
| 648 |
+
# releases exist.
|
| 649 |
+
#
|
| 650 |
+
# However, the "universal2" binary format can have a
|
| 651 |
+
# macOS version earlier than 11.0 when the x86_64 part of the binary supports
|
| 652 |
+
# that version of macOS.
|
| 653 |
+
major_version = 10
|
| 654 |
+
if arch == "x86_64":
|
| 655 |
+
for minor_version in range(16, 3, -1):
|
| 656 |
+
compat_version = major_version, minor_version
|
| 657 |
+
binary_formats = _mac_binary_formats(compat_version, arch)
|
| 658 |
+
for binary_format in binary_formats:
|
| 659 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 660 |
+
else:
|
| 661 |
+
for minor_version in range(16, 3, -1):
|
| 662 |
+
compat_version = major_version, minor_version
|
| 663 |
+
binary_format = "universal2"
|
| 664 |
+
yield f"macosx_{major_version}_{minor_version}_{binary_format}"
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
def ios_platforms(
|
| 668 |
+
version: AppleVersion | None = None, multiarch: str | None = None
|
| 669 |
+
) -> Iterator[str]:
|
| 670 |
+
"""
|
| 671 |
+
|
| 672 |
+
Yields the :attr:`~Tag.platform` tags for iOS.
|
| 673 |
+
|
| 674 |
+
:param tuple version: A two-item tuple representing the version of iOS.
|
| 675 |
+
Defaults to the current system's version.
|
| 676 |
+
:param str multiarch: The CPU architecture+ABI to be used. This should be in
|
| 677 |
+
the format by ``sys.implementation._multiarch`` (e.g.,
|
| 678 |
+
``arm64_iphoneos`` or ``x86_64_iphonesimulator``).
|
| 679 |
+
Defaults to the current system's multiarch value.
|
| 680 |
+
|
| 681 |
+
.. note::
|
| 682 |
+
Behavior of this method is undefined if invoked on non-iOS platforms
|
| 683 |
+
without providing explicit version and multiarch arguments.
|
| 684 |
+
"""
|
| 685 |
+
if version is None:
|
| 686 |
+
# if iOS is the current platform, ios_ver *must* be defined. However,
|
| 687 |
+
# it won't exist for CPython versions before 3.13, which causes a mypy
|
| 688 |
+
# error.
|
| 689 |
+
_, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore]
|
| 690 |
+
version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
|
| 691 |
+
|
| 692 |
+
if multiarch is None:
|
| 693 |
+
multiarch = sys.implementation._multiarch
|
| 694 |
+
multiarch = multiarch.replace("-", "_")
|
| 695 |
+
|
| 696 |
+
ios_platform_template = "ios_{major}_{minor}_{multiarch}"
|
| 697 |
+
|
| 698 |
+
# Consider any iOS major.minor version from the version requested, down to
|
| 699 |
+
# 12.0. 12.0 is the first iOS version that is known to have enough features
|
| 700 |
+
# to support CPython. Consider every possible minor release up to X.9. There
|
| 701 |
+
# highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
|
| 702 |
+
# candidates that won't ever match doesn't really hurt, and it saves us from
|
| 703 |
+
# having to keep an explicit list of known iOS versions in the code. Return
|
| 704 |
+
# the results descending order of version number.
|
| 705 |
+
|
| 706 |
+
# If the requested major version is less than 12, there won't be any matches.
|
| 707 |
+
if version[0] < 12:
|
| 708 |
+
return
|
| 709 |
+
|
| 710 |
+
# Consider the actual X.Y version that was requested.
|
| 711 |
+
yield ios_platform_template.format(
|
| 712 |
+
major=version[0], minor=version[1], multiarch=multiarch
|
| 713 |
+
)
|
| 714 |
+
|
| 715 |
+
# Consider every minor version from X.0 to the minor version prior to the
|
| 716 |
+
# version requested by the platform.
|
| 717 |
+
for minor in range(version[1] - 1, -1, -1):
|
| 718 |
+
yield ios_platform_template.format(
|
| 719 |
+
major=version[0], minor=minor, multiarch=multiarch
|
| 720 |
+
)
|
| 721 |
+
|
| 722 |
+
for major in range(version[0] - 1, 11, -1):
|
| 723 |
+
for minor in range(9, -1, -1):
|
| 724 |
+
yield ios_platform_template.format(
|
| 725 |
+
major=major, minor=minor, multiarch=multiarch
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
def android_platforms(
|
| 730 |
+
api_level: int | None = None, abi: str | None = None
|
| 731 |
+
) -> Iterator[str]:
|
| 732 |
+
"""
|
| 733 |
+
Yields the :attr:`~Tag.platform` tags for Android. If this function is invoked on
|
| 734 |
+
non-Android platforms, the ``api_level`` and ``abi`` arguments are required.
|
| 735 |
+
|
| 736 |
+
:param int api_level: The maximum `API level
|
| 737 |
+
<https://developer.android.com/tools/releases/platforms>`__ to return. Defaults
|
| 738 |
+
to the current system's version, as returned by ``platform.android_ver``.
|
| 739 |
+
:param str abi: The `Android ABI <https://developer.android.com/ndk/guides/abis>`__,
|
| 740 |
+
e.g. ``arm64_v8a``. Defaults to the current system's ABI , as returned by
|
| 741 |
+
``sysconfig.get_platform``. Hyphens and periods will be replaced with
|
| 742 |
+
underscores.
|
| 743 |
+
"""
|
| 744 |
+
if platform.system() != "Android" and (api_level is None or abi is None):
|
| 745 |
+
raise TypeError(
|
| 746 |
+
"on non-Android platforms, the api_level and abi arguments are required"
|
| 747 |
+
)
|
| 748 |
+
|
| 749 |
+
if api_level is None:
|
| 750 |
+
# Python 3.13 was the first version to return platform.system() == "Android",
|
| 751 |
+
# and also the first version to define platform.android_ver().
|
| 752 |
+
api_level = platform.android_ver().api_level # type: ignore[attr-defined]
|
| 753 |
+
|
| 754 |
+
if abi is None:
|
| 755 |
+
abi = sysconfig.get_platform().split("-")[-1]
|
| 756 |
+
abi = _normalize_string(abi)
|
| 757 |
+
|
| 758 |
+
# 16 is the minimum API level known to have enough features to support CPython
|
| 759 |
+
# without major patching. Yield every API level from the maximum down to the
|
| 760 |
+
# minimum, inclusive.
|
| 761 |
+
min_api_level = 16
|
| 762 |
+
for ver in range(api_level, min_api_level - 1, -1):
|
| 763 |
+
yield f"android_{ver}_{abi}"
|
| 764 |
+
|
| 765 |
+
|
| 766 |
+
def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
|
| 767 |
+
linux = _normalize_string(sysconfig.get_platform())
|
| 768 |
+
if not linux.startswith("linux_"):
|
| 769 |
+
# we should never be here, just yield the sysconfig one and return
|
| 770 |
+
yield linux
|
| 771 |
+
return
|
| 772 |
+
if is_32bit:
|
| 773 |
+
if linux == "linux_x86_64":
|
| 774 |
+
linux = "linux_i686"
|
| 775 |
+
elif linux == "linux_aarch64":
|
| 776 |
+
linux = "linux_armv8l"
|
| 777 |
+
_, arch = linux.split("_", 1)
|
| 778 |
+
archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
|
| 779 |
+
yield from _manylinux.platform_tags(archs)
|
| 780 |
+
yield from _musllinux.platform_tags(archs)
|
| 781 |
+
for arch in archs:
|
| 782 |
+
yield f"linux_{arch}"
|
| 783 |
+
|
| 784 |
+
|
| 785 |
+
def _emscripten_platforms() -> Iterator[str]:
|
| 786 |
+
pyemscripten_platform_version = sysconfig.get_config_var(
|
| 787 |
+
"PYEMSCRIPTEN_PLATFORM_VERSION"
|
| 788 |
+
)
|
| 789 |
+
if pyemscripten_platform_version:
|
| 790 |
+
yield f"pyemscripten_{pyemscripten_platform_version}_wasm32"
|
| 791 |
+
yield from _generic_platforms()
|
| 792 |
+
|
| 793 |
+
|
| 794 |
+
def _generic_platforms() -> Iterator[str]:
|
| 795 |
+
yield _normalize_string(sysconfig.get_platform())
|
| 796 |
+
|
| 797 |
+
|
| 798 |
+
def platform_tags() -> Iterator[str]:
|
| 799 |
+
"""
|
| 800 |
+
Yields the :attr:`~Tag.platform` tags for the running interpreter.
|
| 801 |
+
"""
|
| 802 |
+
if platform.system() == "Darwin":
|
| 803 |
+
return mac_platforms()
|
| 804 |
+
elif platform.system() == "iOS":
|
| 805 |
+
return ios_platforms()
|
| 806 |
+
elif platform.system() == "Android":
|
| 807 |
+
return android_platforms()
|
| 808 |
+
elif platform.system() == "Linux":
|
| 809 |
+
return _linux_platforms()
|
| 810 |
+
elif platform.system() == "Emscripten":
|
| 811 |
+
return _emscripten_platforms()
|
| 812 |
+
else:
|
| 813 |
+
return _generic_platforms()
|
| 814 |
+
|
| 815 |
+
|
| 816 |
+
def interpreter_name() -> str:
|
| 817 |
+
"""
|
| 818 |
+
Returns the name of the running interpreter.
|
| 819 |
+
|
| 820 |
+
Some implementations have a reserved, two-letter abbreviation which will
|
| 821 |
+
be returned when appropriate.
|
| 822 |
+
|
| 823 |
+
This typically acts as the prefix to the :attr:`~Tag.interpreter` tag.
|
| 824 |
+
"""
|
| 825 |
+
name = sys.implementation.name
|
| 826 |
+
return INTERPRETER_SHORT_NAMES.get(name) or name
|
| 827 |
+
|
| 828 |
+
|
| 829 |
+
def interpreter_version(*, warn: bool = False) -> str:
|
| 830 |
+
"""
|
| 831 |
+
Returns the running interpreter's version.
|
| 832 |
+
|
| 833 |
+
This typically acts as the suffix to the :attr:`~Tag.interpreter` tag.
|
| 834 |
+
|
| 835 |
+
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
| 836 |
+
"""
|
| 837 |
+
version = _get_config_var("py_version_nodot", warn=warn)
|
| 838 |
+
return str(version) if version else _version_nodot(sys.version_info[:2])
|
| 839 |
+
|
| 840 |
+
|
| 841 |
+
def _version_nodot(version: PythonVersion) -> str:
|
| 842 |
+
return "".join(map(str, version))
|
| 843 |
+
|
| 844 |
+
|
| 845 |
+
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
| 846 |
+
"""
|
| 847 |
+
Yields the sequence of tag triples that the running interpreter supports.
|
| 848 |
+
|
| 849 |
+
The iterable is ordered so that the best-matching tag is first in the
|
| 850 |
+
sequence. The exact preferential order to tags is interpreter-specific, but
|
| 851 |
+
in general the tag importance is in the order of:
|
| 852 |
+
|
| 853 |
+
1. Interpreter
|
| 854 |
+
2. Platform
|
| 855 |
+
3. ABI
|
| 856 |
+
|
| 857 |
+
This order is due to the fact that an ABI is inherently tied to the
|
| 858 |
+
platform, but platform-specific code is not necessarily tied to the ABI. The
|
| 859 |
+
interpreter is the most important tag as it dictates basic support for any
|
| 860 |
+
wheel.
|
| 861 |
+
|
| 862 |
+
The function returns an iterable in order to allow for the possible
|
| 863 |
+
short-circuiting of tag generation if the entire sequence is not necessary
|
| 864 |
+
and tag calculation happens to be expensive.
|
| 865 |
+
|
| 866 |
+
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
| 867 |
+
|
| 868 |
+
.. versionchanged:: 21.3
|
| 869 |
+
Added the `pp3-none-any` tag (:issue:`311`).
|
| 870 |
+
.. versionchanged:: 27.0
|
| 871 |
+
Added the `abi3t` tag (:issue:`1099`).
|
| 872 |
+
"""
|
| 873 |
+
|
| 874 |
+
interp_name = interpreter_name()
|
| 875 |
+
if interp_name == "cp":
|
| 876 |
+
yield from cpython_tags(warn=warn)
|
| 877 |
+
else:
|
| 878 |
+
yield from generic_tags()
|
| 879 |
+
|
| 880 |
+
if interp_name == "pp":
|
| 881 |
+
interp = "pp3"
|
| 882 |
+
elif interp_name == "cp":
|
| 883 |
+
interp = "cp" + interpreter_version(warn=warn)
|
| 884 |
+
else:
|
| 885 |
+
interp = None
|
| 886 |
+
yield from compatible_tags(interpreter=interp)
|
| 887 |
+
|
| 888 |
+
|
| 889 |
+
def create_compatible_tags_selector(
|
| 890 |
+
tags: Iterable[Tag],
|
| 891 |
+
) -> Callable[[Iterable[tuple[_T, AbstractSet[Tag]]]], Iterator[_T]]:
|
| 892 |
+
"""Create a callable to select things compatible with supported tags.
|
| 893 |
+
|
| 894 |
+
This function accepts an ordered sequence of tags, with the preferred
|
| 895 |
+
tags first.
|
| 896 |
+
|
| 897 |
+
The returned callable accepts an iterable of tuples (thing, set[Tag]),
|
| 898 |
+
and returns an iterator of things, with the things with the best
|
| 899 |
+
matching tags first.
|
| 900 |
+
|
| 901 |
+
Example to select compatible wheel filenames:
|
| 902 |
+
|
| 903 |
+
>>> from packaging import tags
|
| 904 |
+
>>> from packaging.utils import parse_wheel_filename
|
| 905 |
+
>>> selector = tags.create_compatible_tags_selector(tags.sys_tags())
|
| 906 |
+
>>> filenames = ["foo-1.0-py3-none-any.whl", "foo-1.0-py2-none-any.whl"]
|
| 907 |
+
>>> list(selector([
|
| 908 |
+
... (filename, parse_wheel_filename(filename)[-1]) for filename in filenames
|
| 909 |
+
... ]))
|
| 910 |
+
['foo-1.0-py3-none-any.whl']
|
| 911 |
+
|
| 912 |
+
.. versionadded:: 26.1
|
| 913 |
+
"""
|
| 914 |
+
tag_ranks: dict[Tag, int] = {}
|
| 915 |
+
for rank, tag in enumerate(tags):
|
| 916 |
+
tag_ranks.setdefault(tag, rank) # ignore duplicate tags, keep first
|
| 917 |
+
supported_tags = tag_ranks.keys()
|
| 918 |
+
|
| 919 |
+
def selector(
|
| 920 |
+
tagged_things: Iterable[tuple[_T, AbstractSet[Tag]]],
|
| 921 |
+
) -> Iterator[_T]:
|
| 922 |
+
ranked_things: list[tuple[_T, int]] = []
|
| 923 |
+
for thing, thing_tags in tagged_things:
|
| 924 |
+
supported_thing_tags = thing_tags & supported_tags
|
| 925 |
+
if supported_thing_tags:
|
| 926 |
+
thing_rank = min(tag_ranks[t] for t in supported_thing_tags)
|
| 927 |
+
ranked_things.append((thing, thing_rank))
|
| 928 |
+
return iter(
|
| 929 |
+
thing for thing, _ in sorted(ranked_things, key=operator.itemgetter(1))
|
| 930 |
+
)
|
| 931 |
+
|
| 932 |
+
return selector
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/utils.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
from typing import NewType, Tuple, Union, cast
|
| 9 |
+
|
| 10 |
+
from .tags import Tag, UnsortedTagsError, parse_tag
|
| 11 |
+
from .version import InvalidVersion, Version, _TrimmedRelease
|
| 12 |
+
|
| 13 |
+
__all__ = [
|
| 14 |
+
"BuildTag",
|
| 15 |
+
"InvalidName",
|
| 16 |
+
"InvalidSdistFilename",
|
| 17 |
+
"InvalidWheelFilename",
|
| 18 |
+
"NormalizedName",
|
| 19 |
+
"canonicalize_name",
|
| 20 |
+
"canonicalize_version",
|
| 21 |
+
"is_normalized_name",
|
| 22 |
+
"parse_sdist_filename",
|
| 23 |
+
"parse_wheel_filename",
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def __dir__() -> list[str]:
|
| 28 |
+
return __all__
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
| 32 |
+
|
| 33 |
+
NormalizedName = NewType("NormalizedName", str)
|
| 34 |
+
"""
|
| 35 |
+
A :class:`typing.NewType` of :class:`str`, representing a normalized name.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class InvalidName(ValueError):
|
| 40 |
+
"""
|
| 41 |
+
An invalid distribution name; users should refer to the packaging user guide.
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class InvalidWheelFilename(ValueError):
|
| 46 |
+
"""
|
| 47 |
+
An invalid wheel filename was found, users should refer to PEP 427.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class InvalidSdistFilename(ValueError):
|
| 52 |
+
"""
|
| 53 |
+
An invalid sdist filename was found, users should refer to the packaging user guide.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# Core metadata spec for `Name`
|
| 58 |
+
_validate_regex = re.compile(
|
| 59 |
+
r"[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]", re.IGNORECASE | re.ASCII
|
| 60 |
+
)
|
| 61 |
+
_normalized_regex = re.compile(r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]", re.ASCII)
|
| 62 |
+
# PEP 427: The build number must start with a digit.
|
| 63 |
+
_build_tag_regex = re.compile(r"(\d+)(.*)", re.ASCII)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
| 67 |
+
"""
|
| 68 |
+
This function takes a valid Python package or extra name, and returns the
|
| 69 |
+
normalized form of it.
|
| 70 |
+
|
| 71 |
+
The return type is typed as :class:`NormalizedName`. This allows type
|
| 72 |
+
checkers to help require that a string has passed through this function
|
| 73 |
+
before use.
|
| 74 |
+
|
| 75 |
+
If **validate** is true, then the function will check if **name** is a valid
|
| 76 |
+
distribution name before normalizing.
|
| 77 |
+
|
| 78 |
+
:param str name: The name to normalize.
|
| 79 |
+
:param bool validate: Check whether the name is a valid distribution name.
|
| 80 |
+
:raises InvalidName: If **validate** is true and the name is not an
|
| 81 |
+
acceptable distribution name.
|
| 82 |
+
|
| 83 |
+
>>> from packaging.utils import canonicalize_name
|
| 84 |
+
>>> canonicalize_name("Django")
|
| 85 |
+
'django'
|
| 86 |
+
>>> canonicalize_name("oslo.concurrency")
|
| 87 |
+
'oslo-concurrency'
|
| 88 |
+
>>> canonicalize_name("requests")
|
| 89 |
+
'requests'
|
| 90 |
+
"""
|
| 91 |
+
if validate and not _validate_regex.fullmatch(name):
|
| 92 |
+
raise InvalidName(f"name is invalid: {name!r}")
|
| 93 |
+
# Ensure all ``.`` and ``_`` are ``-``
|
| 94 |
+
# Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
|
| 95 |
+
# Much faster than re, and even faster than str.translate
|
| 96 |
+
value = name.lower().replace("_", "-").replace(".", "-")
|
| 97 |
+
# Condense repeats (faster than regex)
|
| 98 |
+
while "--" in value:
|
| 99 |
+
value = value.replace("--", "-")
|
| 100 |
+
return cast("NormalizedName", value)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def is_normalized_name(name: str) -> bool:
|
| 104 |
+
"""
|
| 105 |
+
Check if a name is already normalized (i.e. :func:`canonicalize_name` would
|
| 106 |
+
roundtrip to the same value).
|
| 107 |
+
|
| 108 |
+
:param str name: The name to check.
|
| 109 |
+
|
| 110 |
+
>>> from packaging.utils import is_normalized_name
|
| 111 |
+
>>> is_normalized_name("requests")
|
| 112 |
+
True
|
| 113 |
+
>>> is_normalized_name("Django")
|
| 114 |
+
False
|
| 115 |
+
"""
|
| 116 |
+
return _normalized_regex.fullmatch(name) is not None
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def canonicalize_version(
|
| 120 |
+
version: Version | str, *, strip_trailing_zero: bool = True
|
| 121 |
+
) -> str:
|
| 122 |
+
"""Return a canonical form of a version as a string.
|
| 123 |
+
|
| 124 |
+
This function takes a string representing a package version (or a
|
| 125 |
+
:class:`~packaging.version.Version` instance), and returns the
|
| 126 |
+
normalized form of it. By default, it strips trailing zeros from
|
| 127 |
+
the release segment.
|
| 128 |
+
|
| 129 |
+
>>> from packaging.utils import canonicalize_version
|
| 130 |
+
>>> canonicalize_version('1.0.1')
|
| 131 |
+
'1.0.1'
|
| 132 |
+
|
| 133 |
+
Per PEP 625, versions may have multiple canonical forms, differing
|
| 134 |
+
only by trailing zeros.
|
| 135 |
+
|
| 136 |
+
>>> canonicalize_version('1.0.0')
|
| 137 |
+
'1'
|
| 138 |
+
>>> canonicalize_version('1.0.0', strip_trailing_zero=False)
|
| 139 |
+
'1.0.0'
|
| 140 |
+
|
| 141 |
+
Invalid versions are returned unaltered.
|
| 142 |
+
|
| 143 |
+
>>> canonicalize_version('foo bar baz')
|
| 144 |
+
'foo bar baz'
|
| 145 |
+
|
| 146 |
+
>>> canonicalize_version('1.4.0.0.0')
|
| 147 |
+
'1.4'
|
| 148 |
+
"""
|
| 149 |
+
if isinstance(version, str):
|
| 150 |
+
try:
|
| 151 |
+
version = Version(version)
|
| 152 |
+
except InvalidVersion:
|
| 153 |
+
return str(version)
|
| 154 |
+
return str(_TrimmedRelease(version) if strip_trailing_zero else version)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def parse_wheel_filename(
|
| 158 |
+
filename: str,
|
| 159 |
+
*,
|
| 160 |
+
validate_order: bool = False,
|
| 161 |
+
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
|
| 162 |
+
"""
|
| 163 |
+
This function takes the filename of a wheel file, and parses it,
|
| 164 |
+
returning a tuple of name, version, build number, and tags.
|
| 165 |
+
|
| 166 |
+
The name part of the tuple is normalized and typed as
|
| 167 |
+
:class:`NormalizedName`. The version portion is an instance of
|
| 168 |
+
:class:`~packaging.version.Version`. The build number is ``()`` if
|
| 169 |
+
there is no build number in the wheel filename, otherwise a
|
| 170 |
+
two-item tuple of an integer for the leading digits and
|
| 171 |
+
a string for the rest of the build number. The tags portion is a
|
| 172 |
+
frozen set of :class:`~packaging.tags.Tag` instances (as the tag
|
| 173 |
+
string format allows multiple tags to be combined into a single
|
| 174 |
+
string).
|
| 175 |
+
|
| 176 |
+
If **validate_order** is true, compressed tag set components are
|
| 177 |
+
checked to be in sorted order as required by PEP 425.
|
| 178 |
+
|
| 179 |
+
:param str filename: The name of the wheel file.
|
| 180 |
+
:param bool validate_order: Check whether compressed tag set components
|
| 181 |
+
are in sorted order.
|
| 182 |
+
:raises InvalidWheelFilename: If the filename in question
|
| 183 |
+
does not follow the :ref:`wheel specification
|
| 184 |
+
<pypug:binary-distribution-format>`.
|
| 185 |
+
|
| 186 |
+
>>> from packaging.utils import parse_wheel_filename
|
| 187 |
+
>>> from packaging.tags import Tag
|
| 188 |
+
>>> from packaging.version import Version
|
| 189 |
+
>>> name, ver, build, tags = parse_wheel_filename("foo-1.0-py3-none-any.whl")
|
| 190 |
+
>>> name
|
| 191 |
+
'foo'
|
| 192 |
+
>>> ver == Version('1.0')
|
| 193 |
+
True
|
| 194 |
+
>>> tags == {Tag("py3", "none", "any")}
|
| 195 |
+
True
|
| 196 |
+
>>> not build
|
| 197 |
+
True
|
| 198 |
+
|
| 199 |
+
.. versionadded:: 26.1
|
| 200 |
+
The *validate_order* parameter.
|
| 201 |
+
"""
|
| 202 |
+
if not filename.endswith(".whl"):
|
| 203 |
+
raise InvalidWheelFilename(
|
| 204 |
+
f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
filename = filename[:-4]
|
| 208 |
+
dashes = filename.count("-")
|
| 209 |
+
if dashes not in (4, 5):
|
| 210 |
+
raise InvalidWheelFilename(
|
| 211 |
+
f"Invalid wheel filename (wrong number of parts): {filename!r}"
|
| 212 |
+
)
|
| 213 |
+
|
| 214 |
+
parts = filename.split("-", dashes - 2)
|
| 215 |
+
name_part = parts[0]
|
| 216 |
+
# See PEP 427 for the rules on escaping the project name.
|
| 217 |
+
if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
|
| 218 |
+
raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
|
| 219 |
+
name = canonicalize_name(name_part)
|
| 220 |
+
|
| 221 |
+
try:
|
| 222 |
+
version = Version(parts[1])
|
| 223 |
+
except InvalidVersion as e:
|
| 224 |
+
raise InvalidWheelFilename(
|
| 225 |
+
f"Invalid wheel filename (invalid version): {filename!r}"
|
| 226 |
+
) from e
|
| 227 |
+
|
| 228 |
+
if dashes == 5:
|
| 229 |
+
build_part = parts[2]
|
| 230 |
+
build_match = _build_tag_regex.match(build_part)
|
| 231 |
+
if build_match is None:
|
| 232 |
+
raise InvalidWheelFilename(
|
| 233 |
+
f"Invalid build number: {build_part} in {filename!r}"
|
| 234 |
+
)
|
| 235 |
+
build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
|
| 236 |
+
else:
|
| 237 |
+
build = ()
|
| 238 |
+
tag_str = parts[-1]
|
| 239 |
+
try:
|
| 240 |
+
tags = parse_tag(tag_str, validate_order=validate_order)
|
| 241 |
+
except UnsortedTagsError:
|
| 242 |
+
raise InvalidWheelFilename(
|
| 243 |
+
f"Invalid wheel filename (compressed tag set components must be in "
|
| 244 |
+
f"sorted order per PEP 425): {filename!r}"
|
| 245 |
+
) from None
|
| 246 |
+
return (name, version, build, tags)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
|
| 250 |
+
"""
|
| 251 |
+
This function takes the filename of a sdist file (as specified
|
| 252 |
+
in the `Source distribution format`_ documentation), and parses
|
| 253 |
+
it, returning a tuple of the normalized name and version as
|
| 254 |
+
represented by an instance of :class:`~packaging.version.Version`.
|
| 255 |
+
|
| 256 |
+
:param str filename: The name of the sdist file.
|
| 257 |
+
:raises InvalidSdistFilename: If the filename does not end
|
| 258 |
+
with an sdist extension (``.zip`` or ``.tar.gz``), or if it does not
|
| 259 |
+
contain a dash separating the name and the version of the distribution.
|
| 260 |
+
|
| 261 |
+
>>> from packaging.utils import parse_sdist_filename
|
| 262 |
+
>>> from packaging.version import Version
|
| 263 |
+
>>> name, ver = parse_sdist_filename("foo-1.0.tar.gz")
|
| 264 |
+
>>> name
|
| 265 |
+
'foo'
|
| 266 |
+
>>> ver == Version('1.0')
|
| 267 |
+
True
|
| 268 |
+
|
| 269 |
+
.. _Source distribution format: https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-name
|
| 270 |
+
"""
|
| 271 |
+
if filename.endswith(".tar.gz"):
|
| 272 |
+
file_stem = filename[: -len(".tar.gz")]
|
| 273 |
+
elif filename.endswith(".zip"):
|
| 274 |
+
file_stem = filename[: -len(".zip")]
|
| 275 |
+
else:
|
| 276 |
+
raise InvalidSdistFilename(
|
| 277 |
+
f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
|
| 278 |
+
f" {filename!r}"
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
# We are requiring a PEP 440 version, which cannot contain dashes,
|
| 282 |
+
# so we split on the last dash.
|
| 283 |
+
name_part, sep, version_part = file_stem.rpartition("-")
|
| 284 |
+
if not sep:
|
| 285 |
+
raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
|
| 286 |
+
|
| 287 |
+
name = canonicalize_name(name_part)
|
| 288 |
+
|
| 289 |
+
try:
|
| 290 |
+
version = Version(version_part)
|
| 291 |
+
except InvalidVersion as e:
|
| 292 |
+
raise InvalidSdistFilename(
|
| 293 |
+
f"Invalid sdist filename (invalid version): {filename!r}"
|
| 294 |
+
) from e
|
| 295 |
+
|
| 296 |
+
return (name, version)
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/version.py
ADDED
|
@@ -0,0 +1,1231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file is dual licensed under the terms of the Apache License, Version
|
| 2 |
+
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
| 3 |
+
# for complete details.
|
| 4 |
+
"""
|
| 5 |
+
.. testsetup::
|
| 6 |
+
|
| 7 |
+
from packaging.version import parse, normalize_pre, Version, _cmpkey
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import re
|
| 13 |
+
import sys
|
| 14 |
+
import typing
|
| 15 |
+
from typing import (
|
| 16 |
+
Any,
|
| 17 |
+
Callable,
|
| 18 |
+
Literal,
|
| 19 |
+
NamedTuple,
|
| 20 |
+
SupportsInt,
|
| 21 |
+
Tuple,
|
| 22 |
+
TypedDict,
|
| 23 |
+
Union,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
if typing.TYPE_CHECKING:
|
| 27 |
+
from typing_extensions import Self, Unpack
|
| 28 |
+
|
| 29 |
+
if sys.version_info >= (3, 13): # pragma: no cover
|
| 30 |
+
from warnings import deprecated as _deprecated
|
| 31 |
+
elif typing.TYPE_CHECKING:
|
| 32 |
+
from typing_extensions import deprecated as _deprecated
|
| 33 |
+
else: # pragma: no cover
|
| 34 |
+
import functools
|
| 35 |
+
import warnings
|
| 36 |
+
|
| 37 |
+
def _deprecated(message: str) -> object:
|
| 38 |
+
def decorator(func: Callable[[...], object]) -> object:
|
| 39 |
+
@functools.wraps(func)
|
| 40 |
+
def wrapper(*args: object, **kwargs: object) -> object:
|
| 41 |
+
warnings.warn(
|
| 42 |
+
message,
|
| 43 |
+
category=DeprecationWarning,
|
| 44 |
+
stacklevel=2,
|
| 45 |
+
)
|
| 46 |
+
return func(*args, **kwargs)
|
| 47 |
+
|
| 48 |
+
return wrapper
|
| 49 |
+
|
| 50 |
+
return decorator
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
_LETTER_NORMALIZATION = {
|
| 54 |
+
"alpha": "a",
|
| 55 |
+
"beta": "b",
|
| 56 |
+
"c": "rc",
|
| 57 |
+
"pre": "rc",
|
| 58 |
+
"preview": "rc",
|
| 59 |
+
"rev": "post",
|
| 60 |
+
"r": "post",
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "normalize_pre", "parse"]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def __dir__() -> list[str]:
|
| 67 |
+
return __all__
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
LocalType = Tuple[Union[int, str], ...]
|
| 71 |
+
|
| 72 |
+
CmpLocalType = Tuple[Tuple[int, str], ...]
|
| 73 |
+
CmpSuffix = Tuple[int, int, int, int, int, int]
|
| 74 |
+
CmpKey = Union[
|
| 75 |
+
Tuple[int, Tuple[int, ...], CmpSuffix],
|
| 76 |
+
Tuple[int, Tuple[int, ...], CmpSuffix, CmpLocalType],
|
| 77 |
+
]
|
| 78 |
+
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class _VersionReplace(TypedDict, total=False):
|
| 82 |
+
epoch: int | None
|
| 83 |
+
release: tuple[int, ...] | None
|
| 84 |
+
pre: tuple[str, int] | None
|
| 85 |
+
post: int | None
|
| 86 |
+
dev: int | None
|
| 87 |
+
local: str | None
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def normalize_pre(letter: str, /) -> str:
|
| 91 |
+
"""Normalize the pre-release segment of a version string.
|
| 92 |
+
|
| 93 |
+
Returns a lowercase version of the string if not a known pre-release
|
| 94 |
+
identifier.
|
| 95 |
+
|
| 96 |
+
>>> normalize_pre('alpha')
|
| 97 |
+
'a'
|
| 98 |
+
>>> normalize_pre('BETA')
|
| 99 |
+
'b'
|
| 100 |
+
>>> normalize_pre('rc')
|
| 101 |
+
'rc'
|
| 102 |
+
|
| 103 |
+
:param letter:
|
| 104 |
+
|
| 105 |
+
.. versionadded:: 26.1
|
| 106 |
+
"""
|
| 107 |
+
letter = letter.lower()
|
| 108 |
+
return _LETTER_NORMALIZATION.get(letter, letter)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def parse(version: str) -> Version:
|
| 112 |
+
"""Parse the given version string.
|
| 113 |
+
|
| 114 |
+
This is identical to the :class:`Version` constructor.
|
| 115 |
+
|
| 116 |
+
>>> parse('1.0.dev1')
|
| 117 |
+
<Version('1.0.dev1')>
|
| 118 |
+
|
| 119 |
+
:param version: The version string to parse.
|
| 120 |
+
:raises InvalidVersion: When the version string is not a valid version.
|
| 121 |
+
"""
|
| 122 |
+
return Version(version)
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class InvalidVersion(ValueError):
|
| 126 |
+
"""Raised when a version string is not a valid version.
|
| 127 |
+
|
| 128 |
+
>>> Version("invalid")
|
| 129 |
+
Traceback (most recent call last):
|
| 130 |
+
...
|
| 131 |
+
packaging.version.InvalidVersion: Invalid version: 'invalid'
|
| 132 |
+
"""
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
class _BaseVersion:
|
| 136 |
+
__slots__ = ()
|
| 137 |
+
|
| 138 |
+
# This can also be a normal member (see the packaging_legacy package);
|
| 139 |
+
# we are just requiring it to be readable. Actually defining a property
|
| 140 |
+
# has runtime effect on subclasses, so it's typing only.
|
| 141 |
+
if typing.TYPE_CHECKING:
|
| 142 |
+
|
| 143 |
+
@property
|
| 144 |
+
def _key(self) -> tuple[Any, ...]: ...
|
| 145 |
+
|
| 146 |
+
def __hash__(self) -> int:
|
| 147 |
+
return hash(self._key)
|
| 148 |
+
|
| 149 |
+
# Please keep the duplicated `isinstance` check
|
| 150 |
+
# in the six comparisons hereunder
|
| 151 |
+
# unless you find a way to avoid adding overhead function calls.
|
| 152 |
+
def __lt__(self, other: _BaseVersion) -> bool:
|
| 153 |
+
if not isinstance(other, _BaseVersion):
|
| 154 |
+
return NotImplemented
|
| 155 |
+
|
| 156 |
+
return self._key < other._key
|
| 157 |
+
|
| 158 |
+
def __le__(self, other: _BaseVersion) -> bool:
|
| 159 |
+
if not isinstance(other, _BaseVersion):
|
| 160 |
+
return NotImplemented
|
| 161 |
+
|
| 162 |
+
return self._key <= other._key
|
| 163 |
+
|
| 164 |
+
def __eq__(self, other: object) -> bool:
|
| 165 |
+
if not isinstance(other, _BaseVersion):
|
| 166 |
+
return NotImplemented
|
| 167 |
+
|
| 168 |
+
return self._key == other._key
|
| 169 |
+
|
| 170 |
+
def __ge__(self, other: _BaseVersion) -> bool:
|
| 171 |
+
if not isinstance(other, _BaseVersion):
|
| 172 |
+
return NotImplemented
|
| 173 |
+
|
| 174 |
+
return self._key >= other._key
|
| 175 |
+
|
| 176 |
+
def __gt__(self, other: _BaseVersion) -> bool:
|
| 177 |
+
if not isinstance(other, _BaseVersion):
|
| 178 |
+
return NotImplemented
|
| 179 |
+
|
| 180 |
+
return self._key > other._key
|
| 181 |
+
|
| 182 |
+
def __ne__(self, other: object) -> bool:
|
| 183 |
+
if not isinstance(other, _BaseVersion):
|
| 184 |
+
return NotImplemented
|
| 185 |
+
|
| 186 |
+
return self._key != other._key
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# Deliberately not anchored to the start and end of the string, to make it
|
| 190 |
+
# easier for 3rd party code to reuse
|
| 191 |
+
|
| 192 |
+
# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
|
| 193 |
+
_VERSION_PATTERN = r"""
|
| 194 |
+
v?+ # optional leading v
|
| 195 |
+
(?a:
|
| 196 |
+
(?:(?P<epoch>[0-9]+)!)?+ # epoch
|
| 197 |
+
(?P<release>[0-9]+(?:\.[0-9]+)*+) # release segment
|
| 198 |
+
(?P<pre> # pre-release
|
| 199 |
+
[._-]?+
|
| 200 |
+
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
| 201 |
+
[._-]?+
|
| 202 |
+
(?P<pre_n>[0-9]+)?
|
| 203 |
+
)?+
|
| 204 |
+
(?P<post> # post release
|
| 205 |
+
(?:-(?P<post_n1>[0-9]+))
|
| 206 |
+
|
|
| 207 |
+
(?:
|
| 208 |
+
[._-]?
|
| 209 |
+
(?P<post_l>post|rev|r)
|
| 210 |
+
[._-]?
|
| 211 |
+
(?P<post_n2>[0-9]+)?
|
| 212 |
+
)
|
| 213 |
+
)?+
|
| 214 |
+
(?P<dev> # dev release
|
| 215 |
+
[._-]?+
|
| 216 |
+
(?P<dev_l>dev)
|
| 217 |
+
[._-]?+
|
| 218 |
+
(?P<dev_n>[0-9]+)?
|
| 219 |
+
)?+
|
| 220 |
+
)
|
| 221 |
+
(?a:\+
|
| 222 |
+
(?P<local> # local version
|
| 223 |
+
[a-z0-9]+
|
| 224 |
+
(?:[._-][a-z0-9]+)*+
|
| 225 |
+
)
|
| 226 |
+
)?+
|
| 227 |
+
"""
|
| 228 |
+
|
| 229 |
+
_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
|
| 230 |
+
|
| 231 |
+
# Possessive qualifiers were added in Python 3.11.
|
| 232 |
+
# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
|
| 233 |
+
# Older PyPy also had a bug.
|
| 234 |
+
VERSION_PATTERN = (
|
| 235 |
+
_VERSION_PATTERN_OLD
|
| 236 |
+
if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
|
| 237 |
+
or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
|
| 238 |
+
or sys.version_info < (3, 11)
|
| 239 |
+
else _VERSION_PATTERN
|
| 240 |
+
)
|
| 241 |
+
"""
|
| 242 |
+
A string containing the regular expression used to match a valid version.
|
| 243 |
+
|
| 244 |
+
The pattern is not anchored at either end, and is intended for embedding in larger
|
| 245 |
+
expressions (for example, matching a version number as part of a file name). The
|
| 246 |
+
regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
|
| 247 |
+
flags set.
|
| 248 |
+
|
| 249 |
+
.. versionchanged:: 26.0
|
| 250 |
+
|
| 251 |
+
The regex now uses possessive qualifiers on Python 3.11 if they are
|
| 252 |
+
supported (CPython 3.11.5+, PyPy 3.11.13+).
|
| 253 |
+
|
| 254 |
+
:meta hide-value:
|
| 255 |
+
"""
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
# Validation pattern for local version in replace()
|
| 259 |
+
_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)
|
| 260 |
+
|
| 261 |
+
# Fast path: If a version has only digits and dots then we
|
| 262 |
+
# can skip the regex and parse it as a release segment
|
| 263 |
+
_SIMPLE_VERSION_INDICATORS = frozenset(".0123456789")
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
def _validate_epoch(value: object, /) -> int:
|
| 267 |
+
epoch = value or 0
|
| 268 |
+
if isinstance(epoch, int) and epoch >= 0:
|
| 269 |
+
return epoch
|
| 270 |
+
msg = f"epoch must be non-negative integer, got {epoch}"
|
| 271 |
+
raise InvalidVersion(msg)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def _validate_release(value: object, /) -> tuple[int, ...]:
|
| 275 |
+
release = (0,) if value is None else value
|
| 276 |
+
if (
|
| 277 |
+
isinstance(release, tuple)
|
| 278 |
+
and len(release) > 0
|
| 279 |
+
and all(isinstance(i, int) and i >= 0 for i in release)
|
| 280 |
+
):
|
| 281 |
+
return release
|
| 282 |
+
msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
|
| 283 |
+
raise InvalidVersion(msg)
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
|
| 287 |
+
if value is None:
|
| 288 |
+
return value
|
| 289 |
+
if isinstance(value, tuple) and len(value) == 2:
|
| 290 |
+
letter, number = value
|
| 291 |
+
letter = normalize_pre(letter)
|
| 292 |
+
if letter in {"a", "b", "rc"} and isinstance(number, int) and number >= 0:
|
| 293 |
+
# type checkers can't infer the Literal type here on letter
|
| 294 |
+
return (letter, number) # type: ignore[return-value]
|
| 295 |
+
msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
|
| 296 |
+
raise InvalidVersion(msg)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
|
| 300 |
+
if value is None:
|
| 301 |
+
return value
|
| 302 |
+
if isinstance(value, int) and value >= 0:
|
| 303 |
+
return ("post", value)
|
| 304 |
+
msg = f"post must be non-negative integer, got {value}"
|
| 305 |
+
raise InvalidVersion(msg)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
|
| 309 |
+
if value is None:
|
| 310 |
+
return value
|
| 311 |
+
if isinstance(value, int) and value >= 0:
|
| 312 |
+
return ("dev", value)
|
| 313 |
+
msg = f"dev must be non-negative integer, got {value}"
|
| 314 |
+
raise InvalidVersion(msg)
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def _validate_local(value: object, /) -> LocalType | None:
|
| 318 |
+
if value is None:
|
| 319 |
+
return value
|
| 320 |
+
if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
|
| 321 |
+
return _parse_local_version(value)
|
| 322 |
+
msg = f"local must be a valid version string, got {value!r}"
|
| 323 |
+
raise InvalidVersion(msg)
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
# Backward compatibility for internals before 26.0. Do not use.
|
| 327 |
+
class _Version(NamedTuple):
|
| 328 |
+
epoch: int
|
| 329 |
+
release: tuple[int, ...]
|
| 330 |
+
dev: tuple[Literal["dev"], int] | None
|
| 331 |
+
pre: tuple[Literal["a", "b", "rc"], int] | None
|
| 332 |
+
post: tuple[Literal["post"], int] | None
|
| 333 |
+
local: LocalType | None
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
class Version(_BaseVersion):
|
| 337 |
+
"""This class abstracts handling of a project's versions.
|
| 338 |
+
|
| 339 |
+
A :class:`Version` instance is comparison aware and can be compared and
|
| 340 |
+
sorted using the standard Python interfaces.
|
| 341 |
+
|
| 342 |
+
>>> v1 = Version("1.0a5")
|
| 343 |
+
>>> v2 = Version("1.0")
|
| 344 |
+
>>> v1
|
| 345 |
+
<Version('1.0a5')>
|
| 346 |
+
>>> v2
|
| 347 |
+
<Version('1.0')>
|
| 348 |
+
>>> v1 < v2
|
| 349 |
+
True
|
| 350 |
+
>>> v1 == v2
|
| 351 |
+
False
|
| 352 |
+
>>> v1 > v2
|
| 353 |
+
False
|
| 354 |
+
>>> v1 >= v2
|
| 355 |
+
False
|
| 356 |
+
>>> v1 <= v2
|
| 357 |
+
True
|
| 358 |
+
|
| 359 |
+
:class:`Version` is immutable; use :meth:`__replace__` to change
|
| 360 |
+
part of a version.
|
| 361 |
+
|
| 362 |
+
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
| 363 |
+
format so the same pickle can be loaded in future packaging releases.
|
| 364 |
+
|
| 365 |
+
.. versionchanged:: 26.2
|
| 366 |
+
|
| 367 |
+
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
| 368 |
+
be unpickled with future releases. Backward compatibility with pickles
|
| 369 |
+
from packaging < 26.2 is supported but may be removed in a future
|
| 370 |
+
release.
|
| 371 |
+
"""
|
| 372 |
+
|
| 373 |
+
__slots__ = (
|
| 374 |
+
"_dev",
|
| 375 |
+
"_epoch",
|
| 376 |
+
"_hash_cache",
|
| 377 |
+
"_key_cache",
|
| 378 |
+
"_local",
|
| 379 |
+
"_post",
|
| 380 |
+
"_pre",
|
| 381 |
+
"_release",
|
| 382 |
+
)
|
| 383 |
+
__match_args__ = ("_str",)
|
| 384 |
+
"""
|
| 385 |
+
Pattern matching is supported on Python 3.10+.
|
| 386 |
+
|
| 387 |
+
.. versionadded:: 26.0
|
| 388 |
+
|
| 389 |
+
:meta hide-value:
|
| 390 |
+
"""
|
| 391 |
+
|
| 392 |
+
_regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
|
| 393 |
+
|
| 394 |
+
_epoch: int
|
| 395 |
+
_release: tuple[int, ...]
|
| 396 |
+
_dev: tuple[Literal["dev"], int] | None
|
| 397 |
+
_pre: tuple[Literal["a", "b", "rc"], int] | None
|
| 398 |
+
_post: tuple[Literal["post"], int] | None
|
| 399 |
+
_local: LocalType | None
|
| 400 |
+
|
| 401 |
+
_hash_cache: int | None
|
| 402 |
+
_key_cache: CmpKey | None
|
| 403 |
+
|
| 404 |
+
def __init__(self, version: str) -> None:
|
| 405 |
+
"""Initialize a Version object.
|
| 406 |
+
|
| 407 |
+
:param version:
|
| 408 |
+
The string representation of a version which will be parsed and normalized
|
| 409 |
+
before use.
|
| 410 |
+
:raises InvalidVersion:
|
| 411 |
+
If the ``version`` does not conform to PEP 440 in any way then this
|
| 412 |
+
exception will be raised.
|
| 413 |
+
"""
|
| 414 |
+
if _SIMPLE_VERSION_INDICATORS.issuperset(version):
|
| 415 |
+
try:
|
| 416 |
+
self._release = tuple(map(int, version.split(".")))
|
| 417 |
+
except ValueError:
|
| 418 |
+
# Empty parts (from "1..2", ".1", etc.) are invalid versions.
|
| 419 |
+
# Any other ValueError (e.g. int str-digits limit) should
|
| 420 |
+
# propagate to the caller.
|
| 421 |
+
if "" in version.split("."):
|
| 422 |
+
raise InvalidVersion(f"Invalid version: {version!r}") from None
|
| 423 |
+
# TODO: remove "no cover" when Python 3.9 is dropped.
|
| 424 |
+
raise # pragma: no cover
|
| 425 |
+
|
| 426 |
+
self._epoch = 0
|
| 427 |
+
self._pre = None
|
| 428 |
+
self._post = None
|
| 429 |
+
self._dev = None
|
| 430 |
+
self._local = None
|
| 431 |
+
self._key_cache = None
|
| 432 |
+
self._hash_cache = None
|
| 433 |
+
return
|
| 434 |
+
|
| 435 |
+
# Validate the version and parse it into pieces
|
| 436 |
+
match = self._regex.fullmatch(version)
|
| 437 |
+
if not match:
|
| 438 |
+
raise InvalidVersion(f"Invalid version: {version!r}")
|
| 439 |
+
self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
|
| 440 |
+
self._release = tuple(map(int, match.group("release").split(".")))
|
| 441 |
+
# We can type ignore the assignments below because the regex guarantees
|
| 442 |
+
# the correct strings
|
| 443 |
+
self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n")) # type: ignore[assignment]
|
| 444 |
+
self._post = _parse_letter_version( # type: ignore[assignment]
|
| 445 |
+
match.group("post_l"), match.group("post_n1") or match.group("post_n2")
|
| 446 |
+
)
|
| 447 |
+
self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n")) # type: ignore[assignment]
|
| 448 |
+
self._local = _parse_local_version(match.group("local"))
|
| 449 |
+
|
| 450 |
+
# Key which will be used for sorting
|
| 451 |
+
self._key_cache = None
|
| 452 |
+
self._hash_cache = None
|
| 453 |
+
|
| 454 |
+
@classmethod
|
| 455 |
+
def from_parts(
|
| 456 |
+
cls,
|
| 457 |
+
*,
|
| 458 |
+
epoch: int = 0,
|
| 459 |
+
release: tuple[int, ...],
|
| 460 |
+
pre: tuple[str, int] | None = None,
|
| 461 |
+
post: int | None = None,
|
| 462 |
+
dev: int | None = None,
|
| 463 |
+
local: str | None = None,
|
| 464 |
+
) -> Self:
|
| 465 |
+
"""
|
| 466 |
+
Return a new version composed of the various parts.
|
| 467 |
+
|
| 468 |
+
This allows you to build a version without going though a string and
|
| 469 |
+
running a regular expression. It normalizes pre-release strings. The
|
| 470 |
+
``release=`` keyword argument is required.
|
| 471 |
+
|
| 472 |
+
>>> Version.from_parts(release=(1,2,3))
|
| 473 |
+
<Version('1.2.3')>
|
| 474 |
+
>>> Version.from_parts(release=(0,1,0), pre=("b", 1))
|
| 475 |
+
<Version('0.1.0b1')>
|
| 476 |
+
|
| 477 |
+
:param epoch:
|
| 478 |
+
:param release: This version tuple is required
|
| 479 |
+
|
| 480 |
+
.. versionadded:: 26.1
|
| 481 |
+
"""
|
| 482 |
+
_epoch = _validate_epoch(epoch)
|
| 483 |
+
_release = _validate_release(release)
|
| 484 |
+
_pre = _validate_pre(pre) if pre is not None else None
|
| 485 |
+
_post = _validate_post(post) if post is not None else None
|
| 486 |
+
_dev = _validate_dev(dev) if dev is not None else None
|
| 487 |
+
_local = _validate_local(local) if local is not None else None
|
| 488 |
+
|
| 489 |
+
new_version = cls.__new__(cls)
|
| 490 |
+
new_version._key_cache = None
|
| 491 |
+
new_version._hash_cache = None
|
| 492 |
+
new_version._epoch = _epoch
|
| 493 |
+
new_version._release = _release
|
| 494 |
+
new_version._pre = _pre
|
| 495 |
+
new_version._post = _post
|
| 496 |
+
new_version._dev = _dev
|
| 497 |
+
new_version._local = _local
|
| 498 |
+
|
| 499 |
+
return new_version
|
| 500 |
+
|
| 501 |
+
def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
|
| 502 |
+
"""
|
| 503 |
+
__replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...)
|
| 504 |
+
|
| 505 |
+
Return a new version with parts replaced.
|
| 506 |
+
|
| 507 |
+
This returns a new version (unless no parts were changed). The
|
| 508 |
+
pre-release is normalized. Setting a value to ``None`` clears it.
|
| 509 |
+
|
| 510 |
+
>>> v = Version("1.2.3")
|
| 511 |
+
>>> v.__replace__(pre=("a", 1))
|
| 512 |
+
<Version('1.2.3a1')>
|
| 513 |
+
|
| 514 |
+
:param int | None epoch:
|
| 515 |
+
:param tuple[int, ...] | None release:
|
| 516 |
+
:param tuple[str, int] | None pre:
|
| 517 |
+
:param int | None post:
|
| 518 |
+
:param int | None dev:
|
| 519 |
+
:param str | None local:
|
| 520 |
+
|
| 521 |
+
.. versionadded:: 26.0
|
| 522 |
+
.. versionchanged:: 26.1
|
| 523 |
+
|
| 524 |
+
The pre-release portion is now normalized.
|
| 525 |
+
"""
|
| 526 |
+
epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
|
| 527 |
+
release = (
|
| 528 |
+
_validate_release(kwargs["release"])
|
| 529 |
+
if "release" in kwargs
|
| 530 |
+
else self._release
|
| 531 |
+
)
|
| 532 |
+
pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
|
| 533 |
+
post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
|
| 534 |
+
dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
|
| 535 |
+
local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
|
| 536 |
+
|
| 537 |
+
if (
|
| 538 |
+
epoch == self._epoch
|
| 539 |
+
and release == self._release
|
| 540 |
+
and pre == self._pre
|
| 541 |
+
and post == self._post
|
| 542 |
+
and dev == self._dev
|
| 543 |
+
and local == self._local
|
| 544 |
+
):
|
| 545 |
+
return self
|
| 546 |
+
|
| 547 |
+
new_version = self.__class__.__new__(self.__class__)
|
| 548 |
+
new_version._key_cache = None
|
| 549 |
+
new_version._hash_cache = None
|
| 550 |
+
new_version._epoch = epoch
|
| 551 |
+
new_version._release = release
|
| 552 |
+
new_version._pre = pre
|
| 553 |
+
new_version._post = post
|
| 554 |
+
new_version._dev = dev
|
| 555 |
+
new_version._local = local
|
| 556 |
+
|
| 557 |
+
return new_version
|
| 558 |
+
|
| 559 |
+
@property
|
| 560 |
+
def _key(self) -> CmpKey:
|
| 561 |
+
if self._key_cache is None:
|
| 562 |
+
self._key_cache = _cmpkey(
|
| 563 |
+
self._epoch,
|
| 564 |
+
self._release,
|
| 565 |
+
self._pre,
|
| 566 |
+
self._post,
|
| 567 |
+
self._dev,
|
| 568 |
+
self._local,
|
| 569 |
+
)
|
| 570 |
+
return self._key_cache
|
| 571 |
+
|
| 572 |
+
# __hash__ must be defined when __eq__ is overridden,
|
| 573 |
+
# otherwise Python sets __hash__ to None.
|
| 574 |
+
def __hash__(self) -> int:
|
| 575 |
+
if (cached_hash := self._hash_cache) is not None:
|
| 576 |
+
return cached_hash
|
| 577 |
+
|
| 578 |
+
if (key := self._key_cache) is None:
|
| 579 |
+
self._key_cache = key = _cmpkey(
|
| 580 |
+
self._epoch,
|
| 581 |
+
self._release,
|
| 582 |
+
self._pre,
|
| 583 |
+
self._post,
|
| 584 |
+
self._dev,
|
| 585 |
+
self._local,
|
| 586 |
+
)
|
| 587 |
+
self._hash_cache = cached_hash = hash(key)
|
| 588 |
+
return cached_hash
|
| 589 |
+
|
| 590 |
+
# Override comparison methods to use direct _key_cache access
|
| 591 |
+
# This is faster than property access, especially before Python 3.12
|
| 592 |
+
def __lt__(self, other: _BaseVersion) -> bool:
|
| 593 |
+
if isinstance(other, Version):
|
| 594 |
+
if self._key_cache is None:
|
| 595 |
+
self._key_cache = _cmpkey(
|
| 596 |
+
self._epoch,
|
| 597 |
+
self._release,
|
| 598 |
+
self._pre,
|
| 599 |
+
self._post,
|
| 600 |
+
self._dev,
|
| 601 |
+
self._local,
|
| 602 |
+
)
|
| 603 |
+
if other._key_cache is None:
|
| 604 |
+
other._key_cache = _cmpkey(
|
| 605 |
+
other._epoch,
|
| 606 |
+
other._release,
|
| 607 |
+
other._pre,
|
| 608 |
+
other._post,
|
| 609 |
+
other._dev,
|
| 610 |
+
other._local,
|
| 611 |
+
)
|
| 612 |
+
return self._key_cache < other._key_cache
|
| 613 |
+
|
| 614 |
+
if not isinstance(other, _BaseVersion):
|
| 615 |
+
return NotImplemented
|
| 616 |
+
|
| 617 |
+
return super().__lt__(other)
|
| 618 |
+
|
| 619 |
+
def __le__(self, other: _BaseVersion) -> bool:
|
| 620 |
+
if isinstance(other, Version):
|
| 621 |
+
if self._key_cache is None:
|
| 622 |
+
self._key_cache = _cmpkey(
|
| 623 |
+
self._epoch,
|
| 624 |
+
self._release,
|
| 625 |
+
self._pre,
|
| 626 |
+
self._post,
|
| 627 |
+
self._dev,
|
| 628 |
+
self._local,
|
| 629 |
+
)
|
| 630 |
+
if other._key_cache is None:
|
| 631 |
+
other._key_cache = _cmpkey(
|
| 632 |
+
other._epoch,
|
| 633 |
+
other._release,
|
| 634 |
+
other._pre,
|
| 635 |
+
other._post,
|
| 636 |
+
other._dev,
|
| 637 |
+
other._local,
|
| 638 |
+
)
|
| 639 |
+
return self._key_cache <= other._key_cache
|
| 640 |
+
|
| 641 |
+
if not isinstance(other, _BaseVersion):
|
| 642 |
+
return NotImplemented
|
| 643 |
+
|
| 644 |
+
return super().__le__(other)
|
| 645 |
+
|
| 646 |
+
def __eq__(self, other: object) -> bool:
|
| 647 |
+
if isinstance(other, Version):
|
| 648 |
+
if self._key_cache is None:
|
| 649 |
+
self._key_cache = _cmpkey(
|
| 650 |
+
self._epoch,
|
| 651 |
+
self._release,
|
| 652 |
+
self._pre,
|
| 653 |
+
self._post,
|
| 654 |
+
self._dev,
|
| 655 |
+
self._local,
|
| 656 |
+
)
|
| 657 |
+
if other._key_cache is None:
|
| 658 |
+
other._key_cache = _cmpkey(
|
| 659 |
+
other._epoch,
|
| 660 |
+
other._release,
|
| 661 |
+
other._pre,
|
| 662 |
+
other._post,
|
| 663 |
+
other._dev,
|
| 664 |
+
other._local,
|
| 665 |
+
)
|
| 666 |
+
return self._key_cache == other._key_cache
|
| 667 |
+
|
| 668 |
+
if not isinstance(other, _BaseVersion):
|
| 669 |
+
return NotImplemented
|
| 670 |
+
|
| 671 |
+
return super().__eq__(other)
|
| 672 |
+
|
| 673 |
+
def __ge__(self, other: _BaseVersion) -> bool:
|
| 674 |
+
if isinstance(other, Version):
|
| 675 |
+
if self._key_cache is None:
|
| 676 |
+
self._key_cache = _cmpkey(
|
| 677 |
+
self._epoch,
|
| 678 |
+
self._release,
|
| 679 |
+
self._pre,
|
| 680 |
+
self._post,
|
| 681 |
+
self._dev,
|
| 682 |
+
self._local,
|
| 683 |
+
)
|
| 684 |
+
if other._key_cache is None:
|
| 685 |
+
other._key_cache = _cmpkey(
|
| 686 |
+
other._epoch,
|
| 687 |
+
other._release,
|
| 688 |
+
other._pre,
|
| 689 |
+
other._post,
|
| 690 |
+
other._dev,
|
| 691 |
+
other._local,
|
| 692 |
+
)
|
| 693 |
+
return self._key_cache >= other._key_cache
|
| 694 |
+
|
| 695 |
+
if not isinstance(other, _BaseVersion):
|
| 696 |
+
return NotImplemented
|
| 697 |
+
|
| 698 |
+
return super().__ge__(other)
|
| 699 |
+
|
| 700 |
+
def __gt__(self, other: _BaseVersion) -> bool:
|
| 701 |
+
if isinstance(other, Version):
|
| 702 |
+
if self._key_cache is None:
|
| 703 |
+
self._key_cache = _cmpkey(
|
| 704 |
+
self._epoch,
|
| 705 |
+
self._release,
|
| 706 |
+
self._pre,
|
| 707 |
+
self._post,
|
| 708 |
+
self._dev,
|
| 709 |
+
self._local,
|
| 710 |
+
)
|
| 711 |
+
if other._key_cache is None:
|
| 712 |
+
other._key_cache = _cmpkey(
|
| 713 |
+
other._epoch,
|
| 714 |
+
other._release,
|
| 715 |
+
other._pre,
|
| 716 |
+
other._post,
|
| 717 |
+
other._dev,
|
| 718 |
+
other._local,
|
| 719 |
+
)
|
| 720 |
+
return self._key_cache > other._key_cache
|
| 721 |
+
|
| 722 |
+
if not isinstance(other, _BaseVersion):
|
| 723 |
+
return NotImplemented
|
| 724 |
+
|
| 725 |
+
return super().__gt__(other)
|
| 726 |
+
|
| 727 |
+
def __ne__(self, other: object) -> bool:
|
| 728 |
+
if isinstance(other, Version):
|
| 729 |
+
if self._key_cache is None:
|
| 730 |
+
self._key_cache = _cmpkey(
|
| 731 |
+
self._epoch,
|
| 732 |
+
self._release,
|
| 733 |
+
self._pre,
|
| 734 |
+
self._post,
|
| 735 |
+
self._dev,
|
| 736 |
+
self._local,
|
| 737 |
+
)
|
| 738 |
+
if other._key_cache is None:
|
| 739 |
+
other._key_cache = _cmpkey(
|
| 740 |
+
other._epoch,
|
| 741 |
+
other._release,
|
| 742 |
+
other._pre,
|
| 743 |
+
other._post,
|
| 744 |
+
other._dev,
|
| 745 |
+
other._local,
|
| 746 |
+
)
|
| 747 |
+
return self._key_cache != other._key_cache
|
| 748 |
+
|
| 749 |
+
if not isinstance(other, _BaseVersion):
|
| 750 |
+
return NotImplemented
|
| 751 |
+
|
| 752 |
+
return super().__ne__(other)
|
| 753 |
+
|
| 754 |
+
def __getstate__(
|
| 755 |
+
self,
|
| 756 |
+
) -> tuple[
|
| 757 |
+
int,
|
| 758 |
+
tuple[int, ...],
|
| 759 |
+
tuple[str, int] | None,
|
| 760 |
+
tuple[str, int] | None,
|
| 761 |
+
tuple[str, int] | None,
|
| 762 |
+
LocalType | None,
|
| 763 |
+
]:
|
| 764 |
+
# Return state as a 6-item tuple for compactness:
|
| 765 |
+
# (epoch, release, pre, post, dev, local)
|
| 766 |
+
# Cache members are excluded and will be recomputed on demand
|
| 767 |
+
return (
|
| 768 |
+
self._epoch,
|
| 769 |
+
self._release,
|
| 770 |
+
self._pre,
|
| 771 |
+
self._post,
|
| 772 |
+
self._dev,
|
| 773 |
+
self._local,
|
| 774 |
+
)
|
| 775 |
+
|
| 776 |
+
def __setstate__(self, state: object) -> None:
|
| 777 |
+
# Always discard cached values — they may contain stale references
|
| 778 |
+
# (e.g. packaging._structures.InfinityType from pre-26.1 pickles)
|
| 779 |
+
# and will be recomputed on demand from the core fields above.
|
| 780 |
+
self._key_cache = None
|
| 781 |
+
self._hash_cache = None
|
| 782 |
+
|
| 783 |
+
if isinstance(state, tuple):
|
| 784 |
+
if len(state) == 6:
|
| 785 |
+
# New format (26.2+): (epoch, release, pre, post, dev, local)
|
| 786 |
+
(
|
| 787 |
+
self._epoch,
|
| 788 |
+
self._release,
|
| 789 |
+
self._pre,
|
| 790 |
+
self._post,
|
| 791 |
+
self._dev,
|
| 792 |
+
self._local,
|
| 793 |
+
) = state
|
| 794 |
+
return
|
| 795 |
+
if len(state) == 2:
|
| 796 |
+
# Format (packaging 26.0-26.1): (None, {slot: value}).
|
| 797 |
+
_, slot_dict = state
|
| 798 |
+
if isinstance(slot_dict, dict):
|
| 799 |
+
self._epoch = slot_dict["_epoch"]
|
| 800 |
+
self._release = slot_dict["_release"]
|
| 801 |
+
self._pre = slot_dict.get("_pre")
|
| 802 |
+
self._post = slot_dict.get("_post")
|
| 803 |
+
self._dev = slot_dict.get("_dev")
|
| 804 |
+
self._local = slot_dict.get("_local")
|
| 805 |
+
return
|
| 806 |
+
if isinstance(state, dict):
|
| 807 |
+
# Old format (packaging <= 25.x, no __slots__): state is a plain
|
| 808 |
+
# dict with "_version" (_Version NamedTuple) and "_key" entries.
|
| 809 |
+
version_nt = state.get("_version")
|
| 810 |
+
if version_nt is not None:
|
| 811 |
+
self._epoch = version_nt.epoch
|
| 812 |
+
self._release = version_nt.release
|
| 813 |
+
self._pre = version_nt.pre
|
| 814 |
+
self._post = version_nt.post
|
| 815 |
+
self._dev = version_nt.dev
|
| 816 |
+
self._local = version_nt.local
|
| 817 |
+
return
|
| 818 |
+
|
| 819 |
+
raise TypeError(f"Cannot restore Version from {state!r}")
|
| 820 |
+
|
| 821 |
+
@property
|
| 822 |
+
@_deprecated("Version._version is private and will be removed soon")
|
| 823 |
+
def _version(self) -> _Version:
|
| 824 |
+
return _Version(
|
| 825 |
+
self._epoch, self._release, self._dev, self._pre, self._post, self._local
|
| 826 |
+
)
|
| 827 |
+
|
| 828 |
+
@_version.setter
|
| 829 |
+
@_deprecated("Version._version is private and will be removed soon")
|
| 830 |
+
def _version(self, value: _Version) -> None:
|
| 831 |
+
self._epoch = value.epoch
|
| 832 |
+
self._release = value.release
|
| 833 |
+
self._dev = value.dev
|
| 834 |
+
self._pre = value.pre
|
| 835 |
+
self._post = value.post
|
| 836 |
+
self._local = value.local
|
| 837 |
+
self._key_cache = None
|
| 838 |
+
self._hash_cache = None
|
| 839 |
+
|
| 840 |
+
def __repr__(self) -> str:
|
| 841 |
+
"""A representation of the Version that shows all internal state.
|
| 842 |
+
|
| 843 |
+
>>> Version('1.0.0')
|
| 844 |
+
<Version('1.0.0')>
|
| 845 |
+
"""
|
| 846 |
+
return f"<{self.__class__.__name__}({str(self)!r})>"
|
| 847 |
+
|
| 848 |
+
def __str__(self) -> str:
|
| 849 |
+
"""A string representation of the version that can be round-tripped.
|
| 850 |
+
|
| 851 |
+
>>> str(Version("1.0a5"))
|
| 852 |
+
'1.0a5'
|
| 853 |
+
"""
|
| 854 |
+
# This is a hot function, so not calling self.base_version
|
| 855 |
+
version = ".".join(map(str, self.release))
|
| 856 |
+
|
| 857 |
+
# Epoch
|
| 858 |
+
if self.epoch:
|
| 859 |
+
version = f"{self.epoch}!{version}"
|
| 860 |
+
|
| 861 |
+
# Pre-release
|
| 862 |
+
if self.pre is not None:
|
| 863 |
+
version += "".join(map(str, self.pre))
|
| 864 |
+
|
| 865 |
+
# Post-release
|
| 866 |
+
if self.post is not None:
|
| 867 |
+
version += f".post{self.post}"
|
| 868 |
+
|
| 869 |
+
# Development release
|
| 870 |
+
if self.dev is not None:
|
| 871 |
+
version += f".dev{self.dev}"
|
| 872 |
+
|
| 873 |
+
# Local version segment
|
| 874 |
+
if self.local is not None:
|
| 875 |
+
version += f"+{self.local}"
|
| 876 |
+
|
| 877 |
+
return version
|
| 878 |
+
|
| 879 |
+
@property
|
| 880 |
+
def _str(self) -> str:
|
| 881 |
+
"""Internal property for match_args"""
|
| 882 |
+
return str(self)
|
| 883 |
+
|
| 884 |
+
@property
|
| 885 |
+
def epoch(self) -> int:
|
| 886 |
+
"""The epoch of the version.
|
| 887 |
+
|
| 888 |
+
>>> Version("2.0.0").epoch
|
| 889 |
+
0
|
| 890 |
+
>>> Version("1!2.0.0").epoch
|
| 891 |
+
1
|
| 892 |
+
"""
|
| 893 |
+
return self._epoch
|
| 894 |
+
|
| 895 |
+
@property
|
| 896 |
+
def release(self) -> tuple[int, ...]:
|
| 897 |
+
"""The components of the "release" segment of the version.
|
| 898 |
+
|
| 899 |
+
>>> Version("1.2.3").release
|
| 900 |
+
(1, 2, 3)
|
| 901 |
+
>>> Version("2.0.0").release
|
| 902 |
+
(2, 0, 0)
|
| 903 |
+
>>> Version("1!2.0.0.post0").release
|
| 904 |
+
(2, 0, 0)
|
| 905 |
+
|
| 906 |
+
Includes trailing zeroes but not the epoch or any pre-release / development /
|
| 907 |
+
post-release suffixes.
|
| 908 |
+
"""
|
| 909 |
+
return self._release
|
| 910 |
+
|
| 911 |
+
@property
|
| 912 |
+
def pre(self) -> tuple[Literal["a", "b", "rc"], int] | None:
|
| 913 |
+
"""The pre-release segment of the version.
|
| 914 |
+
|
| 915 |
+
>>> print(Version("1.2.3").pre)
|
| 916 |
+
None
|
| 917 |
+
>>> Version("1.2.3a1").pre
|
| 918 |
+
('a', 1)
|
| 919 |
+
>>> Version("1.2.3b1").pre
|
| 920 |
+
('b', 1)
|
| 921 |
+
>>> Version("1.2.3rc1").pre
|
| 922 |
+
('rc', 1)
|
| 923 |
+
"""
|
| 924 |
+
return self._pre
|
| 925 |
+
|
| 926 |
+
@property
|
| 927 |
+
def post(self) -> int | None:
|
| 928 |
+
"""The post-release number of the version.
|
| 929 |
+
|
| 930 |
+
>>> print(Version("1.2.3").post)
|
| 931 |
+
None
|
| 932 |
+
>>> Version("1.2.3.post1").post
|
| 933 |
+
1
|
| 934 |
+
"""
|
| 935 |
+
return self._post[1] if self._post else None
|
| 936 |
+
|
| 937 |
+
@property
|
| 938 |
+
def dev(self) -> int | None:
|
| 939 |
+
"""The development number of the version.
|
| 940 |
+
|
| 941 |
+
>>> print(Version("1.2.3").dev)
|
| 942 |
+
None
|
| 943 |
+
>>> Version("1.2.3.dev1").dev
|
| 944 |
+
1
|
| 945 |
+
"""
|
| 946 |
+
return self._dev[1] if self._dev else None
|
| 947 |
+
|
| 948 |
+
@property
|
| 949 |
+
def local(self) -> str | None:
|
| 950 |
+
"""The local version segment of the version.
|
| 951 |
+
|
| 952 |
+
>>> print(Version("1.2.3").local)
|
| 953 |
+
None
|
| 954 |
+
>>> Version("1.2.3+abc").local
|
| 955 |
+
'abc'
|
| 956 |
+
"""
|
| 957 |
+
if self._local:
|
| 958 |
+
return ".".join(str(x) for x in self._local)
|
| 959 |
+
else:
|
| 960 |
+
return None
|
| 961 |
+
|
| 962 |
+
@property
|
| 963 |
+
def public(self) -> str:
|
| 964 |
+
"""The public portion of the version.
|
| 965 |
+
|
| 966 |
+
This returns a string. If you want a :class:`Version` again and care
|
| 967 |
+
about performance, use ``v.__replace__(local=None)`` instead.
|
| 968 |
+
|
| 969 |
+
>>> Version("1.2.3").public
|
| 970 |
+
'1.2.3'
|
| 971 |
+
>>> Version("1.2.3+abc").public
|
| 972 |
+
'1.2.3'
|
| 973 |
+
>>> Version("1!1.2.3dev1+abc").public
|
| 974 |
+
'1!1.2.3.dev1'
|
| 975 |
+
"""
|
| 976 |
+
return str(self).split("+", 1)[0]
|
| 977 |
+
|
| 978 |
+
@property
|
| 979 |
+
def base_version(self) -> str:
|
| 980 |
+
"""The "base version" of the version.
|
| 981 |
+
|
| 982 |
+
This returns a string. If you want a :class:`Version` again and care
|
| 983 |
+
about performance, use
|
| 984 |
+
``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead.
|
| 985 |
+
|
| 986 |
+
>>> Version("1.2.3").base_version
|
| 987 |
+
'1.2.3'
|
| 988 |
+
>>> Version("1.2.3+abc").base_version
|
| 989 |
+
'1.2.3'
|
| 990 |
+
>>> Version("1!1.2.3dev1+abc").base_version
|
| 991 |
+
'1!1.2.3'
|
| 992 |
+
|
| 993 |
+
The "base version" is the public version of the project without any pre or post
|
| 994 |
+
release markers.
|
| 995 |
+
"""
|
| 996 |
+
release_segment = ".".join(map(str, self.release))
|
| 997 |
+
return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
|
| 998 |
+
|
| 999 |
+
@property
|
| 1000 |
+
def is_prerelease(self) -> bool:
|
| 1001 |
+
"""Whether this version is a pre-release.
|
| 1002 |
+
|
| 1003 |
+
>>> Version("1.2.3").is_prerelease
|
| 1004 |
+
False
|
| 1005 |
+
>>> Version("1.2.3a1").is_prerelease
|
| 1006 |
+
True
|
| 1007 |
+
>>> Version("1.2.3b1").is_prerelease
|
| 1008 |
+
True
|
| 1009 |
+
>>> Version("1.2.3rc1").is_prerelease
|
| 1010 |
+
True
|
| 1011 |
+
>>> Version("1.2.3dev1").is_prerelease
|
| 1012 |
+
True
|
| 1013 |
+
"""
|
| 1014 |
+
return self.dev is not None or self.pre is not None
|
| 1015 |
+
|
| 1016 |
+
@property
|
| 1017 |
+
def is_postrelease(self) -> bool:
|
| 1018 |
+
"""Whether this version is a post-release.
|
| 1019 |
+
|
| 1020 |
+
>>> Version("1.2.3").is_postrelease
|
| 1021 |
+
False
|
| 1022 |
+
>>> Version("1.2.3.post1").is_postrelease
|
| 1023 |
+
True
|
| 1024 |
+
"""
|
| 1025 |
+
return self.post is not None
|
| 1026 |
+
|
| 1027 |
+
@property
|
| 1028 |
+
def is_devrelease(self) -> bool:
|
| 1029 |
+
"""Whether this version is a development release.
|
| 1030 |
+
|
| 1031 |
+
>>> Version("1.2.3").is_devrelease
|
| 1032 |
+
False
|
| 1033 |
+
>>> Version("1.2.3.dev1").is_devrelease
|
| 1034 |
+
True
|
| 1035 |
+
"""
|
| 1036 |
+
return self.dev is not None
|
| 1037 |
+
|
| 1038 |
+
@property
|
| 1039 |
+
def major(self) -> int:
|
| 1040 |
+
"""The first item of :attr:`release` or ``0`` if unavailable.
|
| 1041 |
+
|
| 1042 |
+
>>> Version("1.2.3").major
|
| 1043 |
+
1
|
| 1044 |
+
"""
|
| 1045 |
+
return self.release[0] if len(self.release) >= 1 else 0
|
| 1046 |
+
|
| 1047 |
+
@property
|
| 1048 |
+
def minor(self) -> int:
|
| 1049 |
+
"""The second item of :attr:`release` or ``0`` if unavailable.
|
| 1050 |
+
|
| 1051 |
+
>>> Version("1.2.3").minor
|
| 1052 |
+
2
|
| 1053 |
+
>>> Version("1").minor
|
| 1054 |
+
0
|
| 1055 |
+
"""
|
| 1056 |
+
return self.release[1] if len(self.release) >= 2 else 0
|
| 1057 |
+
|
| 1058 |
+
@property
|
| 1059 |
+
def micro(self) -> int:
|
| 1060 |
+
"""The third item of :attr:`release` or ``0`` if unavailable.
|
| 1061 |
+
|
| 1062 |
+
>>> Version("1.2.3").micro
|
| 1063 |
+
3
|
| 1064 |
+
>>> Version("1").micro
|
| 1065 |
+
0
|
| 1066 |
+
"""
|
| 1067 |
+
return self.release[2] if len(self.release) >= 3 else 0
|
| 1068 |
+
|
| 1069 |
+
|
| 1070 |
+
class _TrimmedRelease(Version):
|
| 1071 |
+
__slots__ = ()
|
| 1072 |
+
|
| 1073 |
+
def __init__(self, version: str | Version) -> None:
|
| 1074 |
+
if isinstance(version, Version):
|
| 1075 |
+
self._epoch = version._epoch
|
| 1076 |
+
self._release = version._release
|
| 1077 |
+
self._dev = version._dev
|
| 1078 |
+
self._pre = version._pre
|
| 1079 |
+
self._post = version._post
|
| 1080 |
+
self._local = version._local
|
| 1081 |
+
self._key_cache = version._key_cache
|
| 1082 |
+
return
|
| 1083 |
+
super().__init__(version) # pragma: no cover
|
| 1084 |
+
|
| 1085 |
+
@property
|
| 1086 |
+
def release(self) -> tuple[int, ...]:
|
| 1087 |
+
"""
|
| 1088 |
+
Release segment without any trailing zeros.
|
| 1089 |
+
|
| 1090 |
+
>>> _TrimmedRelease('1.0.0').release
|
| 1091 |
+
(1,)
|
| 1092 |
+
>>> _TrimmedRelease('0.0').release
|
| 1093 |
+
(0,)
|
| 1094 |
+
"""
|
| 1095 |
+
# This leaves one 0.
|
| 1096 |
+
rel = super().release
|
| 1097 |
+
len_release = len(rel)
|
| 1098 |
+
i = len_release
|
| 1099 |
+
while i > 1 and rel[i - 1] == 0:
|
| 1100 |
+
i -= 1
|
| 1101 |
+
return rel if i == len_release else rel[:i]
|
| 1102 |
+
|
| 1103 |
+
|
| 1104 |
+
def _parse_letter_version(
|
| 1105 |
+
letter: str | None, number: str | bytes | SupportsInt | None
|
| 1106 |
+
) -> tuple[str, int] | None:
|
| 1107 |
+
if letter:
|
| 1108 |
+
# We normalize any letters to their lower case form
|
| 1109 |
+
letter = letter.lower()
|
| 1110 |
+
|
| 1111 |
+
# We consider some words to be alternate spellings of other words and
|
| 1112 |
+
# in those cases we want to normalize the spellings to our preferred
|
| 1113 |
+
# spelling.
|
| 1114 |
+
letter = _LETTER_NORMALIZATION.get(letter, letter)
|
| 1115 |
+
|
| 1116 |
+
# We consider there to be an implicit 0 in a pre-release if there is
|
| 1117 |
+
# not a numeral associated with it.
|
| 1118 |
+
return letter, int(number or 0)
|
| 1119 |
+
|
| 1120 |
+
if number:
|
| 1121 |
+
# We assume if we are given a number, but we are not given a letter
|
| 1122 |
+
# then this is using the implicit post release syntax (e.g. 1.0-1)
|
| 1123 |
+
return "post", int(number)
|
| 1124 |
+
|
| 1125 |
+
return None
|
| 1126 |
+
|
| 1127 |
+
|
| 1128 |
+
_local_version_separators = re.compile(r"[\._-]")
|
| 1129 |
+
|
| 1130 |
+
|
| 1131 |
+
def _parse_local_version(local: str | None) -> LocalType | None:
|
| 1132 |
+
"""
|
| 1133 |
+
Takes a string like ``"abc.1.twelve"`` and turns it into
|
| 1134 |
+
``("abc", 1, "twelve")``.
|
| 1135 |
+
"""
|
| 1136 |
+
if local is not None:
|
| 1137 |
+
return tuple(
|
| 1138 |
+
part.lower() if not part.isdigit() else int(part)
|
| 1139 |
+
for part in _local_version_separators.split(local)
|
| 1140 |
+
)
|
| 1141 |
+
return None
|
| 1142 |
+
|
| 1143 |
+
|
| 1144 |
+
# Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release).
|
| 1145 |
+
_PRE_RANK = {"a": 0, "b": 1, "rc": 2}
|
| 1146 |
+
_PRE_RANK_DEV_ONLY = -1 # sorts before a(0)
|
| 1147 |
+
_PRE_RANK_STABLE = 3 # sorts after rc(2)
|
| 1148 |
+
|
| 1149 |
+
# In local version segments, strings sort before ints per PEP 440.
|
| 1150 |
+
_LOCAL_STR_RANK = -1 # sorts before all non-negative ints
|
| 1151 |
+
|
| 1152 |
+
# Pre-computed suffix for stable releases (no pre, post, or dev segments).
|
| 1153 |
+
# See _cmpkey() for the suffix layout.
|
| 1154 |
+
_STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0)
|
| 1155 |
+
|
| 1156 |
+
|
| 1157 |
+
def _cmpkey(
|
| 1158 |
+
epoch: int,
|
| 1159 |
+
release: tuple[int, ...],
|
| 1160 |
+
pre: tuple[str, int] | None,
|
| 1161 |
+
post: tuple[str, int] | None,
|
| 1162 |
+
dev: tuple[str, int] | None,
|
| 1163 |
+
local: LocalType | None,
|
| 1164 |
+
) -> CmpKey:
|
| 1165 |
+
"""Build a comparison key for PEP 440 ordering.
|
| 1166 |
+
|
| 1167 |
+
Returns ``(epoch, release, suffix)`` or
|
| 1168 |
+
``(epoch, release, suffix, local)`` so that plain tuple
|
| 1169 |
+
comparison gives the correct order.
|
| 1170 |
+
|
| 1171 |
+
Trailing zeros are stripped from the release so that ``1.0.0 == 1``.
|
| 1172 |
+
|
| 1173 |
+
The suffix is a flat 6-int tuple that encodes pre/post/dev:
|
| 1174 |
+
``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``
|
| 1175 |
+
|
| 1176 |
+
pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=3
|
| 1177 |
+
Dev-only releases (no pre or post) get -1 so they sort before
|
| 1178 |
+
any alpha/beta/rc. Releases without a pre-release tag get 3
|
| 1179 |
+
so they sort after rc.
|
| 1180 |
+
post_rank: no-post=0, post=1
|
| 1181 |
+
Releases without a post segment sort before those with one.
|
| 1182 |
+
dev_rank: dev=0, no-dev=1
|
| 1183 |
+
Releases without a dev segment sort after those with one.
|
| 1184 |
+
|
| 1185 |
+
Local segments use ``(n, "")`` for ints and ``(-1, s)`` for strings,
|
| 1186 |
+
following PEP 440: strings sort before ints, strings compare
|
| 1187 |
+
lexicographically, ints compare numerically, and shorter segments
|
| 1188 |
+
sort before longer when prefixes match. Versions without a local
|
| 1189 |
+
segment sort before those with one (3-tuple < 4-tuple).
|
| 1190 |
+
|
| 1191 |
+
>>> _cmpkey(0, (1, 0, 0), None, None, None, None)
|
| 1192 |
+
(0, (1,), (3, 0, 0, 0, 1, 0))
|
| 1193 |
+
>>> _cmpkey(0, (1,), ("a", 1), None, None, None)
|
| 1194 |
+
(0, (1,), (0, 1, 0, 0, 1, 0))
|
| 1195 |
+
>>> _cmpkey(0, (1,), None, None, None, ("ubuntu", 1))
|
| 1196 |
+
(0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, '')))
|
| 1197 |
+
"""
|
| 1198 |
+
# Strip trailing zeros: 1.0.0 compares equal to 1.
|
| 1199 |
+
len_release = len(release)
|
| 1200 |
+
i = len_release
|
| 1201 |
+
while i and release[i - 1] == 0:
|
| 1202 |
+
i -= 1
|
| 1203 |
+
trimmed = release if i == len_release else release[:i]
|
| 1204 |
+
|
| 1205 |
+
# Fast path: stable release with no local segment.
|
| 1206 |
+
if pre is None and post is None and dev is None and local is None:
|
| 1207 |
+
return epoch, trimmed, _STABLE_SUFFIX
|
| 1208 |
+
|
| 1209 |
+
if pre is None and post is None and dev is not None:
|
| 1210 |
+
# dev-only (e.g. 1.0.dev1) sorts before all pre-releases.
|
| 1211 |
+
pre_rank, pre_n = _PRE_RANK_DEV_ONLY, 0
|
| 1212 |
+
elif pre is None:
|
| 1213 |
+
pre_rank, pre_n = _PRE_RANK_STABLE, 0
|
| 1214 |
+
else:
|
| 1215 |
+
pre_rank, pre_n = _PRE_RANK[pre[0]], pre[1]
|
| 1216 |
+
|
| 1217 |
+
post_rank = 0 if post is None else 1
|
| 1218 |
+
post_n = 0 if post is None else post[1]
|
| 1219 |
+
|
| 1220 |
+
dev_rank = 1 if dev is None else 0
|
| 1221 |
+
dev_n = 0 if dev is None else dev[1]
|
| 1222 |
+
|
| 1223 |
+
suffix = (pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)
|
| 1224 |
+
|
| 1225 |
+
if local is None:
|
| 1226 |
+
return epoch, trimmed, suffix
|
| 1227 |
+
|
| 1228 |
+
cmp_local: CmpLocalType = tuple(
|
| 1229 |
+
(seg, "") if isinstance(seg, int) else (_LOCAL_STR_RANK, seg) for seg in local
|
| 1230 |
+
)
|
| 1231 |
+
return epoch, trimmed, suffix, cmp_local
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: pluggy
|
| 3 |
+
Version: 1.6.0
|
| 4 |
+
Summary: plugin and hook calling mechanisms for python
|
| 5 |
+
Author-email: Holger Krekel <holger@merlinux.eu>
|
| 6 |
+
License: MIT
|
| 7 |
+
Classifier: Development Status :: 6 - Mature
|
| 8 |
+
Classifier: Intended Audience :: Developers
|
| 9 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 10 |
+
Classifier: Operating System :: POSIX
|
| 11 |
+
Classifier: Operating System :: Microsoft :: Windows
|
| 12 |
+
Classifier: Operating System :: MacOS :: MacOS X
|
| 13 |
+
Classifier: Topic :: Software Development :: Testing
|
| 14 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 15 |
+
Classifier: Topic :: Utilities
|
| 16 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 17 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 18 |
+
Classifier: Programming Language :: Python :: 3
|
| 19 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 25 |
+
Requires-Python: >=3.9
|
| 26 |
+
Description-Content-Type: text/x-rst
|
| 27 |
+
License-File: LICENSE
|
| 28 |
+
Provides-Extra: dev
|
| 29 |
+
Requires-Dist: pre-commit; extra == "dev"
|
| 30 |
+
Requires-Dist: tox; extra == "dev"
|
| 31 |
+
Provides-Extra: testing
|
| 32 |
+
Requires-Dist: pytest; extra == "testing"
|
| 33 |
+
Requires-Dist: pytest-benchmark; extra == "testing"
|
| 34 |
+
Requires-Dist: coverage; extra == "testing"
|
| 35 |
+
Dynamic: license-file
|
| 36 |
+
|
| 37 |
+
====================================================
|
| 38 |
+
pluggy - A minimalist production ready plugin system
|
| 39 |
+
====================================================
|
| 40 |
+
|
| 41 |
+
|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov|
|
| 42 |
+
|
| 43 |
+
This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects.
|
| 44 |
+
|
| 45 |
+
Please `read the docs`_ to learn more!
|
| 46 |
+
|
| 47 |
+
A definitive example
|
| 48 |
+
====================
|
| 49 |
+
.. code-block:: python
|
| 50 |
+
|
| 51 |
+
import pluggy
|
| 52 |
+
|
| 53 |
+
hookspec = pluggy.HookspecMarker("myproject")
|
| 54 |
+
hookimpl = pluggy.HookimplMarker("myproject")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class MySpec:
|
| 58 |
+
"""A hook specification namespace."""
|
| 59 |
+
|
| 60 |
+
@hookspec
|
| 61 |
+
def myhook(self, arg1, arg2):
|
| 62 |
+
"""My special little hook that you can customize."""
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class Plugin_1:
|
| 66 |
+
"""A hook implementation namespace."""
|
| 67 |
+
|
| 68 |
+
@hookimpl
|
| 69 |
+
def myhook(self, arg1, arg2):
|
| 70 |
+
print("inside Plugin_1.myhook()")
|
| 71 |
+
return arg1 + arg2
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class Plugin_2:
|
| 75 |
+
"""A 2nd hook implementation namespace."""
|
| 76 |
+
|
| 77 |
+
@hookimpl
|
| 78 |
+
def myhook(self, arg1, arg2):
|
| 79 |
+
print("inside Plugin_2.myhook()")
|
| 80 |
+
return arg1 - arg2
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# create a manager and add the spec
|
| 84 |
+
pm = pluggy.PluginManager("myproject")
|
| 85 |
+
pm.add_hookspecs(MySpec)
|
| 86 |
+
|
| 87 |
+
# register plugins
|
| 88 |
+
pm.register(Plugin_1())
|
| 89 |
+
pm.register(Plugin_2())
|
| 90 |
+
|
| 91 |
+
# call our ``myhook`` hook
|
| 92 |
+
results = pm.hook.myhook(arg1=1, arg2=2)
|
| 93 |
+
print(results)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
Running this directly gets us::
|
| 97 |
+
|
| 98 |
+
$ python docs/examples/toy-example.py
|
| 99 |
+
inside Plugin_2.myhook()
|
| 100 |
+
inside Plugin_1.myhook()
|
| 101 |
+
[-1, 3]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
.. badges
|
| 105 |
+
|
| 106 |
+
.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg
|
| 107 |
+
:target: https://pypi.org/pypi/pluggy
|
| 108 |
+
|
| 109 |
+
.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg
|
| 110 |
+
:target: https://pypi.org/pypi/pluggy
|
| 111 |
+
|
| 112 |
+
.. |github-actions| image:: https://github.com/pytest-dev/pluggy/workflows/main/badge.svg
|
| 113 |
+
:target: https://github.com/pytest-dev/pluggy/actions
|
| 114 |
+
|
| 115 |
+
.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg
|
| 116 |
+
:target: https://anaconda.org/conda-forge/pytest
|
| 117 |
+
|
| 118 |
+
.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg
|
| 119 |
+
:alt: Join the chat at https://gitter.im/pytest-dev/pluggy
|
| 120 |
+
:target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
| 121 |
+
|
| 122 |
+
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
| 123 |
+
:target: https://github.com/ambv/black
|
| 124 |
+
|
| 125 |
+
.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg
|
| 126 |
+
:target: https://codecov.io/gh/pytest-dev/pluggy
|
| 127 |
+
:alt: Code coverage Status
|
| 128 |
+
|
| 129 |
+
.. links
|
| 130 |
+
.. _pytest:
|
| 131 |
+
http://pytest.org
|
| 132 |
+
.. _tox:
|
| 133 |
+
https://tox.readthedocs.org
|
| 134 |
+
.. _devpi:
|
| 135 |
+
http://doc.devpi.net
|
| 136 |
+
.. _read the docs:
|
| 137 |
+
https://pluggy.readthedocs.io/en/latest/
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
Support pluggy
|
| 141 |
+
--------------
|
| 142 |
+
|
| 143 |
+
`Open Collective`_ is an online funding platform for open and transparent communities.
|
| 144 |
+
It provides tools to raise money and share your finances in full transparency.
|
| 145 |
+
|
| 146 |
+
It is the platform of choice for individuals and companies that want to make one-time or
|
| 147 |
+
monthly donations directly to the project.
|
| 148 |
+
|
| 149 |
+
``pluggy`` is part of the ``pytest-dev`` project, see more details in the `pytest collective`_.
|
| 150 |
+
|
| 151 |
+
.. _Open Collective: https://opencollective.com
|
| 152 |
+
.. _pytest collective: https://opencollective.com/pytest
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pluggy-1.6.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
pluggy-1.6.0.dist-info/METADATA,sha256=dDjDXuJaCV63QW-EtGHC10Qlxec0rVTDkSRTxlJE4Bw,4811
|
| 3 |
+
pluggy-1.6.0.dist-info/RECORD,,
|
| 4 |
+
pluggy-1.6.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 5 |
+
pluggy-1.6.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
| 6 |
+
pluggy-1.6.0.dist-info/licenses/LICENSE,sha256=1rZebCE6XQtXeRHTTW5ZSbn1nXbCOMUHGi8_wWz7JgY,1110
|
| 7 |
+
pluggy-1.6.0.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
|
| 8 |
+
pluggy/__init__.py,sha256=D6dp1gmEDjtDp8hAwQc-qrgaulnL4iltrqkLDd-g9tg,811
|
| 9 |
+
pluggy/_callers.py,sha256=gEZllGaSYVssZ2UmpNfmYC0bdVgh2jYbAFeYKvuRMjY,5991
|
| 10 |
+
pluggy/_hooks.py,sha256=E6f3nYcI6dbEuO0Gmy61ozgGU_59_e69kC08a06EBuo,25218
|
| 11 |
+
pluggy/_manager.py,sha256=K4Ip_pkEjvT2oOIfQPp8CwAWoXVnENgQRcy9tlGii0o,20219
|
| 12 |
+
pluggy/_result.py,sha256=3Xfy7DrjXbYb7puRquyY2VbidIWNq6Pp7QnuElMdj8Q,3098
|
| 13 |
+
pluggy/_tracing.py,sha256=nXd2BCmDgf8jJxV-HO3PqxR-WV53eWnF8B4AF1nJGgo,2073
|
| 14 |
+
pluggy/_version.py,sha256=5FGJNp9Lkk9uOxeCjXpoCGBF79Ar6LGPOR7-atBqb_4,511
|
| 15 |
+
pluggy/_warnings.py,sha256=td0AvZBpfamriCC3OqsLwxMh-SzAMjfjmc58T5vP3lw,828
|
| 16 |
+
pluggy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/REQUESTED
ADDED
|
File without changes
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (80.7.1)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|