smlflg commited on
Commit
e68b3b1
·
verified ·
1 Parent(s): 19a9d01

Initial public upload from Projekte/CodingAgent (part 4)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/INSTALLER +1 -0
  3. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/METADATA +241 -0
  4. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/RECORD +119 -0
  5. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/REQUESTED +0 -0
  6. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/WHEEL +4 -0
  7. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/entry_points.txt +2 -0
  8. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp-1.28.1.dist-info/licenses/LICENSE +21 -0
  9. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/shared/tool_name_validation.py +129 -0
  10. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/shared/version.py +3 -0
  11. hai-mcp-pr7/.venv/lib/python3.13/site-packages/mcp/types.py +1999 -0
  12. hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/__init__.py +38 -0
  13. hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/decoders.py +1 -0
  14. hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/exceptions.py +1 -0
  15. hai-mcp-pr7/.venv/lib/python3.13/site-packages/multipart/multipart.py +1 -0
  16. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/INSTALLER +1 -0
  17. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/METADATA +112 -0
  18. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/RECORD +29 -0
  19. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/REQUESTED +0 -0
  20. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/WHEEL +4 -0
  21. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE +3 -0
  22. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE.APACHE +177 -0
  23. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging-26.2.dist-info/licenses/LICENSE.BSD +23 -0
  24. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/__init__.py +15 -0
  25. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_elffile.py +108 -0
  26. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_manylinux.py +262 -0
  27. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_musllinux.py +85 -0
  28. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_parser.py +393 -0
  29. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_structures.py +33 -0
  30. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/_tokenizer.py +193 -0
  31. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/dependency_groups.py +302 -0
  32. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/direct_url.py +325 -0
  33. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/errors.py +94 -0
  34. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/licenses/__init__.py +186 -0
  35. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/licenses/_spdx.py +799 -0
  36. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/markers.py +492 -0
  37. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/metadata.py +964 -0
  38. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/py.typed +0 -0
  39. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/pylock.py +905 -0
  40. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/requirements.py +129 -0
  41. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/specifiers.py +1943 -0
  42. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/tags.py +932 -0
  43. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/utils.py +296 -0
  44. hai-mcp-pr7/.venv/lib/python3.13/site-packages/packaging/version.py +1231 -0
  45. hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/INSTALLER +1 -0
  46. hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/METADATA +152 -0
  47. hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/RECORD +16 -0
  48. hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/REQUESTED +0 -0
  49. hai-mcp-pr7/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/WHEEL +5 -0
  50. 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.