Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9d12555b5 | ||
|
|
fe8d60cf64 | ||
|
|
3679a808c5 | ||
|
|
91fb97a7c0 | ||
|
|
727ea1fa0d | ||
|
|
4a94f1aea8 | ||
|
|
781b240128 | ||
|
|
1a542cc9e9 | ||
|
|
5b2bf39cba | ||
|
|
db8f849a06 | ||
|
|
2ce48ad558 |
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: Build Package Files
|
- name: Build Package Files
|
||||||
run: npm run package
|
run: npm run package
|
||||||
- name: Update package.json Version
|
- name: Update package.json Version
|
||||||
run: npm version ${{ github.ref_name }} --no-git-tag-version
|
run: npm version ${{ github.ref_name }} --no-git-tag-version --allow-same-version
|
||||||
- name: Commit and Push Changes
|
- name: Commit and Push Changes
|
||||||
run: |
|
run: |
|
||||||
git config user.name "${{ vars.USERNAME_GIT }}"
|
git config user.name "${{ vars.USERNAME_GIT }}"
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
# Get current branch; if detached, resolve to a remote branch that contains this commit
|
# Get current branch; if detached, resolve to a remote branch that contains this commit
|
||||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
if [[ "$BRANCH" == "HEAD" || -z "$BRANCH" ]]; then
|
if [[ "$BRANCH" == "HEAD" || -z "$BRANCH" ]]; then
|
||||||
# If target_commitish is a branch, use it; if it’s a SHA, find a branch that contains it
|
# If target_commitish is a branch, use it; if it's a SHA, find a branch that contains it
|
||||||
CANDIDATE='${{ github.event.release.target_commitish }}'
|
CANDIDATE='${{ github.event.release.target_commitish }}'
|
||||||
if [[ "$CANDIDATE" =~ ^[0-9a-f]{40}$ ]]; then
|
if [[ "$CANDIDATE" =~ ^[0-9a-f]{40}$ ]]; then
|
||||||
BRANCH="$(git branch -r --contains "$GITHUB_SHA" | sed -n 's|.*origin/||p' | head -n1)"
|
BRANCH="$(git branch -r --contains "$GITHUB_SHA" | sed -n 's|.*origin/||p' | head -n1)"
|
||||||
|
|||||||
+1
-2
@@ -8,5 +8,4 @@ __pycache__
|
|||||||
*.pyc
|
*.pyc
|
||||||
.nox
|
.nox
|
||||||
*.g4
|
*.g4
|
||||||
.antlr
|
.antlr
|
||||||
.claude
|
|
||||||
+1
-5
@@ -12,8 +12,4 @@
|
|||||||
|
|
||||||
## [0.2.0]
|
## [0.2.0]
|
||||||
|
|
||||||
- Add DEF File Support
|
- Add DEF File Support
|
||||||
|
|
||||||
## [2026.6.100]
|
|
||||||
|
|
||||||
- Fix several bugs
|
|
||||||
@@ -19,9 +19,8 @@ A comprehensive VS Code extension providing language support for NX CAM postproc
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Install from the VS Code Marketplace
|
1. Install from the VS Code Marketplace
|
||||||
2. Install Python 3.8 or higher
|
2. Open any `.cdl`, `.tcl`, or `.def` file
|
||||||
3. Open any `.cdl`, `.tcl`, or `.def` file
|
3. The extension will automatically activate and provide language support
|
||||||
4. The extension will automatically activate and provide language support
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|||||||
+20
-35
@@ -29,7 +29,7 @@ import { loadServerDefaults } from "./common/setup"
|
|||||||
import { getLSClientTraceLevel } from "./common/utilities"
|
import { getLSClientTraceLevel } from "./common/utilities"
|
||||||
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from "./common/vscodeapi"
|
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from "./common/vscodeapi"
|
||||||
|
|
||||||
let client: LanguageClient | undefined
|
let client: LanguageClient
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
// This is required to get server name and module. This should be
|
// This is required to get server name and module. This should be
|
||||||
@@ -61,7 +61,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
traceLog(`Module: ${serverInfo.module}`)
|
traceLog(`Module: ${serverInfo.module}`)
|
||||||
traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`)
|
traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`)
|
||||||
|
|
||||||
const runServerImpl = async () => {
|
const runServer = async () => {
|
||||||
const interpreter = getInterpreterFromSetting(serverId)
|
const interpreter = getInterpreterFromSetting(serverId)
|
||||||
if (interpreter && interpreter.length > 0) {
|
if (interpreter && interpreter.length > 0) {
|
||||||
if (checkVersion(await resolveInterpreter(interpreter))) {
|
if (checkVersion(await resolveInterpreter(interpreter))) {
|
||||||
@@ -90,16 +90,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize server (re)starts. Overlapping triggers (interpreter change,
|
|
||||||
// config change, restart command, initial activation) would otherwise each
|
|
||||||
// read the stale module-level `client`, start a new server and leave the
|
|
||||||
// previous one running orphaned -> hints/hover shown multiple times.
|
|
||||||
let runServerQueue: Promise<void> = Promise.resolve()
|
|
||||||
const runServer = () => {
|
|
||||||
runServerQueue = runServerQueue.catch(() => undefined).then(() => runServerImpl())
|
|
||||||
return runServerQueue
|
|
||||||
}
|
|
||||||
|
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
onDidChangePythonInterpreter(async () => {
|
onDidChangePythonInterpreter(async () => {
|
||||||
await runServer()
|
await runServer()
|
||||||
@@ -125,10 +115,6 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Folding ranges for TCL are provided by the language server
|
|
||||||
// (folding_range_provider in lsp_server.py). No client-side provider here to
|
|
||||||
// avoid duplicate folding regions.
|
|
||||||
|
|
||||||
//
|
//
|
||||||
const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider(
|
const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider(
|
||||||
{ scheme: "file", language: "cdl" },
|
{ scheme: "file", language: "cdl" },
|
||||||
@@ -221,27 +207,26 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
context.subscriptions.push(diagnosticCollectionCdl, diagnosticCollectionDef)
|
context.subscriptions.push(diagnosticCollectionCdl, diagnosticCollectionDef)
|
||||||
|
|
||||||
// Check if the first line of the CDL file contains "MACHINE"
|
// Check if the first line of the CDL file contains "MACHINE"
|
||||||
context.subscriptions.push(
|
vscode.workspace.onDidOpenTextDocument((document) => {
|
||||||
vscode.workspace.onDidOpenTextDocument((document) => {
|
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
if (document.languageId === "cdl") {
|
||||||
if (document.languageId === "cdl") {
|
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
||||||
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
} else if (document.languageId === "def") {
|
||||||
} else if (document.languageId === "def") {
|
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
||||||
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
})
|
||||||
const document = event.document
|
|
||||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||||
if (document.languageId === "cdl") {
|
const document = event.document
|
||||||
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||||
} else if (document.languageId === "def") {
|
if (document.languageId === "cdl") {
|
||||||
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
||||||
}
|
} else if (document.languageId === "def") {
|
||||||
|
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate(): Thenable<void> | undefined {
|
export function deactivate(): Thenable<void> | undefined {
|
||||||
|
|||||||
+26
-33
@@ -1,35 +1,28 @@
|
|||||||
{
|
{
|
||||||
"comments": {
|
"comments": {
|
||||||
// symbol used for single line comment. Remove this entry if your language does not support line comments
|
// symbol used for single line comment. Remove this entry if your language does not support line comments
|
||||||
"lineComment": "#"
|
"lineComment": "#"
|
||||||
},
|
},
|
||||||
"indentationRules": {
|
// symbols used as brackets
|
||||||
// Ignore pure comment lines so they don't terminate folds inside Tcl brace blocks.
|
"brackets": [
|
||||||
"unIndentedLinePattern": "^\\s*#.*$",
|
["{", "}"],
|
||||||
// Opening braces on their own line are common in NX Tcl.
|
["[", "]"],
|
||||||
"increaseIndentPattern": "^((?!#).)*(\\{[^}\"']*)$",
|
["(", ")"]
|
||||||
"decreaseIndentPattern": "^\\s*[\\}\\]\\)].*$"
|
],
|
||||||
},
|
// symbols that are auto closed when typing
|
||||||
// symbols used as brackets
|
"autoClosingPairs": [
|
||||||
"brackets": [
|
["{", "}"],
|
||||||
["{", "}"],
|
["[", "]"],
|
||||||
["[", "]"],
|
["(", ")"],
|
||||||
["(", ")"]
|
["\"", "\""],
|
||||||
],
|
["'", "'"]
|
||||||
// symbols that are auto closed when typing
|
],
|
||||||
"autoClosingPairs": [
|
// symbols that can be used to surround a selection
|
||||||
["{", "}"],
|
"surroundingPairs": [
|
||||||
["[", "]"],
|
["{", "}"],
|
||||||
["(", ")"],
|
["[", "]"],
|
||||||
["\"", "\""],
|
["(", ")"],
|
||||||
["'", "'"]
|
["\"", "\""],
|
||||||
],
|
["'", "'"]
|
||||||
// symbols that can be used to surround a selection
|
]
|
||||||
"surroundingPairs": [
|
|
||||||
["{", "}"],
|
|
||||||
["[", "]"],
|
|
||||||
["(", ")"],
|
|
||||||
["\"", "\""],
|
|
||||||
["'", "'"]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "nx-post-support",
|
"name": "nx-post-support",
|
||||||
"version": "2025.9.200",
|
"version": "2025.9.100",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nx-post-support",
|
"name": "nx-post-support",
|
||||||
"version": "2025.9.200",
|
"version": "2025.9.100",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/vscode": "^1.96.0",
|
"@types/vscode": "^1.96.0",
|
||||||
"@vscode/vsce": "^3.2.1",
|
"@vscode/vsce": "^3.2.1",
|
||||||
|
|||||||
+2
-5
@@ -1,13 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "nx-post-support",
|
"name": "nx-post-support",
|
||||||
"displayName": "NX Postprocessor Support",
|
"displayName": "NX Postprocessor Support",
|
||||||
"description": "VS Code extension for NX CAM postprocessor development with syntax highlighting, formatting, linting, and auto-completion for CDL, TCL, and DEF files",
|
"description": "",
|
||||||
"version": "2026.6.0",
|
"version": "2025.9.100",
|
||||||
"publisher": "Christoph",
|
"publisher": "Christoph",
|
||||||
"icon": "images/nx-1.png",
|
"icon": "images/nx-1.png",
|
||||||
"extensionDependencies": [
|
|
||||||
"ms-python.python"
|
|
||||||
],
|
|
||||||
"serverInfo": {
|
"serverInfo": {
|
||||||
"name": "NX Postprocessor Support",
|
"name": "NX Postprocessor Support",
|
||||||
"module": "nx-post-support"
|
"module": "nx-post-support"
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ def attrib(
|
|||||||
type: None = ...,
|
type: None = ...,
|
||||||
converter: None = ...,
|
converter: None = ...,
|
||||||
factory: None = ...,
|
factory: None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -200,7 +200,7 @@ def attrib(
|
|||||||
| tuple[_ConverterType]
|
| tuple[_ConverterType]
|
||||||
| None = ...,
|
| None = ...,
|
||||||
factory: Callable[[], _T] | None = ...,
|
factory: Callable[[], _T] | None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -223,7 +223,7 @@ def attrib(
|
|||||||
| tuple[_ConverterType]
|
| tuple[_ConverterType]
|
||||||
| None = ...,
|
| None = ...,
|
||||||
factory: Callable[[], _T] | None = ...,
|
factory: Callable[[], _T] | None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -246,7 +246,7 @@ def attrib(
|
|||||||
| tuple[_ConverterType]
|
| tuple[_ConverterType]
|
||||||
| None = ...,
|
| None = ...,
|
||||||
factory: Callable[[], _T] | None = ...,
|
factory: Callable[[], _T] | None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -308,7 +308,7 @@ def attrs(
|
|||||||
match_args: bool = ...,
|
match_args: bool = ...,
|
||||||
unsafe_hash: bool | None = ...,
|
unsafe_hash: bool | None = ...,
|
||||||
) -> Callable[[_C], _C]: ...
|
) -> Callable[[_C], _C]: ...
|
||||||
def fields(cls: type[AttrsInstance] | AttrsInstance) -> Any: ...
|
def fields(cls: type[AttrsInstance]) -> Any: ...
|
||||||
def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
|
def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
|
||||||
def validate(inst: AttrsInstance) -> None: ...
|
def validate(inst: AttrsInstance) -> None: ...
|
||||||
def resolve_types(
|
def resolve_types(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from typing import _GenericAlias
|
|||||||
|
|
||||||
|
|
||||||
PYPY = platform.python_implementation() == "PyPy"
|
PYPY = platform.python_implementation() == "PyPy"
|
||||||
|
PY_3_9_PLUS = sys.version_info[:2] >= (3, 9)
|
||||||
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
|
PY_3_10_PLUS = sys.version_info[:2] >= (3, 10)
|
||||||
PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
|
PY_3_11_PLUS = sys.version_info[:2] >= (3, 11)
|
||||||
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
|
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
|
||||||
@@ -17,16 +18,10 @@ PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
|
|||||||
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
|
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
|
||||||
|
|
||||||
|
|
||||||
if PY_3_14_PLUS:
|
if PY_3_14_PLUS: # pragma: no cover
|
||||||
import annotationlib
|
import annotationlib
|
||||||
|
|
||||||
# We request forward-ref annotations to not break in the presence of
|
_get_annotations = annotationlib.get_annotations
|
||||||
# forward references.
|
|
||||||
|
|
||||||
def _get_annotations(cls):
|
|
||||||
return annotationlib.get_annotations(
|
|
||||||
cls, format=annotationlib.Format.FORWARDREF
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
|
|||||||
+16
-45
@@ -3,28 +3,11 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from ._compat import get_generic_base
|
from ._compat import PY_3_9_PLUS, get_generic_base
|
||||||
from ._make import _OBJ_SETATTR, NOTHING, fields
|
from ._make import _OBJ_SETATTR, NOTHING, fields
|
||||||
from .exceptions import AttrsAttributeNotFoundError
|
from .exceptions import AttrsAttributeNotFoundError
|
||||||
|
|
||||||
|
|
||||||
_ATOMIC_TYPES = frozenset(
|
|
||||||
{
|
|
||||||
type(None),
|
|
||||||
bool,
|
|
||||||
int,
|
|
||||||
float,
|
|
||||||
str,
|
|
||||||
complex,
|
|
||||||
bytes,
|
|
||||||
type(...),
|
|
||||||
type,
|
|
||||||
range,
|
|
||||||
property,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def asdict(
|
def asdict(
|
||||||
inst,
|
inst,
|
||||||
recurse=True,
|
recurse=True,
|
||||||
@@ -88,10 +71,7 @@ def asdict(
|
|||||||
v = value_serializer(inst, a, v)
|
v = value_serializer(inst, a, v)
|
||||||
|
|
||||||
if recurse is True:
|
if recurse is True:
|
||||||
value_type = type(v)
|
if has(v.__class__):
|
||||||
if value_type in _ATOMIC_TYPES:
|
|
||||||
rv[a.name] = v
|
|
||||||
elif has(value_type):
|
|
||||||
rv[a.name] = asdict(
|
rv[a.name] = asdict(
|
||||||
v,
|
v,
|
||||||
recurse=True,
|
recurse=True,
|
||||||
@@ -100,8 +80,8 @@ def asdict(
|
|||||||
retain_collection_types=retain_collection_types,
|
retain_collection_types=retain_collection_types,
|
||||||
value_serializer=value_serializer,
|
value_serializer=value_serializer,
|
||||||
)
|
)
|
||||||
elif issubclass(value_type, (tuple, list, set, frozenset)):
|
elif isinstance(v, (tuple, list, set, frozenset)):
|
||||||
cf = value_type if retain_collection_types is True else list
|
cf = v.__class__ if retain_collection_types is True else list
|
||||||
items = [
|
items = [
|
||||||
_asdict_anything(
|
_asdict_anything(
|
||||||
i,
|
i,
|
||||||
@@ -121,7 +101,7 @@ def asdict(
|
|||||||
# Workaround for TypeError: cf.__new__() missing 1 required
|
# Workaround for TypeError: cf.__new__() missing 1 required
|
||||||
# positional argument (which appears, for a namedturle)
|
# positional argument (which appears, for a namedturle)
|
||||||
rv[a.name] = cf(*items)
|
rv[a.name] = cf(*items)
|
||||||
elif issubclass(value_type, dict):
|
elif isinstance(v, dict):
|
||||||
df = dict_factory
|
df = dict_factory
|
||||||
rv[a.name] = df(
|
rv[a.name] = df(
|
||||||
(
|
(
|
||||||
@@ -162,12 +142,7 @@ def _asdict_anything(
|
|||||||
"""
|
"""
|
||||||
``asdict`` only works on attrs instances, this works on anything.
|
``asdict`` only works on attrs instances, this works on anything.
|
||||||
"""
|
"""
|
||||||
val_type = type(val)
|
if getattr(val.__class__, "__attrs_attrs__", None) is not None:
|
||||||
if val_type in _ATOMIC_TYPES:
|
|
||||||
rv = val
|
|
||||||
if value_serializer is not None:
|
|
||||||
rv = value_serializer(None, None, rv)
|
|
||||||
elif getattr(val_type, "__attrs_attrs__", None) is not None:
|
|
||||||
# Attrs class.
|
# Attrs class.
|
||||||
rv = asdict(
|
rv = asdict(
|
||||||
val,
|
val,
|
||||||
@@ -177,7 +152,7 @@ def _asdict_anything(
|
|||||||
retain_collection_types=retain_collection_types,
|
retain_collection_types=retain_collection_types,
|
||||||
value_serializer=value_serializer,
|
value_serializer=value_serializer,
|
||||||
)
|
)
|
||||||
elif issubclass(val_type, (tuple, list, set, frozenset)):
|
elif isinstance(val, (tuple, list, set, frozenset)):
|
||||||
if retain_collection_types is True:
|
if retain_collection_types is True:
|
||||||
cf = val.__class__
|
cf = val.__class__
|
||||||
elif is_key:
|
elif is_key:
|
||||||
@@ -198,7 +173,7 @@ def _asdict_anything(
|
|||||||
for i in val
|
for i in val
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
elif issubclass(val_type, dict):
|
elif isinstance(val, dict):
|
||||||
df = dict_factory
|
df = dict_factory
|
||||||
rv = df(
|
rv = df(
|
||||||
(
|
(
|
||||||
@@ -278,11 +253,8 @@ def astuple(
|
|||||||
v = getattr(inst, a.name)
|
v = getattr(inst, a.name)
|
||||||
if filter is not None and not filter(a, v):
|
if filter is not None and not filter(a, v):
|
||||||
continue
|
continue
|
||||||
value_type = type(v)
|
|
||||||
if recurse is True:
|
if recurse is True:
|
||||||
if value_type in _ATOMIC_TYPES:
|
if has(v.__class__):
|
||||||
rv.append(v)
|
|
||||||
elif has(value_type):
|
|
||||||
rv.append(
|
rv.append(
|
||||||
astuple(
|
astuple(
|
||||||
v,
|
v,
|
||||||
@@ -292,7 +264,7 @@ def astuple(
|
|||||||
retain_collection_types=retain,
|
retain_collection_types=retain,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif issubclass(value_type, (tuple, list, set, frozenset)):
|
elif isinstance(v, (tuple, list, set, frozenset)):
|
||||||
cf = v.__class__ if retain is True else list
|
cf = v.__class__ if retain is True else list
|
||||||
items = [
|
items = [
|
||||||
(
|
(
|
||||||
@@ -316,8 +288,8 @@ def astuple(
|
|||||||
# Workaround for TypeError: cf.__new__() missing 1 required
|
# Workaround for TypeError: cf.__new__() missing 1 required
|
||||||
# positional argument (which appears, for a namedturle)
|
# positional argument (which appears, for a namedturle)
|
||||||
rv.append(cf(*items))
|
rv.append(cf(*items))
|
||||||
elif issubclass(value_type, dict):
|
elif isinstance(v, dict):
|
||||||
df = value_type if retain is True else dict
|
df = v.__class__ if retain is True else dict
|
||||||
rv.append(
|
rv.append(
|
||||||
df(
|
df(
|
||||||
(
|
(
|
||||||
@@ -478,11 +450,10 @@ def resolve_types(
|
|||||||
if getattr(cls, "__attrs_types_resolved__", None) != cls:
|
if getattr(cls, "__attrs_types_resolved__", None) != cls:
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
kwargs = {
|
kwargs = {"globalns": globalns, "localns": localns}
|
||||||
"globalns": globalns,
|
|
||||||
"localns": localns,
|
if PY_3_9_PLUS:
|
||||||
"include_extras": include_extras,
|
kwargs["include_extras"] = include_extras
|
||||||
}
|
|
||||||
|
|
||||||
hints = typing.get_type_hints(cls, **kwargs)
|
hints = typing.get_type_hints(cls, **kwargs)
|
||||||
for field in fields(cls) if attribs is None else attribs:
|
for field in fields(cls) if attribs is None else attribs:
|
||||||
|
|||||||
+113
-396
@@ -12,7 +12,6 @@ import linecache
|
|||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import weakref
|
|
||||||
|
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Mapping
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
@@ -114,7 +113,7 @@ def attrib(
|
|||||||
type=None,
|
type=None,
|
||||||
converter=None,
|
converter=None,
|
||||||
factory=None,
|
factory=None,
|
||||||
kw_only=None,
|
kw_only=False,
|
||||||
eq=None,
|
eq=None,
|
||||||
order=None,
|
order=None,
|
||||||
on_setattr=None,
|
on_setattr=None,
|
||||||
@@ -157,9 +156,6 @@ def attrib(
|
|||||||
*eq*, *order*, and *cmp* also accept a custom callable
|
*eq*, *order*, and *cmp* also accept a custom callable
|
||||||
.. versionchanged:: 21.1.0 *cmp* undeprecated
|
.. versionchanged:: 21.1.0 *cmp* undeprecated
|
||||||
.. versionadded:: 22.2.0 *alias*
|
.. versionadded:: 22.2.0 *alias*
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*kw_only* can now be None, and its default is also changed from False to
|
|
||||||
None.
|
|
||||||
"""
|
"""
|
||||||
eq, eq_key, order, order_key = _determine_attrib_eq_order(
|
eq, eq_key, order, order_key = _determine_attrib_eq_order(
|
||||||
cmp, eq, order, True
|
cmp, eq, order, True
|
||||||
@@ -377,12 +373,7 @@ def _collect_base_attrs_broken(cls, taken_attr_names):
|
|||||||
|
|
||||||
|
|
||||||
def _transform_attrs(
|
def _transform_attrs(
|
||||||
cls,
|
cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer
|
||||||
these,
|
|
||||||
auto_attribs,
|
|
||||||
kw_only,
|
|
||||||
collect_by_mro,
|
|
||||||
field_transformer,
|
|
||||||
) -> _Attributes:
|
) -> _Attributes:
|
||||||
"""
|
"""
|
||||||
Transform all `_CountingAttr`s on a class into `Attribute`s.
|
Transform all `_CountingAttr`s on a class into `Attribute`s.
|
||||||
@@ -437,15 +428,8 @@ def _transform_attrs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fca = Attribute.from_counting_attr
|
fca = Attribute.from_counting_attr
|
||||||
no = ClassProps.KeywordOnly.NO
|
|
||||||
own_attrs = [
|
own_attrs = [
|
||||||
fca(
|
fca(attr_name, ca, anns.get(attr_name)) for attr_name, ca in ca_list
|
||||||
attr_name,
|
|
||||||
ca,
|
|
||||||
kw_only is not no,
|
|
||||||
anns.get(attr_name),
|
|
||||||
)
|
|
||||||
for attr_name, ca in ca_list
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if collect_by_mro:
|
if collect_by_mro:
|
||||||
@@ -457,21 +441,12 @@ def _transform_attrs(
|
|||||||
cls, {a.name for a in own_attrs}
|
cls, {a.name for a in own_attrs}
|
||||||
)
|
)
|
||||||
|
|
||||||
if kw_only is ClassProps.KeywordOnly.FORCE:
|
if kw_only:
|
||||||
own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
|
own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
|
||||||
base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
|
base_attrs = [a.evolve(kw_only=True) for a in base_attrs]
|
||||||
|
|
||||||
attrs = base_attrs + own_attrs
|
attrs = base_attrs + own_attrs
|
||||||
|
|
||||||
# Resolve default field alias before executing field_transformer, so that
|
|
||||||
# the transformer receives fully populated Attribute objects with usable
|
|
||||||
# alias values.
|
|
||||||
for a in attrs:
|
|
||||||
if not a.alias:
|
|
||||||
# Evolve is very slow, so we hold our nose and do it dirty.
|
|
||||||
_OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
|
|
||||||
_OBJ_SETATTR.__get__(a)("alias_is_default", True)
|
|
||||||
|
|
||||||
if field_transformer is not None:
|
if field_transformer is not None:
|
||||||
attrs = tuple(field_transformer(cls, attrs))
|
attrs = tuple(field_transformer(cls, attrs))
|
||||||
|
|
||||||
@@ -489,12 +464,13 @@ def _transform_attrs(
|
|||||||
if had_default is False and a.default is not NOTHING:
|
if had_default is False and a.default is not NOTHING:
|
||||||
had_default = True
|
had_default = True
|
||||||
|
|
||||||
# Resolve default field alias for any new attributes that the
|
# Resolve default field alias after executing field_transformer.
|
||||||
# field_transformer may have added without setting an alias.
|
# This allows field_transformer to differentiate between explicit vs
|
||||||
|
# default aliases and supply their own defaults.
|
||||||
for a in attrs:
|
for a in attrs:
|
||||||
if not a.alias:
|
if not a.alias:
|
||||||
|
# Evolve is very slow, so we hold our nose and do it dirty.
|
||||||
_OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
|
_OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name))
|
||||||
_OBJ_SETATTR.__get__(a)("alias_is_default", True)
|
|
||||||
|
|
||||||
# Create AttrsClass *after* applying the field_transformer since it may
|
# Create AttrsClass *after* applying the field_transformer since it may
|
||||||
# add or remove attributes!
|
# add or remove attributes!
|
||||||
@@ -577,7 +553,7 @@ def _frozen_delattrs(self, name):
|
|||||||
"""
|
"""
|
||||||
Attached to frozen classes as __delattr__.
|
Attached to frozen classes as __delattr__.
|
||||||
"""
|
"""
|
||||||
if isinstance(self, BaseException) and name == "__notes__":
|
if isinstance(self, BaseException) and name in ("__notes__",):
|
||||||
BaseException.__delattr__(self, name)
|
BaseException.__delattr__(self, name)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -675,31 +651,38 @@ class _ClassBuilder:
|
|||||||
self,
|
self,
|
||||||
cls: type,
|
cls: type,
|
||||||
these,
|
these,
|
||||||
auto_attribs: bool,
|
slots,
|
||||||
props: ClassProps,
|
frozen,
|
||||||
has_custom_setattr: bool,
|
weakref_slot,
|
||||||
|
getstate_setstate,
|
||||||
|
auto_attribs,
|
||||||
|
kw_only,
|
||||||
|
cache_hash,
|
||||||
|
is_exc,
|
||||||
|
collect_by_mro,
|
||||||
|
on_setattr,
|
||||||
|
has_custom_setattr,
|
||||||
|
field_transformer,
|
||||||
):
|
):
|
||||||
attrs, base_attrs, base_map = _transform_attrs(
|
attrs, base_attrs, base_map = _transform_attrs(
|
||||||
cls,
|
cls,
|
||||||
these,
|
these,
|
||||||
auto_attribs,
|
auto_attribs,
|
||||||
props.kw_only,
|
kw_only,
|
||||||
props.collected_fields_by_mro,
|
collect_by_mro,
|
||||||
props.field_transformer,
|
field_transformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._cls = cls
|
self._cls = cls
|
||||||
self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}
|
self._cls_dict = dict(cls.__dict__) if slots else {}
|
||||||
self._attrs = attrs
|
self._attrs = attrs
|
||||||
self._base_names = {a.name for a in base_attrs}
|
self._base_names = {a.name for a in base_attrs}
|
||||||
self._base_attr_map = base_map
|
self._base_attr_map = base_map
|
||||||
self._attr_names = tuple(a.name for a in attrs)
|
self._attr_names = tuple(a.name for a in attrs)
|
||||||
self._slots = props.is_slotted
|
self._slots = slots
|
||||||
self._frozen = props.is_frozen
|
self._frozen = frozen
|
||||||
self._weakref_slot = props.has_weakref_slot
|
self._weakref_slot = weakref_slot
|
||||||
self._cache_hash = (
|
self._cache_hash = cache_hash
|
||||||
props.hashability is ClassProps.Hashability.HASHABLE_CACHED
|
|
||||||
)
|
|
||||||
self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
|
self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
|
||||||
self._pre_init_has_args = False
|
self._pre_init_has_args = False
|
||||||
if self._has_pre_init:
|
if self._has_pre_init:
|
||||||
@@ -710,21 +693,20 @@ class _ClassBuilder:
|
|||||||
self._pre_init_has_args = len(pre_init_signature.parameters) > 1
|
self._pre_init_has_args = len(pre_init_signature.parameters) > 1
|
||||||
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
|
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
|
||||||
self._delete_attribs = not bool(these)
|
self._delete_attribs = not bool(these)
|
||||||
self._is_exc = props.is_exception
|
self._is_exc = is_exc
|
||||||
self._on_setattr = props.on_setattr_hook
|
self._on_setattr = on_setattr
|
||||||
|
|
||||||
self._has_custom_setattr = has_custom_setattr
|
self._has_custom_setattr = has_custom_setattr
|
||||||
self._wrote_own_setattr = False
|
self._wrote_own_setattr = False
|
||||||
|
|
||||||
self._cls_dict["__attrs_attrs__"] = self._attrs
|
self._cls_dict["__attrs_attrs__"] = self._attrs
|
||||||
self._cls_dict["__attrs_props__"] = props
|
|
||||||
|
|
||||||
if props.is_frozen:
|
if frozen:
|
||||||
self._cls_dict["__setattr__"] = _frozen_setattrs
|
self._cls_dict["__setattr__"] = _frozen_setattrs
|
||||||
self._cls_dict["__delattr__"] = _frozen_delattrs
|
self._cls_dict["__delattr__"] = _frozen_delattrs
|
||||||
|
|
||||||
self._wrote_own_setattr = True
|
self._wrote_own_setattr = True
|
||||||
elif self._on_setattr in (
|
elif on_setattr in (
|
||||||
_DEFAULT_ON_SETATTR,
|
_DEFAULT_ON_SETATTR,
|
||||||
setters.validate,
|
setters.validate,
|
||||||
setters.convert,
|
setters.convert,
|
||||||
@@ -740,18 +722,18 @@ class _ClassBuilder:
|
|||||||
break
|
break
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
self._on_setattr == _DEFAULT_ON_SETATTR
|
on_setattr == _DEFAULT_ON_SETATTR
|
||||||
and not (has_validator or has_converter)
|
and not (has_validator or has_converter)
|
||||||
)
|
)
|
||||||
or (self._on_setattr == setters.validate and not has_validator)
|
or (on_setattr == setters.validate and not has_validator)
|
||||||
or (self._on_setattr == setters.convert and not has_converter)
|
or (on_setattr == setters.convert and not has_converter)
|
||||||
):
|
):
|
||||||
# If class-level on_setattr is set to convert + validate, but
|
# If class-level on_setattr is set to convert + validate, but
|
||||||
# there's no field to convert or validate, pretend like there's
|
# there's no field to convert or validate, pretend like there's
|
||||||
# no on_setattr.
|
# no on_setattr.
|
||||||
self._on_setattr = None
|
self._on_setattr = None
|
||||||
|
|
||||||
if props.added_pickling:
|
if getstate_setstate:
|
||||||
(
|
(
|
||||||
self._cls_dict["__getstate__"],
|
self._cls_dict["__getstate__"],
|
||||||
self._cls_dict["__setstate__"],
|
self._cls_dict["__setstate__"],
|
||||||
@@ -802,7 +784,6 @@ class _ClassBuilder:
|
|||||||
self._eval_snippets()
|
self._eval_snippets()
|
||||||
if self._slots is True:
|
if self._slots is True:
|
||||||
cls = self._create_slots_class()
|
cls = self._create_slots_class()
|
||||||
self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)
|
|
||||||
else:
|
else:
|
||||||
cls = self._patch_original_class()
|
cls = self._patch_original_class()
|
||||||
if PY_3_10_PLUS:
|
if PY_3_10_PLUS:
|
||||||
@@ -864,10 +845,6 @@ class _ClassBuilder:
|
|||||||
if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
|
if k not in (*tuple(self._attr_names), "__dict__", "__weakref__")
|
||||||
}
|
}
|
||||||
|
|
||||||
# 3.14.0rc2+
|
|
||||||
if hasattr(sys, "_clear_type_descriptors"):
|
|
||||||
sys._clear_type_descriptors(self._cls)
|
|
||||||
|
|
||||||
# If our class doesn't have its own implementation of __setattr__
|
# If our class doesn't have its own implementation of __setattr__
|
||||||
# (either from the user or by us), check the bases, if one of them has
|
# (either from the user or by us), check the bases, if one of them has
|
||||||
# an attrs-made __setattr__, that needs to be reset. We don't walk the
|
# an attrs-made __setattr__, that needs to be reset. We don't walk the
|
||||||
@@ -1104,7 +1081,9 @@ class _ClassBuilder:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def add_replace(self):
|
def add_replace(self):
|
||||||
self._cls_dict["__replace__"] = self._add_method_dunders(evolve)
|
self._cls_dict["__replace__"] = self._add_method_dunders(
|
||||||
|
lambda self, **changes: evolve(self, **changes)
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def add_match_args(self):
|
def add_match_args(self):
|
||||||
@@ -1347,7 +1326,6 @@ def attrs(
|
|||||||
field_transformer=None,
|
field_transformer=None,
|
||||||
match_args=True,
|
match_args=True,
|
||||||
unsafe_hash=None,
|
unsafe_hash=None,
|
||||||
force_kw_only=True,
|
|
||||||
):
|
):
|
||||||
r"""
|
r"""
|
||||||
A class decorator that adds :term:`dunder methods` according to the
|
A class decorator that adds :term:`dunder methods` according to the
|
||||||
@@ -1414,10 +1392,6 @@ def attrs(
|
|||||||
If a class has an *inherited* classmethod called
|
If a class has an *inherited* classmethod called
|
||||||
``__attrs_init_subclass__``, it is executed after the class is created.
|
``__attrs_init_subclass__``, it is executed after the class is created.
|
||||||
.. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
|
.. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*.
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*kw_only* now only applies to attributes defined in the current class,
|
|
||||||
and respects attribute-level ``kw_only=False`` settings.
|
|
||||||
.. versionadded:: 25.4.0 *force_kw_only*
|
|
||||||
"""
|
"""
|
||||||
if repr_ns is not None:
|
if repr_ns is not None:
|
||||||
import warnings
|
import warnings
|
||||||
@@ -1439,7 +1413,6 @@ def attrs(
|
|||||||
on_setattr = setters.pipe(*on_setattr)
|
on_setattr = setters.pipe(*on_setattr)
|
||||||
|
|
||||||
def wrap(cls):
|
def wrap(cls):
|
||||||
nonlocal hash
|
|
||||||
is_frozen = frozen or _has_frozen_base_class(cls)
|
is_frozen = frozen or _has_frozen_base_class(cls)
|
||||||
is_exc = auto_exc is True and issubclass(cls, BaseException)
|
is_exc = auto_exc is True and issubclass(cls, BaseException)
|
||||||
has_own_setattr = auto_detect and _has_own_attribute(
|
has_own_setattr = auto_detect and _has_own_attribute(
|
||||||
@@ -1450,112 +1423,84 @@ def attrs(
|
|||||||
msg = "Can't freeze a class with a custom __setattr__."
|
msg = "Can't freeze a class with a custom __setattr__."
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
eq = not is_exc and _determine_whether_to_implement(
|
builder = _ClassBuilder(
|
||||||
cls, eq_, auto_detect, ("__eq__", "__ne__")
|
cls,
|
||||||
)
|
these,
|
||||||
|
slots,
|
||||||
Hashability = ClassProps.Hashability
|
is_frozen,
|
||||||
|
weakref_slot,
|
||||||
if is_exc:
|
_determine_whether_to_implement(
|
||||||
hashability = Hashability.LEAVE_ALONE
|
|
||||||
elif hash is True:
|
|
||||||
hashability = (
|
|
||||||
Hashability.HASHABLE_CACHED
|
|
||||||
if cache_hash
|
|
||||||
else Hashability.HASHABLE
|
|
||||||
)
|
|
||||||
elif hash is False:
|
|
||||||
hashability = Hashability.LEAVE_ALONE
|
|
||||||
elif hash is None:
|
|
||||||
if auto_detect is True and _has_own_attribute(cls, "__hash__"):
|
|
||||||
hashability = Hashability.LEAVE_ALONE
|
|
||||||
elif eq is True and is_frozen is True:
|
|
||||||
hashability = (
|
|
||||||
Hashability.HASHABLE_CACHED
|
|
||||||
if cache_hash
|
|
||||||
else Hashability.HASHABLE
|
|
||||||
)
|
|
||||||
elif eq is False:
|
|
||||||
hashability = Hashability.LEAVE_ALONE
|
|
||||||
else:
|
|
||||||
hashability = Hashability.UNHASHABLE
|
|
||||||
else:
|
|
||||||
msg = "Invalid value for hash. Must be True, False, or None."
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
KeywordOnly = ClassProps.KeywordOnly
|
|
||||||
if kw_only:
|
|
||||||
kwo = KeywordOnly.FORCE if force_kw_only else KeywordOnly.YES
|
|
||||||
else:
|
|
||||||
kwo = KeywordOnly.NO
|
|
||||||
|
|
||||||
props = ClassProps(
|
|
||||||
is_exception=is_exc,
|
|
||||||
is_frozen=is_frozen,
|
|
||||||
is_slotted=slots,
|
|
||||||
collected_fields_by_mro=collect_by_mro,
|
|
||||||
added_init=_determine_whether_to_implement(
|
|
||||||
cls, init, auto_detect, ("__init__",)
|
|
||||||
),
|
|
||||||
added_repr=_determine_whether_to_implement(
|
|
||||||
cls, repr, auto_detect, ("__repr__",)
|
|
||||||
),
|
|
||||||
added_eq=eq,
|
|
||||||
added_ordering=not is_exc
|
|
||||||
and _determine_whether_to_implement(
|
|
||||||
cls,
|
|
||||||
order_,
|
|
||||||
auto_detect,
|
|
||||||
("__lt__", "__le__", "__gt__", "__ge__"),
|
|
||||||
),
|
|
||||||
hashability=hashability,
|
|
||||||
added_match_args=match_args,
|
|
||||||
kw_only=kwo,
|
|
||||||
has_weakref_slot=weakref_slot,
|
|
||||||
added_str=str,
|
|
||||||
added_pickling=_determine_whether_to_implement(
|
|
||||||
cls,
|
cls,
|
||||||
getstate_setstate,
|
getstate_setstate,
|
||||||
auto_detect,
|
auto_detect,
|
||||||
("__getstate__", "__setstate__"),
|
("__getstate__", "__setstate__"),
|
||||||
default=slots,
|
default=slots,
|
||||||
),
|
),
|
||||||
on_setattr_hook=on_setattr,
|
auto_attribs,
|
||||||
field_transformer=field_transformer,
|
kw_only,
|
||||||
|
cache_hash,
|
||||||
|
is_exc,
|
||||||
|
collect_by_mro,
|
||||||
|
on_setattr,
|
||||||
|
has_own_setattr,
|
||||||
|
field_transformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not props.is_hashable and cache_hash:
|
if _determine_whether_to_implement(
|
||||||
msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
|
cls, repr, auto_detect, ("__repr__",)
|
||||||
raise TypeError(msg)
|
):
|
||||||
|
|
||||||
builder = _ClassBuilder(
|
|
||||||
cls,
|
|
||||||
these,
|
|
||||||
auto_attribs=auto_attribs,
|
|
||||||
props=props,
|
|
||||||
has_custom_setattr=has_own_setattr,
|
|
||||||
)
|
|
||||||
|
|
||||||
if props.added_repr:
|
|
||||||
builder.add_repr(repr_ns)
|
builder.add_repr(repr_ns)
|
||||||
|
|
||||||
if props.added_str:
|
if str is True:
|
||||||
builder.add_str()
|
builder.add_str()
|
||||||
|
|
||||||
if props.added_eq:
|
eq = _determine_whether_to_implement(
|
||||||
|
cls, eq_, auto_detect, ("__eq__", "__ne__")
|
||||||
|
)
|
||||||
|
if not is_exc and eq is True:
|
||||||
builder.add_eq()
|
builder.add_eq()
|
||||||
if props.added_ordering:
|
if not is_exc and _determine_whether_to_implement(
|
||||||
|
cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__")
|
||||||
|
):
|
||||||
builder.add_order()
|
builder.add_order()
|
||||||
|
|
||||||
if not frozen:
|
if not frozen:
|
||||||
builder.add_setattr()
|
builder.add_setattr()
|
||||||
|
|
||||||
if props.is_hashable:
|
nonlocal hash
|
||||||
|
if (
|
||||||
|
hash is None
|
||||||
|
and auto_detect is True
|
||||||
|
and _has_own_attribute(cls, "__hash__")
|
||||||
|
):
|
||||||
|
hash = False
|
||||||
|
|
||||||
|
if hash is not True and hash is not False and hash is not None:
|
||||||
|
# Can't use `hash in` because 1 == True for example.
|
||||||
|
msg = "Invalid value for hash. Must be True, False, or None."
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if hash is False or (hash is None and eq is False) or is_exc:
|
||||||
|
# Don't do anything. Should fall back to __object__'s __hash__
|
||||||
|
# which is by id.
|
||||||
|
if cache_hash:
|
||||||
|
msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
|
||||||
|
raise TypeError(msg)
|
||||||
|
elif hash is True or (
|
||||||
|
hash is None and eq is True and is_frozen is True
|
||||||
|
):
|
||||||
|
# Build a __hash__ if told so, or if it's safe.
|
||||||
builder.add_hash()
|
builder.add_hash()
|
||||||
elif props.hashability is Hashability.UNHASHABLE:
|
else:
|
||||||
|
# Raise TypeError on attempts to hash.
|
||||||
|
if cache_hash:
|
||||||
|
msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
|
||||||
|
raise TypeError(msg)
|
||||||
builder.make_unhashable()
|
builder.make_unhashable()
|
||||||
|
|
||||||
if props.added_init:
|
if _determine_whether_to_implement(
|
||||||
|
cls, init, auto_detect, ("__init__",)
|
||||||
|
):
|
||||||
builder.add_init()
|
builder.add_init()
|
||||||
else:
|
else:
|
||||||
builder.add_attrs_init()
|
builder.add_attrs_init()
|
||||||
@@ -1890,16 +1835,16 @@ def _add_repr(cls, ns=None, attrs=None):
|
|||||||
|
|
||||||
def fields(cls):
|
def fields(cls):
|
||||||
"""
|
"""
|
||||||
Return the tuple of *attrs* attributes for a class or instance.
|
Return the tuple of *attrs* attributes for a class.
|
||||||
|
|
||||||
The tuple also allows accessing the fields by their names (see below for
|
The tuple also allows accessing the fields by their names (see below for
|
||||||
examples).
|
examples).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cls (type): Class or instance to introspect.
|
cls (type): Class to introspect.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TypeError: If *cls* is neither a class nor an *attrs* instance.
|
TypeError: If *cls* is not a class.
|
||||||
|
|
||||||
attrs.exceptions.NotAnAttrsClassError:
|
attrs.exceptions.NotAnAttrsClassError:
|
||||||
If *cls* is not an *attrs* class.
|
If *cls* is not an *attrs* class.
|
||||||
@@ -1910,17 +1855,12 @@ def fields(cls):
|
|||||||
.. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
|
.. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
|
||||||
by name.
|
by name.
|
||||||
.. versionchanged:: 23.1.0 Add support for generic classes.
|
.. versionchanged:: 23.1.0 Add support for generic classes.
|
||||||
.. versionchanged:: 26.1.0 Add support for instances.
|
|
||||||
"""
|
"""
|
||||||
generic_base = get_generic_base(cls)
|
generic_base = get_generic_base(cls)
|
||||||
|
|
||||||
if generic_base is None and not isinstance(cls, type):
|
if generic_base is None and not isinstance(cls, type):
|
||||||
type_ = type(cls)
|
msg = "Passed object must be a class."
|
||||||
if getattr(type_, "__attrs_attrs__", None) is None:
|
raise TypeError(msg)
|
||||||
msg = "Passed object must be a class or attrs instance."
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
return fields(type_)
|
|
||||||
|
|
||||||
attrs = getattr(cls, "__attrs_attrs__", None)
|
attrs = getattr(cls, "__attrs_attrs__", None)
|
||||||
|
|
||||||
@@ -2027,7 +1967,7 @@ def _make_init_script(
|
|||||||
attr_dict[a.name] = a
|
attr_dict[a.name] = a
|
||||||
|
|
||||||
if a.on_setattr is not None:
|
if a.on_setattr is not None:
|
||||||
if frozen is True and a.on_setattr is not setters.NO_OP:
|
if frozen is True:
|
||||||
msg = "Frozen classes can't use on_setattr."
|
msg = "Frozen classes can't use on_setattr."
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
@@ -2186,9 +2126,8 @@ def _attrs_to_init_script(
|
|||||||
)
|
)
|
||||||
lines.extend(extra_lines)
|
lines.extend(extra_lines)
|
||||||
|
|
||||||
args = [] # Parameters in the definition of __init__
|
args = []
|
||||||
pre_init_args = [] # Parameters in the call to __attrs_pre_init__
|
kw_only_args = []
|
||||||
kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
|
|
||||||
attrs_to_validate = []
|
attrs_to_validate = []
|
||||||
|
|
||||||
# This is a dictionary of names to validator and converter callables.
|
# This is a dictionary of names to validator and converter callables.
|
||||||
@@ -2266,7 +2205,6 @@ def _attrs_to_init_script(
|
|||||||
kw_only_args.append(arg)
|
kw_only_args.append(arg)
|
||||||
else:
|
else:
|
||||||
args.append(arg)
|
args.append(arg)
|
||||||
pre_init_args.append(arg_name)
|
|
||||||
|
|
||||||
if converter is not None:
|
if converter is not None:
|
||||||
lines.append(
|
lines.append(
|
||||||
@@ -2286,7 +2224,6 @@ def _attrs_to_init_script(
|
|||||||
kw_only_args.append(arg)
|
kw_only_args.append(arg)
|
||||||
else:
|
else:
|
||||||
args.append(arg)
|
args.append(arg)
|
||||||
pre_init_args.append(arg_name)
|
|
||||||
lines.append(f"if {arg_name} is not NOTHING:")
|
lines.append(f"if {arg_name} is not NOTHING:")
|
||||||
|
|
||||||
init_factory_name = _INIT_FACTORY_PAT % (a.name,)
|
init_factory_name = _INIT_FACTORY_PAT % (a.name,)
|
||||||
@@ -2329,7 +2266,6 @@ def _attrs_to_init_script(
|
|||||||
kw_only_args.append(arg_name)
|
kw_only_args.append(arg_name)
|
||||||
else:
|
else:
|
||||||
args.append(arg_name)
|
args.append(arg_name)
|
||||||
pre_init_args.append(arg_name)
|
|
||||||
|
|
||||||
if converter is not None:
|
if converter is not None:
|
||||||
lines.append(
|
lines.append(
|
||||||
@@ -2386,7 +2322,7 @@ def _attrs_to_init_script(
|
|||||||
lines.append(f"BaseException.__init__(self, {vals})")
|
lines.append(f"BaseException.__init__(self, {vals})")
|
||||||
|
|
||||||
args = ", ".join(args)
|
args = ", ".join(args)
|
||||||
pre_init_args = ", ".join(pre_init_args)
|
pre_init_args = args
|
||||||
if kw_only_args:
|
if kw_only_args:
|
||||||
# leading comma & kw_only args
|
# leading comma & kw_only args
|
||||||
args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
|
args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
|
||||||
@@ -2401,7 +2337,7 @@ def _attrs_to_init_script(
|
|||||||
pre_init_args += pre_init_kw_only_args
|
pre_init_args += pre_init_kw_only_args
|
||||||
|
|
||||||
if call_pre_init and pre_init_has_args:
|
if call_pre_init and pre_init_has_args:
|
||||||
# If pre init method has arguments, pass the values given to __init__.
|
# If pre init method has arguments, pass same arguments as `__init__`.
|
||||||
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
|
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
|
||||||
|
|
||||||
# Python <3.12 doesn't allow backslashes in f-strings.
|
# Python <3.12 doesn't allow backslashes in f-strings.
|
||||||
@@ -2440,8 +2376,6 @@ class Attribute:
|
|||||||
- ``name`` (`str`): The name of the attribute.
|
- ``name`` (`str`): The name of the attribute.
|
||||||
- ``alias`` (`str`): The __init__ parameter name of the attribute, after
|
- ``alias`` (`str`): The __init__ parameter name of the attribute, after
|
||||||
any explicit overrides and default private-attribute-name handling.
|
any explicit overrides and default private-attribute-name handling.
|
||||||
- ``alias_is_default`` (`bool`): Whether the ``alias`` was automatically
|
|
||||||
generated (``True``) or explicitly provided by the user (``False``).
|
|
||||||
- ``inherited`` (`bool`): Whether or not that attribute has been inherited
|
- ``inherited`` (`bool`): Whether or not that attribute has been inherited
|
||||||
from a base class.
|
from a base class.
|
||||||
- ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
|
- ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
|
||||||
@@ -2467,7 +2401,6 @@ class Attribute:
|
|||||||
equality checks and hashing anymore.
|
equality checks and hashing anymore.
|
||||||
.. versionadded:: 21.1.0 *eq_key* and *order_key*
|
.. versionadded:: 21.1.0 *eq_key* and *order_key*
|
||||||
.. versionadded:: 22.2.0 *alias*
|
.. versionadded:: 22.2.0 *alias*
|
||||||
.. versionadded:: 26.1.0 *alias_is_default*
|
|
||||||
|
|
||||||
For the full version history of the fields, see `attr.ib`.
|
For the full version history of the fields, see `attr.ib`.
|
||||||
"""
|
"""
|
||||||
@@ -2492,7 +2425,6 @@ class Attribute:
|
|||||||
"inherited",
|
"inherited",
|
||||||
"on_setattr",
|
"on_setattr",
|
||||||
"alias",
|
"alias",
|
||||||
"alias_is_default",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -2515,7 +2447,6 @@ class Attribute:
|
|||||||
order_key=None,
|
order_key=None,
|
||||||
on_setattr=None,
|
on_setattr=None,
|
||||||
alias=None,
|
alias=None,
|
||||||
alias_is_default=None,
|
|
||||||
):
|
):
|
||||||
eq, eq_key, order, order_key = _determine_attrib_eq_order(
|
eq, eq_key, order, order_key = _determine_attrib_eq_order(
|
||||||
cmp, eq_key or eq, order_key or order, True
|
cmp, eq_key or eq, order_key or order, True
|
||||||
@@ -2550,20 +2481,12 @@ class Attribute:
|
|||||||
bound_setattr("inherited", inherited)
|
bound_setattr("inherited", inherited)
|
||||||
bound_setattr("on_setattr", on_setattr)
|
bound_setattr("on_setattr", on_setattr)
|
||||||
bound_setattr("alias", alias)
|
bound_setattr("alias", alias)
|
||||||
bound_setattr(
|
|
||||||
"alias_is_default",
|
|
||||||
alias is None if alias_is_default is None else alias_is_default,
|
|
||||||
)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
raise FrozenInstanceError
|
raise FrozenInstanceError
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_counting_attr(
|
def from_counting_attr(cls, name: str, ca: _CountingAttr, type=None):
|
||||||
cls, name: str, ca: _CountingAttr, kw_only: bool, type=None
|
|
||||||
):
|
|
||||||
# The 'kw_only' argument is the class-level setting, and is used if the
|
|
||||||
# attribute itself does not explicitly set 'kw_only'.
|
|
||||||
# type holds the annotated value. deal with conflicts:
|
# type holds the annotated value. deal with conflicts:
|
||||||
if type is None:
|
if type is None:
|
||||||
type = ca.type
|
type = ca.type
|
||||||
@@ -2582,14 +2505,13 @@ class Attribute:
|
|||||||
ca.metadata,
|
ca.metadata,
|
||||||
type,
|
type,
|
||||||
ca.converter,
|
ca.converter,
|
||||||
kw_only if ca.kw_only is None else ca.kw_only,
|
ca.kw_only,
|
||||||
ca.eq,
|
ca.eq,
|
||||||
ca.eq_key,
|
ca.eq_key,
|
||||||
ca.order,
|
ca.order,
|
||||||
ca.order_key,
|
ca.order_key,
|
||||||
ca.on_setattr,
|
ca.on_setattr,
|
||||||
ca.alias,
|
ca.alias,
|
||||||
ca.alias is None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Don't use attrs.evolve since fields(Attribute) doesn't work
|
# Don't use attrs.evolve since fields(Attribute) doesn't work
|
||||||
@@ -2608,20 +2530,6 @@ class Attribute:
|
|||||||
|
|
||||||
new._setattrs(changes.items())
|
new._setattrs(changes.items())
|
||||||
|
|
||||||
if "alias" in changes and "alias_is_default" not in changes:
|
|
||||||
# Explicit alias provided -- no longer the default.
|
|
||||||
_OBJ_SETATTR.__get__(new)("alias_is_default", False)
|
|
||||||
elif (
|
|
||||||
"name" in changes
|
|
||||||
and "alias" not in changes
|
|
||||||
# Don't auto-generate alias if the user picked picked the old one.
|
|
||||||
and self.alias_is_default
|
|
||||||
):
|
|
||||||
# Name changed, alias was auto-generated -- update it.
|
|
||||||
_OBJ_SETATTR.__get__(new)(
|
|
||||||
"alias", _default_init_alias_for(new.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
# Don't use _add_pickle since fields(Attribute) doesn't work
|
# Don't use _add_pickle since fields(Attribute) doesn't work
|
||||||
@@ -2638,17 +2546,6 @@ class Attribute:
|
|||||||
"""
|
"""
|
||||||
Play nice with pickle.
|
Play nice with pickle.
|
||||||
"""
|
"""
|
||||||
if len(state) < len(self.__slots__):
|
|
||||||
# Pre-26.1.0 pickle without alias_is_default -- infer it
|
|
||||||
# heuristically.
|
|
||||||
state_dict = dict(zip(self.__slots__, state))
|
|
||||||
alias_is_default = state_dict.get(
|
|
||||||
"alias"
|
|
||||||
) is None or state_dict.get("alias") == _default_init_alias_for(
|
|
||||||
state_dict["name"]
|
|
||||||
)
|
|
||||||
state = (*state, alias_is_default)
|
|
||||||
|
|
||||||
self._setattrs(zip(self.__slots__, state))
|
self._setattrs(zip(self.__slots__, state))
|
||||||
|
|
||||||
def _setattrs(self, name_values_pairs):
|
def _setattrs(self, name_values_pairs):
|
||||||
@@ -2672,7 +2569,7 @@ _a = [
|
|||||||
name=name,
|
name=name,
|
||||||
default=NOTHING,
|
default=NOTHING,
|
||||||
validator=None,
|
validator=None,
|
||||||
repr=(name != "alias_is_default"),
|
repr=True,
|
||||||
cmp=None,
|
cmp=None,
|
||||||
eq=True,
|
eq=True,
|
||||||
order=False,
|
order=False,
|
||||||
@@ -2844,188 +2741,6 @@ class _CountingAttr:
|
|||||||
_CountingAttr = _add_eq(_add_repr(_CountingAttr))
|
_CountingAttr = _add_eq(_add_repr(_CountingAttr))
|
||||||
|
|
||||||
|
|
||||||
class ClassProps:
|
|
||||||
"""
|
|
||||||
Effective class properties as derived from parameters to `attr.s()` or
|
|
||||||
`define()` decorators.
|
|
||||||
|
|
||||||
This is the same data structure that *attrs* uses internally to decide how
|
|
||||||
to construct the final class.
|
|
||||||
|
|
||||||
Warning:
|
|
||||||
|
|
||||||
This feature is currently **experimental** and is not covered by our
|
|
||||||
strict backwards-compatibility guarantees.
|
|
||||||
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
is_exception (bool):
|
|
||||||
Whether the class is treated as an exception class.
|
|
||||||
|
|
||||||
is_slotted (bool):
|
|
||||||
Whether the class is `slotted <slotted classes>`.
|
|
||||||
|
|
||||||
has_weakref_slot (bool):
|
|
||||||
Whether the class has a slot for weak references.
|
|
||||||
|
|
||||||
is_frozen (bool):
|
|
||||||
Whether the class is frozen.
|
|
||||||
|
|
||||||
kw_only (KeywordOnly):
|
|
||||||
Whether / how the class enforces keyword-only arguments on the
|
|
||||||
``__init__`` method.
|
|
||||||
|
|
||||||
collected_fields_by_mro (bool):
|
|
||||||
Whether the class fields were collected by method resolution order.
|
|
||||||
That is, correctly but unlike `dataclasses`.
|
|
||||||
|
|
||||||
added_init (bool):
|
|
||||||
Whether the class has an *attrs*-generated ``__init__`` method.
|
|
||||||
|
|
||||||
added_repr (bool):
|
|
||||||
Whether the class has an *attrs*-generated ``__repr__`` method.
|
|
||||||
|
|
||||||
added_eq (bool):
|
|
||||||
Whether the class has *attrs*-generated equality methods.
|
|
||||||
|
|
||||||
added_ordering (bool):
|
|
||||||
Whether the class has *attrs*-generated ordering methods.
|
|
||||||
|
|
||||||
hashability (Hashability): How `hashable <hashing>` the class is.
|
|
||||||
|
|
||||||
added_match_args (bool):
|
|
||||||
Whether the class supports positional `match <match>` over its
|
|
||||||
fields.
|
|
||||||
|
|
||||||
added_str (bool):
|
|
||||||
Whether the class has an *attrs*-generated ``__str__`` method.
|
|
||||||
|
|
||||||
added_pickling (bool):
|
|
||||||
Whether the class has *attrs*-generated ``__getstate__`` and
|
|
||||||
``__setstate__`` methods for `pickle`.
|
|
||||||
|
|
||||||
on_setattr_hook (Callable[[Any, Attribute[Any], Any], Any] | None):
|
|
||||||
The class's ``__setattr__`` hook.
|
|
||||||
|
|
||||||
field_transformer (Callable[[Attribute[Any]], Attribute[Any]] | None):
|
|
||||||
The class's `field transformers <transform-fields>`.
|
|
||||||
|
|
||||||
.. versionadded:: 25.4.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Hashability(enum.Enum):
|
|
||||||
"""
|
|
||||||
The hashability of a class.
|
|
||||||
|
|
||||||
.. versionadded:: 25.4.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
HASHABLE = "hashable"
|
|
||||||
"""Write a ``__hash__``."""
|
|
||||||
HASHABLE_CACHED = "hashable_cache"
|
|
||||||
"""Write a ``__hash__`` and cache the hash."""
|
|
||||||
UNHASHABLE = "unhashable"
|
|
||||||
"""Set ``__hash__`` to ``None``."""
|
|
||||||
LEAVE_ALONE = "leave_alone"
|
|
||||||
"""Don't touch ``__hash__``."""
|
|
||||||
|
|
||||||
class KeywordOnly(enum.Enum):
|
|
||||||
"""
|
|
||||||
How attributes should be treated regarding keyword-only parameters.
|
|
||||||
|
|
||||||
.. versionadded:: 25.4.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
NO = "no"
|
|
||||||
"""Attributes are not keyword-only."""
|
|
||||||
YES = "yes"
|
|
||||||
"""Attributes in current class without kw_only=False are keyword-only."""
|
|
||||||
FORCE = "force"
|
|
||||||
"""All attributes are keyword-only."""
|
|
||||||
|
|
||||||
__slots__ = ( # noqa: RUF023 -- order matters for __init__
|
|
||||||
"is_exception",
|
|
||||||
"is_slotted",
|
|
||||||
"has_weakref_slot",
|
|
||||||
"is_frozen",
|
|
||||||
"kw_only",
|
|
||||||
"collected_fields_by_mro",
|
|
||||||
"added_init",
|
|
||||||
"added_repr",
|
|
||||||
"added_eq",
|
|
||||||
"added_ordering",
|
|
||||||
"hashability",
|
|
||||||
"added_match_args",
|
|
||||||
"added_str",
|
|
||||||
"added_pickling",
|
|
||||||
"on_setattr_hook",
|
|
||||||
"field_transformer",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
is_exception,
|
|
||||||
is_slotted,
|
|
||||||
has_weakref_slot,
|
|
||||||
is_frozen,
|
|
||||||
kw_only,
|
|
||||||
collected_fields_by_mro,
|
|
||||||
added_init,
|
|
||||||
added_repr,
|
|
||||||
added_eq,
|
|
||||||
added_ordering,
|
|
||||||
hashability,
|
|
||||||
added_match_args,
|
|
||||||
added_str,
|
|
||||||
added_pickling,
|
|
||||||
on_setattr_hook,
|
|
||||||
field_transformer,
|
|
||||||
):
|
|
||||||
self.is_exception = is_exception
|
|
||||||
self.is_slotted = is_slotted
|
|
||||||
self.has_weakref_slot = has_weakref_slot
|
|
||||||
self.is_frozen = is_frozen
|
|
||||||
self.kw_only = kw_only
|
|
||||||
self.collected_fields_by_mro = collected_fields_by_mro
|
|
||||||
self.added_init = added_init
|
|
||||||
self.added_repr = added_repr
|
|
||||||
self.added_eq = added_eq
|
|
||||||
self.added_ordering = added_ordering
|
|
||||||
self.hashability = hashability
|
|
||||||
self.added_match_args = added_match_args
|
|
||||||
self.added_str = added_str
|
|
||||||
self.added_pickling = added_pickling
|
|
||||||
self.on_setattr_hook = on_setattr_hook
|
|
||||||
self.field_transformer = field_transformer
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_hashable(self):
|
|
||||||
return (
|
|
||||||
self.hashability is ClassProps.Hashability.HASHABLE
|
|
||||||
or self.hashability is ClassProps.Hashability.HASHABLE_CACHED
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
_cas = [
|
|
||||||
Attribute(
|
|
||||||
name=name,
|
|
||||||
default=NOTHING,
|
|
||||||
validator=None,
|
|
||||||
repr=True,
|
|
||||||
cmp=None,
|
|
||||||
eq=True,
|
|
||||||
order=False,
|
|
||||||
hash=True,
|
|
||||||
init=True,
|
|
||||||
inherited=False,
|
|
||||||
alias=_default_init_alias_for(name),
|
|
||||||
)
|
|
||||||
for name in ClassProps.__slots__
|
|
||||||
]
|
|
||||||
|
|
||||||
ClassProps = _add_eq(_add_repr(ClassProps, attrs=_cas), attrs=_cas)
|
|
||||||
|
|
||||||
|
|
||||||
class Factory:
|
class Factory:
|
||||||
"""
|
"""
|
||||||
Stores a factory callable.
|
Stores a factory callable.
|
||||||
@@ -3133,7 +2848,9 @@ class Converter:
|
|||||||
value, field
|
value, field
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.__call__ = self.converter
|
self.__call__ = lambda value, instance, field: self.converter(
|
||||||
|
value, instance, field
|
||||||
|
)
|
||||||
|
|
||||||
rt = ex.get_return_type()
|
rt = ex.get_return_type()
|
||||||
if rt is not None:
|
if rt is not None:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from ._make import (
|
|||||||
attrib,
|
attrib,
|
||||||
attrs,
|
attrs,
|
||||||
)
|
)
|
||||||
from .exceptions import NotAnAttrsClassError, UnannotatedAttributeError
|
from .exceptions import UnannotatedAttributeError
|
||||||
|
|
||||||
|
|
||||||
def define(
|
def define(
|
||||||
@@ -43,7 +43,6 @@ def define(
|
|||||||
on_setattr=None,
|
on_setattr=None,
|
||||||
field_transformer=None,
|
field_transformer=None,
|
||||||
match_args=True,
|
match_args=True,
|
||||||
force_kw_only=False,
|
|
||||||
):
|
):
|
||||||
r"""
|
r"""
|
||||||
A class decorator that adds :term:`dunder methods` according to
|
A class decorator that adds :term:`dunder methods` according to
|
||||||
@@ -77,7 +76,7 @@ def define(
|
|||||||
sensible ``__ne__`` by default, so it *should* be enough to only
|
sensible ``__ne__`` by default, so it *should* be enough to only
|
||||||
implement ``__eq__`` in most cases).
|
implement ``__eq__`` in most cases).
|
||||||
|
|
||||||
Passing :data:`True` or :data:`False` to *init*, *repr*, *eq*, or *hash*
|
Passing True or False` to *init*, *repr*, *eq*, or *hash*
|
||||||
overrides whatever *auto_detect* would determine.
|
overrides whatever *auto_detect* would determine.
|
||||||
|
|
||||||
auto_exc (bool):
|
auto_exc (bool):
|
||||||
@@ -215,12 +214,8 @@ def define(
|
|||||||
5. Subclasses of a frozen class are frozen too.
|
5. Subclasses of a frozen class are frozen too.
|
||||||
|
|
||||||
kw_only (bool):
|
kw_only (bool):
|
||||||
Make attributes keyword-only in the generated ``__init__`` (if
|
Make all attributes keyword-only in the generated ``__init__`` (if
|
||||||
*init* is False, this parameter is ignored). Attributes that
|
*init* is False, this parameter is ignored).
|
||||||
explicitly set ``kw_only=False`` are not affected; base class
|
|
||||||
attributes are also not affected.
|
|
||||||
|
|
||||||
Also see *force_kw_only*.
|
|
||||||
|
|
||||||
weakref_slot (bool):
|
weakref_slot (bool):
|
||||||
Make instances weak-referenceable. This has no effect unless
|
Make instances weak-referenceable. This has no effect unless
|
||||||
@@ -249,15 +244,6 @@ def define(
|
|||||||
See also `issue #428
|
See also `issue #428
|
||||||
<https://github.com/python-attrs/attrs/issues/428>`_.
|
<https://github.com/python-attrs/attrs/issues/428>`_.
|
||||||
|
|
||||||
force_kw_only (bool):
|
|
||||||
A back-compat flag for restoring pre-25.4.0 behavior. If True and
|
|
||||||
``kw_only=True``, all attributes are made keyword-only, including
|
|
||||||
base class attributes, and those set to ``kw_only=False`` at the
|
|
||||||
attribute level. Defaults to False.
|
|
||||||
|
|
||||||
See also `issue #980
|
|
||||||
<https://github.com/python-attrs/attrs/issues/980>`_.
|
|
||||||
|
|
||||||
getstate_setstate (bool | None):
|
getstate_setstate (bool | None):
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -333,11 +319,6 @@ def define(
|
|||||||
.. versionadded:: 24.3.0
|
.. versionadded:: 24.3.0
|
||||||
Unless already present, a ``__replace__`` method is automatically
|
Unless already present, a ``__replace__`` method is automatically
|
||||||
created for `copy.replace` (Python 3.13+ only).
|
created for `copy.replace` (Python 3.13+ only).
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*kw_only* now only applies to attributes defined in the current class,
|
|
||||||
and respects attribute-level ``kw_only=False`` settings.
|
|
||||||
.. versionadded:: 25.4.0
|
|
||||||
Added *force_kw_only* to go back to the previous *kw_only* behavior.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -356,7 +337,6 @@ def define(
|
|||||||
- *auto_exc=True*
|
- *auto_exc=True*
|
||||||
- *auto_detect=True*
|
- *auto_detect=True*
|
||||||
- *order=False*
|
- *order=False*
|
||||||
- *force_kw_only=False*
|
|
||||||
- Some options that were only relevant on Python 2 or were kept around
|
- Some options that were only relevant on Python 2 or were kept around
|
||||||
for backwards-compatibility have been removed.
|
for backwards-compatibility have been removed.
|
||||||
|
|
||||||
@@ -386,7 +366,6 @@ def define(
|
|||||||
on_setattr=on_setattr,
|
on_setattr=on_setattr,
|
||||||
field_transformer=field_transformer,
|
field_transformer=field_transformer,
|
||||||
match_args=match_args,
|
match_args=match_args,
|
||||||
force_kw_only=force_kw_only,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def wrap(cls):
|
def wrap(cls):
|
||||||
@@ -445,7 +424,7 @@ def field(
|
|||||||
type=None,
|
type=None,
|
||||||
converter=None,
|
converter=None,
|
||||||
factory=None,
|
factory=None,
|
||||||
kw_only=None,
|
kw_only=False,
|
||||||
eq=None,
|
eq=None,
|
||||||
order=None,
|
order=None,
|
||||||
on_setattr=None,
|
on_setattr=None,
|
||||||
@@ -571,10 +550,9 @@ def field(
|
|||||||
itself. You can use it as part of your own code or for `static type
|
itself. You can use it as part of your own code or for `static type
|
||||||
checking <types>`.
|
checking <types>`.
|
||||||
|
|
||||||
kw_only (bool | None):
|
kw_only (bool):
|
||||||
Make this attribute keyword-only in the generated ``__init__`` (if
|
Make this attribute keyword-only in the generated ``__init__`` (if
|
||||||
*init* is False, this parameter is ignored). If None (default),
|
``init`` is False, this parameter is ignored).
|
||||||
mirror the setting from `attrs.define`.
|
|
||||||
|
|
||||||
on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]):
|
on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]):
|
||||||
Allows to overwrite the *on_setattr* setting from `attr.s`. If left
|
Allows to overwrite the *on_setattr* setting from `attr.s`. If left
|
||||||
@@ -594,9 +572,6 @@ def field(
|
|||||||
.. versionadded:: 23.1.0
|
.. versionadded:: 23.1.0
|
||||||
The *type* parameter has been re-added; mostly for `attrs.make_class`.
|
The *type* parameter has been re-added; mostly for `attrs.make_class`.
|
||||||
Please note that type checkers ignore this metadata.
|
Please note that type checkers ignore this metadata.
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*kw_only* can now be None, and its default is also changed from False to
|
|
||||||
None.
|
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
@@ -646,29 +621,3 @@ def astuple(inst, *, recurse=True, filter=None):
|
|||||||
return _astuple(
|
return _astuple(
|
||||||
inst=inst, recurse=recurse, filter=filter, retain_collection_types=True
|
inst=inst, recurse=recurse, filter=filter, retain_collection_types=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def inspect(cls):
|
|
||||||
"""
|
|
||||||
Inspect the class and return its effective build parameters.
|
|
||||||
|
|
||||||
Warning:
|
|
||||||
This feature is currently **experimental** and is not covered by our
|
|
||||||
strict backwards-compatibility guarantees.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
cls: The *attrs*-decorated class to inspect.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The effective build parameters of the class.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
NotAnAttrsClassError: If the class is not an *attrs*-decorated class.
|
|
||||||
|
|
||||||
.. versionadded:: 25.4.0
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return cls.__dict__["__attrs_props__"]
|
|
||||||
except KeyError:
|
|
||||||
msg = f"{cls!r} is not an attrs-decorated class."
|
|
||||||
raise NotAnAttrsClassError(msg) from None
|
|
||||||
|
|||||||
@@ -84,6 +84,3 @@ class VersionInfo:
|
|||||||
# Since alphabetically "dev0" < "final" < "post1" < "post2", we don't
|
# Since alphabetically "dev0" < "final" < "post1" < "post2", we don't
|
||||||
# have to do anything special with releaselevel for now.
|
# have to do anything special with releaselevel for now.
|
||||||
return us < them
|
return us < them
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.year, self.minor, self.micro, self.releaselevel))
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
|
|
||||||
class FrozenError(AttributeError):
|
class FrozenError(AttributeError):
|
||||||
"""
|
"""
|
||||||
@@ -14,10 +16,8 @@ class FrozenError(AttributeError):
|
|||||||
.. versionadded:: 20.1.0
|
.. versionadded:: 20.1.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
msg = "can't set attribute"
|
||||||
msg = "can't set attribute"
|
args: ClassVar[tuple[str]] = [msg]
|
||||||
super().__init__(msg)
|
|
||||||
self.msg = msg
|
|
||||||
|
|
||||||
|
|
||||||
class FrozenInstanceError(FrozenError):
|
class FrozenInstanceError(FrozenError):
|
||||||
|
|||||||
@@ -79,14 +79,12 @@ def disabled():
|
|||||||
This context manager is not thread-safe!
|
This context manager is not thread-safe!
|
||||||
|
|
||||||
.. versionadded:: 21.3.0
|
.. versionadded:: 21.3.0
|
||||||
.. versionchanged:: 26.1.0 The contextmanager is nestable.
|
|
||||||
"""
|
"""
|
||||||
prev = get_run_validators()
|
|
||||||
set_run_validators(False)
|
set_run_validators(False)
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
set_run_validators(prev)
|
set_run_validators(True)
|
||||||
|
|
||||||
|
|
||||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||||
@@ -363,32 +361,26 @@ def deep_iterable(member_validator, iterable_validator=None):
|
|||||||
A validator that performs deep validation of an iterable.
|
A validator that performs deep validation of an iterable.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
member_validator: Validator(s) to apply to iterable members.
|
member_validator: Validator to apply to iterable members.
|
||||||
|
|
||||||
iterable_validator:
|
iterable_validator:
|
||||||
Validator(s) to apply to iterable itself (optional).
|
Validator to apply to iterable itself (optional).
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
TypeError: if any sub-validators fail
|
TypeError: if any sub-validators fail
|
||||||
|
|
||||||
.. versionadded:: 19.1.0
|
.. versionadded:: 19.1.0
|
||||||
|
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*member_validator* and *iterable_validator* can now be a list or tuple
|
|
||||||
of validators.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(member_validator, (list, tuple)):
|
if isinstance(member_validator, (list, tuple)):
|
||||||
member_validator = and_(*member_validator)
|
member_validator = and_(*member_validator)
|
||||||
if isinstance(iterable_validator, (list, tuple)):
|
|
||||||
iterable_validator = and_(*iterable_validator)
|
|
||||||
return _DeepIterable(member_validator, iterable_validator)
|
return _DeepIterable(member_validator, iterable_validator)
|
||||||
|
|
||||||
|
|
||||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||||
class _DeepMapping:
|
class _DeepMapping:
|
||||||
key_validator = attrib(validator=optional(is_callable()))
|
key_validator = attrib(validator=is_callable())
|
||||||
value_validator = attrib(validator=optional(is_callable()))
|
value_validator = attrib(validator=is_callable())
|
||||||
mapping_validator = attrib(validator=optional(is_callable()))
|
mapping_validator = attrib(default=None, validator=optional(is_callable()))
|
||||||
|
|
||||||
def __call__(self, inst, attr, value):
|
def __call__(self, inst, attr, value):
|
||||||
"""
|
"""
|
||||||
@@ -398,62 +390,30 @@ class _DeepMapping:
|
|||||||
self.mapping_validator(inst, attr, value)
|
self.mapping_validator(inst, attr, value)
|
||||||
|
|
||||||
for key in value:
|
for key in value:
|
||||||
if self.key_validator is not None:
|
self.key_validator(inst, attr, key)
|
||||||
self.key_validator(inst, attr, key)
|
self.value_validator(inst, attr, value[key])
|
||||||
if self.value_validator is not None:
|
|
||||||
self.value_validator(inst, attr, value[key])
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
|
return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
|
||||||
|
|
||||||
|
|
||||||
def deep_mapping(
|
def deep_mapping(key_validator, value_validator, mapping_validator=None):
|
||||||
key_validator=None, value_validator=None, mapping_validator=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
A validator that performs deep validation of a dictionary.
|
A validator that performs deep validation of a dictionary.
|
||||||
|
|
||||||
All validators are optional, but at least one of *key_validator* or
|
|
||||||
*value_validator* must be provided.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key_validator: Validator(s) to apply to dictionary keys.
|
key_validator: Validator to apply to dictionary keys.
|
||||||
|
|
||||||
value_validator: Validator(s) to apply to dictionary values.
|
value_validator: Validator to apply to dictionary values.
|
||||||
|
|
||||||
mapping_validator:
|
mapping_validator:
|
||||||
Validator(s) to apply to top-level mapping attribute.
|
Validator to apply to top-level mapping attribute (optional).
|
||||||
|
|
||||||
.. versionadded:: 19.1.0
|
.. versionadded:: 19.1.0
|
||||||
|
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*key_validator* and *value_validator* are now optional, but at least one
|
|
||||||
of them must be provided.
|
|
||||||
|
|
||||||
.. versionchanged:: 25.4.0
|
|
||||||
*key_validator*, *value_validator*, and *mapping_validator* can now be a
|
|
||||||
list or tuple of validators.
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
TypeError: If any sub-validator fails on validation.
|
TypeError: if any sub-validators fail
|
||||||
|
|
||||||
ValueError:
|
|
||||||
If neither *key_validator* nor *value_validator* is provided on
|
|
||||||
instantiation.
|
|
||||||
"""
|
"""
|
||||||
if key_validator is None and value_validator is None:
|
|
||||||
msg = (
|
|
||||||
"At least one of key_validator or value_validator must be provided"
|
|
||||||
)
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
if isinstance(key_validator, (list, tuple)):
|
|
||||||
key_validator = and_(*key_validator)
|
|
||||||
if isinstance(value_validator, (list, tuple)):
|
|
||||||
value_validator = and_(*value_validator)
|
|
||||||
if isinstance(mapping_validator, (list, tuple)):
|
|
||||||
mapping_validator = and_(*mapping_validator)
|
|
||||||
|
|
||||||
return _DeepMapping(key_validator, value_validator, mapping_validator)
|
return _DeepMapping(key_validator, value_validator, mapping_validator)
|
||||||
|
|
||||||
|
|
||||||
@@ -525,7 +485,7 @@ def gt(val):
|
|||||||
A validator that raises `ValueError` if the initializer is called with a
|
A validator that raises `ValueError` if the initializer is called with a
|
||||||
number smaller or equal to *val*.
|
number smaller or equal to *val*.
|
||||||
|
|
||||||
The validator uses `operator.gt` to compare the values.
|
The validator uses `operator.ge` to compare the values.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
val: Exclusive lower bound for values
|
val: Exclusive lower bound for values
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ _T = TypeVar("_T")
|
|||||||
_T1 = TypeVar("_T1")
|
_T1 = TypeVar("_T1")
|
||||||
_T2 = TypeVar("_T2")
|
_T2 = TypeVar("_T2")
|
||||||
_T3 = TypeVar("_T3")
|
_T3 = TypeVar("_T3")
|
||||||
_T4 = TypeVar("_T4")
|
|
||||||
_T5 = TypeVar("_T5")
|
|
||||||
_T6 = TypeVar("_T6")
|
|
||||||
_I = TypeVar("_I", bound=Iterable)
|
_I = TypeVar("_I", bound=Iterable)
|
||||||
_K = TypeVar("_K")
|
_K = TypeVar("_K")
|
||||||
_V = TypeVar("_V")
|
_V = TypeVar("_V")
|
||||||
@@ -54,7 +51,7 @@ def optional(
|
|||||||
validator: (
|
validator: (
|
||||||
_ValidatorType[_T]
|
_ValidatorType[_T]
|
||||||
| list[_ValidatorType[_T]]
|
| list[_ValidatorType[_T]]
|
||||||
| tuple[_ValidatorType[_T], ...]
|
| tuple[_ValidatorType[_T]]
|
||||||
),
|
),
|
||||||
) -> _ValidatorType[_T | None]: ...
|
) -> _ValidatorType[_T | None]: ...
|
||||||
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
|
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
|
||||||
@@ -66,19 +63,12 @@ def matches_re(
|
|||||||
) -> _ValidatorType[AnyStr]: ...
|
) -> _ValidatorType[AnyStr]: ...
|
||||||
def deep_iterable(
|
def deep_iterable(
|
||||||
member_validator: _ValidatorArgType[_T],
|
member_validator: _ValidatorArgType[_T],
|
||||||
iterable_validator: _ValidatorArgType[_I] | None = ...,
|
iterable_validator: _ValidatorType[_I] | None = ...,
|
||||||
) -> _ValidatorType[_I]: ...
|
) -> _ValidatorType[_I]: ...
|
||||||
@overload
|
|
||||||
def deep_mapping(
|
def deep_mapping(
|
||||||
key_validator: _ValidatorArgType[_K],
|
key_validator: _ValidatorType[_K],
|
||||||
value_validator: _ValidatorArgType[_V] | None = ...,
|
value_validator: _ValidatorType[_V],
|
||||||
mapping_validator: _ValidatorArgType[_M] | None = ...,
|
mapping_validator: _ValidatorType[_M] | None = ...,
|
||||||
) -> _ValidatorType[_M]: ...
|
|
||||||
@overload
|
|
||||||
def deep_mapping(
|
|
||||||
key_validator: _ValidatorArgType[_K] | None = ...,
|
|
||||||
value_validator: _ValidatorArgType[_V] = ...,
|
|
||||||
mapping_validator: _ValidatorArgType[_M] | None = ...,
|
|
||||||
) -> _ValidatorType[_M]: ...
|
) -> _ValidatorType[_M]: ...
|
||||||
def is_callable() -> _ValidatorType[_T]: ...
|
def is_callable() -> _ValidatorType[_T]: ...
|
||||||
def lt(val: _T) -> _ValidatorType[_T]: ...
|
def lt(val: _T) -> _ValidatorType[_T]: ...
|
||||||
@@ -93,48 +83,4 @@ def not_(
|
|||||||
msg: str | None = None,
|
msg: str | None = None,
|
||||||
exc_types: type[Exception] | Iterable[type[Exception]] = ...,
|
exc_types: type[Exception] | Iterable[type[Exception]] = ...,
|
||||||
) -> _ValidatorType[_T]: ...
|
) -> _ValidatorType[_T]: ...
|
||||||
@overload
|
def or_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
|
||||||
def or_(
|
|
||||||
__v1: _ValidatorType[_T1],
|
|
||||||
__v2: _ValidatorType[_T2],
|
|
||||||
) -> _ValidatorType[_T1 | _T2]: ...
|
|
||||||
@overload
|
|
||||||
def or_(
|
|
||||||
__v1: _ValidatorType[_T1],
|
|
||||||
__v2: _ValidatorType[_T2],
|
|
||||||
__v3: _ValidatorType[_T3],
|
|
||||||
) -> _ValidatorType[_T1 | _T2 | _T3]: ...
|
|
||||||
@overload
|
|
||||||
def or_(
|
|
||||||
__v1: _ValidatorType[_T1],
|
|
||||||
__v2: _ValidatorType[_T2],
|
|
||||||
__v3: _ValidatorType[_T3],
|
|
||||||
__v4: _ValidatorType[_T4],
|
|
||||||
) -> _ValidatorType[_T1 | _T2 | _T3 | _T4]: ...
|
|
||||||
@overload
|
|
||||||
def or_(
|
|
||||||
__v1: _ValidatorType[_T1],
|
|
||||||
__v2: _ValidatorType[_T2],
|
|
||||||
__v3: _ValidatorType[_T3],
|
|
||||||
__v4: _ValidatorType[_T4],
|
|
||||||
__v5: _ValidatorType[_T5],
|
|
||||||
) -> _ValidatorType[_T1 | _T2 | _T3 | _T4 | _T5]: ...
|
|
||||||
@overload
|
|
||||||
def or_(
|
|
||||||
__v1: _ValidatorType[_T1],
|
|
||||||
__v2: _ValidatorType[_T2],
|
|
||||||
__v3: _ValidatorType[_T3],
|
|
||||||
__v4: _ValidatorType[_T4],
|
|
||||||
__v5: _ValidatorType[_T5],
|
|
||||||
__v6: _ValidatorType[_T6],
|
|
||||||
) -> _ValidatorType[_T1 | _T2 | _T3 | _T4 | _T5 | _T6]: ...
|
|
||||||
@overload
|
|
||||||
def or_(
|
|
||||||
__v1: _ValidatorType[Any],
|
|
||||||
__v2: _ValidatorType[Any],
|
|
||||||
__v3: _ValidatorType[Any],
|
|
||||||
__v4: _ValidatorType[Any],
|
|
||||||
__v5: _ValidatorType[Any],
|
|
||||||
__v6: _ValidatorType[Any],
|
|
||||||
*validators: _ValidatorType[Any],
|
|
||||||
) -> _ValidatorType[Any]: ...
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
+60
-27
@@ -1,6 +1,6 @@
|
|||||||
Metadata-Version: 2.4
|
Metadata-Version: 2.4
|
||||||
Name: attrs
|
Name: attrs
|
||||||
Version: 26.1.0
|
Version: 25.3.0
|
||||||
Summary: Classes Without Boilerplate
|
Summary: Classes Without Boilerplate
|
||||||
Project-URL: Documentation, https://www.attrs.org/
|
Project-URL: Documentation, https://www.attrs.org/
|
||||||
Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html
|
Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html
|
||||||
@@ -12,16 +12,62 @@ License-Expression: MIT
|
|||||||
License-File: LICENSE
|
License-File: LICENSE
|
||||||
Keywords: attribute,boilerplate,class
|
Keywords: attribute,boilerplate,class
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Programming Language :: Python :: 3.8
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
Classifier: Programming Language :: Python :: 3.11
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
Classifier: Programming Language :: Python :: 3.12
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
Classifier: Programming Language :: Python :: 3.13
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
Classifier: Programming Language :: Python :: 3.14
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
Classifier: Typing :: Typed
|
Classifier: Typing :: Typed
|
||||||
Requires-Python: >=3.9
|
Requires-Python: >=3.8
|
||||||
|
Provides-Extra: benchmark
|
||||||
|
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'benchmark'
|
||||||
|
Requires-Dist: hypothesis; extra == 'benchmark'
|
||||||
|
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'benchmark'
|
||||||
|
Requires-Dist: pympler; extra == 'benchmark'
|
||||||
|
Requires-Dist: pytest-codspeed; extra == 'benchmark'
|
||||||
|
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'benchmark'
|
||||||
|
Requires-Dist: pytest-xdist[psutil]; extra == 'benchmark'
|
||||||
|
Requires-Dist: pytest>=4.3.0; extra == 'benchmark'
|
||||||
|
Provides-Extra: cov
|
||||||
|
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'cov'
|
||||||
|
Requires-Dist: coverage[toml]>=5.3; extra == 'cov'
|
||||||
|
Requires-Dist: hypothesis; extra == 'cov'
|
||||||
|
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'cov'
|
||||||
|
Requires-Dist: pympler; extra == 'cov'
|
||||||
|
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'cov'
|
||||||
|
Requires-Dist: pytest-xdist[psutil]; extra == 'cov'
|
||||||
|
Requires-Dist: pytest>=4.3.0; extra == 'cov'
|
||||||
|
Provides-Extra: dev
|
||||||
|
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'dev'
|
||||||
|
Requires-Dist: hypothesis; extra == 'dev'
|
||||||
|
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'dev'
|
||||||
|
Requires-Dist: pre-commit-uv; extra == 'dev'
|
||||||
|
Requires-Dist: pympler; extra == 'dev'
|
||||||
|
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'dev'
|
||||||
|
Requires-Dist: pytest-xdist[psutil]; extra == 'dev'
|
||||||
|
Requires-Dist: pytest>=4.3.0; extra == 'dev'
|
||||||
|
Provides-Extra: docs
|
||||||
|
Requires-Dist: cogapp; extra == 'docs'
|
||||||
|
Requires-Dist: furo; extra == 'docs'
|
||||||
|
Requires-Dist: myst-parser; extra == 'docs'
|
||||||
|
Requires-Dist: sphinx; extra == 'docs'
|
||||||
|
Requires-Dist: sphinx-notfound-page; extra == 'docs'
|
||||||
|
Requires-Dist: sphinxcontrib-towncrier; extra == 'docs'
|
||||||
|
Requires-Dist: towncrier; extra == 'docs'
|
||||||
|
Provides-Extra: tests
|
||||||
|
Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'tests'
|
||||||
|
Requires-Dist: hypothesis; extra == 'tests'
|
||||||
|
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests'
|
||||||
|
Requires-Dist: pympler; extra == 'tests'
|
||||||
|
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests'
|
||||||
|
Requires-Dist: pytest-xdist[psutil]; extra == 'tests'
|
||||||
|
Requires-Dist: pytest>=4.3.0; extra == 'tests'
|
||||||
|
Provides-Extra: tests-mypy
|
||||||
|
Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests-mypy'
|
||||||
|
Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests-mypy'
|
||||||
Description-Content-Type: text/markdown
|
Description-Content-Type: text/markdown
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@@ -32,7 +78,7 @@ Description-Content-Type: text/markdown
|
|||||||
|
|
||||||
|
|
||||||
*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)).
|
*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)).
|
||||||
Trusted by NASA for [Mars missions since 2020](https://github.com/readme/featured/nasa-ingenuity-helicopter)!
|
[Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020!
|
||||||
|
|
||||||
Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
|
Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
|
||||||
|
|
||||||
@@ -50,15 +96,15 @@ Especially those generously supporting us at the *The Organization* tier and hig
|
|||||||
import pathlib, tomllib
|
import pathlib, tomllib
|
||||||
|
|
||||||
for sponsor in tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["sponcon"]["sponsors"]:
|
for sponsor in tomllib.loads(pathlib.Path("pyproject.toml").read_text())["tool"]["sponcon"]["sponsors"]:
|
||||||
print(f'<a href="{sponsor["url"]}"><img title="{sponsor["title"]}" src="https://www.attrs.org/en/26.1.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>')
|
print(f'<a href="{sponsor["url"]}"><img title="{sponsor["title"]}" src="https://www.attrs.org/en/25.3.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>')
|
||||||
]]] -->
|
]]] -->
|
||||||
<a href="https://www.variomedia.de/"><img title="Variomedia AG" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Variomedia.svg" width="190" /></a>
|
<a href="https://www.variomedia.de/"><img title="Variomedia AG" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Variomedia.svg" width="190" /></a>
|
||||||
<a href="https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek"><img title="Tidelift" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Tidelift.svg" width="190" /></a>
|
<a href="https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek"><img title="Tidelift" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Tidelift.svg" width="190" /></a>
|
||||||
<a href="https://kraken.tech/"><img title="Kraken Tech" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Kraken.svg" width="190" /></a>
|
<a href="https://klaviyo.com/"><img title="Klaviyo" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Klaviyo.svg" width="190" /></a>
|
||||||
<a href="https://privacy-solutions.org/"><img title="Privacy Solutions" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Privacy-Solutions.svg" width="190" /></a>
|
<a href="https://privacy-solutions.org/"><img title="Privacy Solutions" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Privacy-Solutions.svg" width="190" /></a>
|
||||||
<a href="https://filepreviews.io/"><img title="FilePreviews" src="https://www.attrs.org/en/26.1.0/_static/sponsors/FilePreviews.svg" width="190" /></a>
|
<a href="https://www.emsys-renewables.com/"><img title="emsys renewables" src="https://www.attrs.org/en/25.3.0/_static/sponsors/emsys-renewables.svg" width="190" /></a>
|
||||||
<a href="https://www.testmuai.com/?utm_medium=sponsor&utm_source=structlog"><img title="TestMu AI" src="https://www.attrs.org/en/26.1.0/_static/sponsors/TestMu-AI.svg" width="190" /></a>
|
<a href="https://filepreviews.io/"><img title="FilePreviews" src="https://www.attrs.org/en/25.3.0/_static/sponsors/FilePreviews.svg" width="190" /></a>
|
||||||
<a href="https://polar.sh/"><img title="Polar" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Polar.svg" width="190" /></a>
|
<a href="https://polar.sh/"><img title="Polar" src="https://www.attrs.org/en/25.3.0/_static/sponsors/Polar.svg" width="190" /></a>
|
||||||
<!-- [[[end]]] -->
|
<!-- [[[end]]] -->
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
@@ -174,23 +220,10 @@ Save time, reduce risk, and improve code health, while paying the maintainers of
|
|||||||
|
|
||||||
## Release Information
|
## Release Information
|
||||||
|
|
||||||
### Backwards-incompatible Changes
|
|
||||||
|
|
||||||
- Field aliases are now resolved *before* calling `field_transformer`, so transformers receive fully populated `Attribute` objects with usable `alias` values instead of `None`.
|
|
||||||
The new `Attribute.alias_is_default` flag indicates whether the alias was auto-generated (`True`) or explicitly set by the user (`False`).
|
|
||||||
[#1509](https://github.com/python-attrs/attrs/issues/1509)
|
|
||||||
|
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Fix type annotations for `attrs.validators.optional()`, so it no longer rejects tuples with more than one validator.
|
- Restore support for generator-based `field_transformer`s.
|
||||||
[#1496](https://github.com/python-attrs/attrs/issues/1496)
|
[#1417](https://github.com/python-attrs/attrs/issues/1417)
|
||||||
- The `attrs.validators.disabled()` contextmanager can now be nested.
|
|
||||||
[#1513](https://github.com/python-attrs/attrs/issues/1513)
|
|
||||||
- Frozen classes can set `on_setattr=attrs.setters.NO_OP` in addition to `None`.
|
|
||||||
[#1515](https://github.com/python-attrs/attrs/issues/1515)
|
|
||||||
- It's now possible to pass *attrs* **instances** in addition to *attrs* **classes** to `attrs.fields()`.
|
|
||||||
[#1529](https://github.com/python-attrs/attrs/issues/1529)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057
|
||||||
|
attr/__init__.pyi,sha256=QIXnnHPoucmDWkbpNsWTP-cgJ1bn8le7DjyRa_wYdew,11281
|
||||||
|
attr/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_cmp.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_compat.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_config.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_funcs.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_make.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_next_gen.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/_version_info.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/converters.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/exceptions.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/filters.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/setters.cpython-311.pyc,,
|
||||||
|
attr/__pycache__/validators.cpython-311.pyc,,
|
||||||
|
attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117
|
||||||
|
attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368
|
||||||
|
attr/_compat.py,sha256=4hlXbWhdDjQCDK6FKF1EgnZ3POiHgtpp54qE0nxaGHg,2704
|
||||||
|
attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843
|
||||||
|
attr/_funcs.py,sha256=5-tUKJtp3h5El55EcDl6GWXFp68fT8D8U7uCRN6497I,15854
|
||||||
|
attr/_make.py,sha256=lBUPPmxiA1BeHzB6OlHoCEh--tVvM1ozXO8eXOa6g4c,96664
|
||||||
|
attr/_next_gen.py,sha256=7FRkbtl_N017SuBhf_Vw3mw2c2pGZhtCGOzadgz7tp4,24395
|
||||||
|
attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469
|
||||||
|
attr/_version_info.py,sha256=exSqb3b5E-fMSsgZAlEw9XcLpEgobPORCZpcaEglAM4,2121
|
||||||
|
attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209
|
||||||
|
attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861
|
||||||
|
attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643
|
||||||
|
attr/exceptions.py,sha256=HRFq4iybmv7-DcZwyjl6M1euM2YeJVK_hFxuaBGAngI,1977
|
||||||
|
attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539
|
||||||
|
attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795
|
||||||
|
attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208
|
||||||
|
attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617
|
||||||
|
attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584
|
||||||
|
attr/validators.py,sha256=WaB1HLAHHqRHWsrv_K9H-sJ7ESil3H3Cmv2d8TtVZx4,20046
|
||||||
|
attr/validators.pyi,sha256=s2WhKPqskxbsckJfKk8zOuuB088GfgpyxcCYSNFLqNU,2603
|
||||||
|
attrs-25.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
attrs-25.3.0.dist-info/METADATA,sha256=W38cREj7s1wqNf1fg4hVwZmL1xh0AdSp4IhtTMROinw,10993
|
||||||
|
attrs-25.3.0.dist-info/RECORD,,
|
||||||
|
attrs-25.3.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
attrs-25.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
||||||
|
attrs-25.3.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109
|
||||||
|
attrs/__init__.py,sha256=qeQJZ4O08yczSn840v9bYOaZyRE81WsVi-QCrY3krCU,1107
|
||||||
|
attrs/__init__.pyi,sha256=nZmInocjM7tHV4AQw0vxO_fo6oJjL_PonlV9zKKW8DY,7931
|
||||||
|
attrs/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
attrs/__pycache__/converters.cpython-311.pyc,,
|
||||||
|
attrs/__pycache__/exceptions.cpython-311.pyc,,
|
||||||
|
attrs/__pycache__/filters.cpython-311.pyc,,
|
||||||
|
attrs/__pycache__/setters.cpython-311.pyc,,
|
||||||
|
attrs/__pycache__/validators.cpython-311.pyc,,
|
||||||
|
attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76
|
||||||
|
attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76
|
||||||
|
attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73
|
||||||
|
attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73
|
||||||
|
attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Wheel-Version: 1.0
|
Wheel-Version: 1.0
|
||||||
Generator: hatchling 1.29.0
|
Generator: hatchling 1.27.0
|
||||||
Root-Is-Purelib: true
|
Root-Is-Purelib: true
|
||||||
Tag: py3-none-any
|
Tag: py3-none-any
|
||||||
@@ -1 +0,0 @@
|
|||||||
uv
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057
|
|
||||||
attr/__init__.pyi,sha256=pVGImAUVovq2_TYl_r_HIYnGlyOaoCuEhxo-EvsnnSc,11325
|
|
||||||
attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117
|
|
||||||
attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368
|
|
||||||
attr/_compat.py,sha256=x0g7iEUOnBVJC72zyFCgb1eKqyxS-7f2LGnNyZ_r95s,2829
|
|
||||||
attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843
|
|
||||||
attr/_funcs.py,sha256=Ix5IETTfz5F01F-12MF_CSFomIn2h8b67EVVz2gCtBE,16479
|
|
||||||
attr/_make.py,sha256=H7OH2eWS5CnBzLUjNFE1WymfPrmF1r8fv2RPdt9MuYA,106129
|
|
||||||
attr/_next_gen.py,sha256=BQtCUlzwg2gWHTYXBQvrEYBnzBUrDvO57u0Py6UCPhc,26274
|
|
||||||
attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469
|
|
||||||
attr/_version_info.py,sha256=w4R-FYC3NK_kMkGUWJlYP4cVAlH9HRaC-um3fcjYkHM,2222
|
|
||||||
attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209
|
|
||||||
attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861
|
|
||||||
attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643
|
|
||||||
attr/exceptions.py,sha256=b4vMbnoQ3VpwWZhqrYi_ssXVCK8o2c4HQSS09cSUM9o,1990
|
|
||||||
attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539
|
|
||||||
attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795
|
|
||||||
attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208
|
|
||||||
attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617
|
|
||||||
attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584
|
|
||||||
attr/validators.py,sha256=m3QRzZTANr4f2C4eVdUoFg11NgXWak8Wat4qQTGhvcs,21553
|
|
||||||
attr/validators.pyi,sha256=gM1ZmHaBckyYWI2EirpRNzqm3B19cw5Iq6B4Kno9YCM,4087
|
|
||||||
attrs-26.1.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
|
||||||
attrs-26.1.0.dist-info/METADATA,sha256=TNQOaQ8jvzfLytNO_WdY4GLfHfB8hoM_fjzpW_H6OMw,8754
|
|
||||||
attrs-26.1.0.dist-info/RECORD,,
|
|
||||||
attrs-26.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
attrs-26.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
||||||
attrs-26.1.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109
|
|
||||||
attrs/__init__.py,sha256=RxaAZNwYiEh-fcvHLZNpQ_DWKni73M_jxEPEftiq1Zc,1183
|
|
||||||
attrs/__init__.pyi,sha256=2gV79g9UxJppGSM48hAZJ6h_MHb70dZoJL31ZNJeZYI,9416
|
|
||||||
attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76
|
|
||||||
attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76
|
|
||||||
attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73
|
|
||||||
attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73
|
|
||||||
attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76
|
|
||||||
@@ -22,8 +22,7 @@ from attr import (
|
|||||||
resolve_types,
|
resolve_types,
|
||||||
validate,
|
validate,
|
||||||
)
|
)
|
||||||
from attr._make import ClassProps
|
from attr._next_gen import asdict, astuple
|
||||||
from attr._next_gen import asdict, astuple, inspect
|
|
||||||
|
|
||||||
from . import converters, exceptions, filters, setters, validators
|
from . import converters, exceptions, filters, setters, validators
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ __all__ = [
|
|||||||
"NOTHING",
|
"NOTHING",
|
||||||
"Attribute",
|
"Attribute",
|
||||||
"AttrsInstance",
|
"AttrsInstance",
|
||||||
"ClassProps",
|
|
||||||
"Converter",
|
"Converter",
|
||||||
"Factory",
|
"Factory",
|
||||||
"NothingType",
|
"NothingType",
|
||||||
@@ -60,7 +58,6 @@ __all__ = [
|
|||||||
"filters",
|
"filters",
|
||||||
"frozen",
|
"frozen",
|
||||||
"has",
|
"has",
|
||||||
"inspect",
|
|
||||||
"make_class",
|
"make_class",
|
||||||
"mutable",
|
"mutable",
|
||||||
"resolve_types",
|
"resolve_types",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ def field(
|
|||||||
metadata: Mapping[Any, Any] | None = ...,
|
metadata: Mapping[Any, Any] | None = ...,
|
||||||
converter: None = ...,
|
converter: None = ...,
|
||||||
factory: None = ...,
|
factory: None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: bool | None = ...,
|
eq: bool | None = ...,
|
||||||
order: bool | None = ...,
|
order: bool | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -98,10 +98,10 @@ def field(
|
|||||||
metadata: Mapping[Any, Any] | None = ...,
|
metadata: Mapping[Any, Any] | None = ...,
|
||||||
converter: _ConverterType
|
converter: _ConverterType
|
||||||
| list[_ConverterType]
|
| list[_ConverterType]
|
||||||
| tuple[_ConverterType, ...]
|
| tuple[_ConverterType]
|
||||||
| None = ...,
|
| None = ...,
|
||||||
factory: Callable[[], _T] | None = ...,
|
factory: Callable[[], _T] | None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -121,10 +121,10 @@ def field(
|
|||||||
metadata: Mapping[Any, Any] | None = ...,
|
metadata: Mapping[Any, Any] | None = ...,
|
||||||
converter: _ConverterType
|
converter: _ConverterType
|
||||||
| list[_ConverterType]
|
| list[_ConverterType]
|
||||||
| tuple[_ConverterType, ...]
|
| tuple[_ConverterType]
|
||||||
| None = ...,
|
| None = ...,
|
||||||
factory: Callable[[], _T] | None = ...,
|
factory: Callable[[], _T] | None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -144,10 +144,10 @@ def field(
|
|||||||
metadata: Mapping[Any, Any] | None = ...,
|
metadata: Mapping[Any, Any] | None = ...,
|
||||||
converter: _ConverterType
|
converter: _ConverterType
|
||||||
| list[_ConverterType]
|
| list[_ConverterType]
|
||||||
| tuple[_ConverterType, ...]
|
| tuple[_ConverterType]
|
||||||
| None = ...,
|
| None = ...,
|
||||||
factory: Callable[[], _T] | None = ...,
|
factory: Callable[[], _T] | None = ...,
|
||||||
kw_only: bool | None = ...,
|
kw_only: bool = ...,
|
||||||
eq: _EqOrderType | None = ...,
|
eq: _EqOrderType | None = ...,
|
||||||
order: _EqOrderType | None = ...,
|
order: _EqOrderType | None = ...,
|
||||||
on_setattr: _OnSetAttrArgType | None = ...,
|
on_setattr: _OnSetAttrArgType | None = ...,
|
||||||
@@ -261,54 +261,3 @@ def frozen(
|
|||||||
field_transformer: _FieldTransformer | None = ...,
|
field_transformer: _FieldTransformer | None = ...,
|
||||||
match_args: bool = ...,
|
match_args: bool = ...,
|
||||||
) -> Callable[[_C], _C]: ...
|
) -> Callable[[_C], _C]: ...
|
||||||
|
|
||||||
class ClassProps:
|
|
||||||
# XXX: somehow when defining/using enums Mypy starts looking at our own
|
|
||||||
# (untyped) code and causes tons of errors.
|
|
||||||
Hashability: Any
|
|
||||||
KeywordOnly: Any
|
|
||||||
|
|
||||||
is_exception: bool
|
|
||||||
is_slotted: bool
|
|
||||||
has_weakref_slot: bool
|
|
||||||
is_frozen: bool
|
|
||||||
# kw_only: ClassProps.KeywordOnly
|
|
||||||
kw_only: Any
|
|
||||||
collected_fields_by_mro: bool
|
|
||||||
added_init: bool
|
|
||||||
added_repr: bool
|
|
||||||
added_eq: bool
|
|
||||||
added_ordering: bool
|
|
||||||
# hashability: ClassProps.Hashability
|
|
||||||
hashability: Any
|
|
||||||
added_match_args: bool
|
|
||||||
added_str: bool
|
|
||||||
added_pickling: bool
|
|
||||||
on_setattr_hook: _OnSetAttrType | None
|
|
||||||
field_transformer: Callable[[Attribute[Any]], Attribute[Any]] | None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
is_exception: bool,
|
|
||||||
is_slotted: bool,
|
|
||||||
has_weakref_slot: bool,
|
|
||||||
is_frozen: bool,
|
|
||||||
# kw_only: ClassProps.KeywordOnly
|
|
||||||
kw_only: Any,
|
|
||||||
collected_fields_by_mro: bool,
|
|
||||||
added_init: bool,
|
|
||||||
added_repr: bool,
|
|
||||||
added_eq: bool,
|
|
||||||
added_ordering: bool,
|
|
||||||
# hashability: ClassProps.Hashability
|
|
||||||
hashability: Any,
|
|
||||||
added_match_args: bool,
|
|
||||||
added_str: bool,
|
|
||||||
added_pickling: bool,
|
|
||||||
on_setattr_hook: _OnSetAttrType,
|
|
||||||
field_transformer: Callable[[Attribute[Any]], Attribute[Any]],
|
|
||||||
) -> None: ...
|
|
||||||
@property
|
|
||||||
def is_hashable(self) -> bool: ...
|
|
||||||
|
|
||||||
def inspect(cls: type) -> ClassProps: ...
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
+6
-9
@@ -1,6 +1,6 @@
|
|||||||
Metadata-Version: 2.4
|
Metadata-Version: 2.4
|
||||||
Name: cattrs
|
Name: cattrs
|
||||||
Version: 26.1.0
|
Version: 25.1.1
|
||||||
Summary: Composable complex class support for attrs and dataclasses.
|
Summary: Composable complex class support for attrs and dataclasses.
|
||||||
Project-URL: Homepage, https://catt.rs
|
Project-URL: Homepage, https://catt.rs
|
||||||
Project-URL: Changelog, https://catt.rs/en/latest/history.html
|
Project-URL: Changelog, https://catt.rs/en/latest/history.html
|
||||||
@@ -14,18 +14,18 @@ Keywords: attrs,dataclasses,serialization
|
|||||||
Classifier: Development Status :: 5 - Production/Stable
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
Classifier: Intended Audience :: Developers
|
Classifier: Intended Audience :: Developers
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Programming Language :: Python :: 3.9
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
Classifier: Programming Language :: Python :: 3.10
|
||||||
Classifier: Programming Language :: Python :: 3.11
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
Classifier: Programming Language :: Python :: 3.12
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
Classifier: Programming Language :: Python :: 3.13
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
Classifier: Programming Language :: Python :: 3.14
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
Classifier: Typing :: Typed
|
Classifier: Typing :: Typed
|
||||||
Requires-Python: >=3.10
|
Requires-Python: >=3.9
|
||||||
Requires-Dist: attrs>=25.4.0
|
Requires-Dist: attrs>=24.3.0
|
||||||
Requires-Dist: exceptiongroup>=1.1.1; python_version < '3.11'
|
Requires-Dist: exceptiongroup>=1.1.1; python_version < '3.11'
|
||||||
Requires-Dist: typing-extensions>=4.14.0
|
Requires-Dist: typing-extensions>=4.12.2
|
||||||
Provides-Extra: bson
|
Provides-Extra: bson
|
||||||
Requires-Dist: pymongo>=4.4.0; extra == 'bson'
|
Requires-Dist: pymongo>=4.4.0; extra == 'bson'
|
||||||
Provides-Extra: cbor2
|
Provides-Extra: cbor2
|
||||||
@@ -35,14 +35,11 @@ Requires-Dist: msgpack>=1.0.5; extra == 'msgpack'
|
|||||||
Provides-Extra: msgspec
|
Provides-Extra: msgspec
|
||||||
Requires-Dist: msgspec>=0.19.0; (implementation_name == 'cpython') and extra == 'msgspec'
|
Requires-Dist: msgspec>=0.19.0; (implementation_name == 'cpython') and extra == 'msgspec'
|
||||||
Provides-Extra: orjson
|
Provides-Extra: orjson
|
||||||
Requires-Dist: orjson>=3.11.3; (implementation_name == 'cpython') and extra == 'orjson'
|
Requires-Dist: orjson>=3.10.7; (implementation_name == 'cpython') and extra == 'orjson'
|
||||||
Provides-Extra: pyyaml
|
Provides-Extra: pyyaml
|
||||||
Requires-Dist: pyyaml>=6.0; extra == 'pyyaml'
|
Requires-Dist: pyyaml>=6.0; extra == 'pyyaml'
|
||||||
Provides-Extra: tomlkit
|
Provides-Extra: tomlkit
|
||||||
Requires-Dist: tomlkit>=0.11.8; extra == 'tomlkit'
|
Requires-Dist: tomlkit>=0.11.8; extra == 'tomlkit'
|
||||||
Provides-Extra: tomllib
|
|
||||||
Requires-Dist: tomli-w>=1.1.0; extra == 'tomllib'
|
|
||||||
Requires-Dist: tomli>=1.1.0; (python_version < '3.11') and extra == 'tomllib'
|
|
||||||
Provides-Extra: ujson
|
Provides-Extra: ujson
|
||||||
Requires-Dist: ujson>=5.10.0; extra == 'ujson'
|
Requires-Dist: ujson>=5.10.0; extra == 'ujson'
|
||||||
Description-Content-Type: text/markdown
|
Description-Content-Type: text/markdown
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
cattr/__init__.py,sha256=bYrmwTYSdYC_ut1xW31V7mxhXBlJQKs8EECgtUBgAuc,906
|
||||||
|
cattr/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
cattr/__pycache__/converters.cpython-311.pyc,,
|
||||||
|
cattr/__pycache__/disambiguators.cpython-311.pyc,,
|
||||||
|
cattr/__pycache__/dispatch.cpython-311.pyc,,
|
||||||
|
cattr/__pycache__/errors.cpython-311.pyc,,
|
||||||
|
cattr/__pycache__/gen.cpython-311.pyc,,
|
||||||
|
cattr/converters.py,sha256=rQhY4J8r7QTZh5WICuFe4GWO1v0DS3DgQ9r569zd6jg,192
|
||||||
|
cattr/disambiguators.py,sha256=ugD1fq1Z5x1pGu5P1lMzcT-IEi1q7IfQJIHEdmg62vM,103
|
||||||
|
cattr/dispatch.py,sha256=uVEOgHWR9Hn5tm-wIw-bDccqrxJByVi8yRKaYyvL67k,125
|
||||||
|
cattr/errors.py,sha256=V4RhoCObwGrlaM3oyn1H_FYxGR8iAB9dG5NxFDYM548,343
|
||||||
|
cattr/gen.py,sha256=hWyKoZ_d2D36Jz_npspyGw8s9pWtUA69sXf0R3uOvgM,597
|
||||||
|
cattr/preconf/__init__.py,sha256=NqPE7uhVfcP-PggkUpsbfAutMo8oHjcoB1cvjgLft-s,78
|
||||||
|
cattr/preconf/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/bson.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/json.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/msgpack.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/orjson.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/pyyaml.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/tomlkit.cpython-311.pyc,,
|
||||||
|
cattr/preconf/__pycache__/ujson.cpython-311.pyc,,
|
||||||
|
cattr/preconf/bson.py,sha256=Bn4hJxac7OthGg_CR4LCPeBp_fz4kx3QniBVOZhguGs,195
|
||||||
|
cattr/preconf/json.py,sha256=LpqYuO3oePDxbQtKFKB0SaoeAi3Z_agIgyNn1VQSIVo,206
|
||||||
|
cattr/preconf/msgpack.py,sha256=pyJ9L9ekNlZ0IQHbJ9Ay_fi_NOqY5_rE_q-UnD94-RM,207
|
||||||
|
cattr/preconf/orjson.py,sha256=Adh-7csx4eqCjx22zipMFgSlDXbR554wvgNHEb8Q5JM,203
|
||||||
|
cattr/preconf/pyyaml.py,sha256=Fy40bejjp7uqgoLhTA_p4wZYF0uFaguHbUK9zs9LoC0,203
|
||||||
|
cattr/preconf/tomlkit.py,sha256=_gADJ_UYpj3EiNXGYjAfSOkcoFIkLpYVOFfLEqBfIJQ,207
|
||||||
|
cattr/preconf/ujson.py,sha256=IzEa7QUcYOaSUMiLQsFEWJnBihmmOLhehsM-5cPY9NI,199
|
||||||
|
cattr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
cattrs-25.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
cattrs-25.1.1.dist-info/METADATA,sha256=ODqSak3dhIZZjmFa-SZT8Si32_3ey_oo2tUefYx0QtU,8388
|
||||||
|
cattrs-25.1.1.dist-info/RECORD,,
|
||||||
|
cattrs-25.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
cattrs-25.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
||||||
|
cattrs-25.1.1.dist-info/licenses/LICENSE,sha256=9fudHt43qIykf0IMSZ3KD0oFvJk-Esd9I1IKrSkcAb8,1074
|
||||||
|
cattrs/__init__.py,sha256=UhiFdxf81gCuBBA6FutoE1oOzthzF_PkAdoE2AVslIo,1901
|
||||||
|
cattrs/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/_compat.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/_generics.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/cols.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/converters.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/disambiguators.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/dispatch.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/errors.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/fns.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/literals.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/typealiases.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/types.cpython-311.pyc,,
|
||||||
|
cattrs/__pycache__/v.cpython-311.pyc,,
|
||||||
|
cattrs/_compat.py,sha256=dMRB8a8RkxFdnQDKRpamresVF_SBkksM2_ZMAuL0s2w,11987
|
||||||
|
cattrs/_generics.py,sha256=keExDE2CGIer8ci12SoJ_rXYTLva9P29uLlvyb_fxtM,966
|
||||||
|
cattrs/cols.py,sha256=mWDchfvjMQ6uACKSfdZs05YiXrWdph2HOJfaqY3D_EI,8848
|
||||||
|
cattrs/converters.py,sha256=ui4BSAxnV1J6Oh0pYN2oOHgp-mIybsXW-M8SXPDNzlo,54262
|
||||||
|
cattrs/disambiguators.py,sha256=eUyWMtW6bQJcXGOWiraq1pFcMlUbo9BJHxFY--KF6Lk,6867
|
||||||
|
cattrs/dispatch.py,sha256=9qA-pmsPvgrM6MGP8Ev2gVP6YL2rXvoBla_C0VgHxQ0,6780
|
||||||
|
cattrs/errors.py,sha256=6IGfE-wVQbOaDNN4xAQJf7Hk_t2QNV0Zem64D6yMZrU,4168
|
||||||
|
cattrs/fns.py,sha256=z5z1VZOZv8t5LwG8cBM_tIXg-_PlQUOyZb9wIrXNqlw,626
|
||||||
|
cattrs/gen/__init__.py,sha256=bpRGHd3G0UTpWcHRcJTNAUcWmX-evoPHGvpH6u29FDU,38842
|
||||||
|
cattrs/gen/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
cattrs/gen/__pycache__/_consts.cpython-311.pyc,,
|
||||||
|
cattrs/gen/__pycache__/_generics.cpython-311.pyc,,
|
||||||
|
cattrs/gen/__pycache__/_lc.cpython-311.pyc,,
|
||||||
|
cattrs/gen/__pycache__/_shared.cpython-311.pyc,,
|
||||||
|
cattrs/gen/__pycache__/typeddicts.cpython-311.pyc,,
|
||||||
|
cattrs/gen/_consts.py,sha256=ZwT_m2J3S7p-UjltpbA1WtfQZLNj9KhmFYCAv6Zl-g0,511
|
||||||
|
cattrs/gen/_generics.py,sha256=_DyXCGql2QIxGhAv3_B1hsi80uPK8PhK2hhZa95YOlo,3011
|
||||||
|
cattrs/gen/_lc.py,sha256=4fjeUsmgQcCAIjnNndBic0gf5qKmxVS3CZHqUQ9Rw5g,882
|
||||||
|
cattrs/gen/_shared.py,sha256=xKsfcVtpyYIir9AW8VuOVoiSbaEI7tsSL0JpUCIUX-g,2296
|
||||||
|
cattrs/gen/typeddicts.py,sha256=Ck3QMr_B1T7vwxyRjfZPHafKphN2hndL181dpQNxzPs,21254
|
||||||
|
cattrs/literals.py,sha256=0kzAewmWk9ikJGoKq4ysnAR22DMawG3iNqLl8NLgpk0,331
|
||||||
|
cattrs/preconf/__init__.py,sha256=P7czFRcjeN6zBcdwUyeBloniltlJptCa8Yd2uFGlz9w,1527
|
||||||
|
cattrs/preconf/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/bson.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/cbor2.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/json.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/msgpack.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/msgspec.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/orjson.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/pyyaml.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/tomlkit.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/__pycache__/ujson.cpython-311.pyc,,
|
||||||
|
cattrs/preconf/bson.py,sha256=6p1kmOFMjswSXFCb1hKJeNvr3kNsAm1gfX_DA6igq8E,4201
|
||||||
|
cattrs/preconf/cbor2.py,sha256=LnREcjpOp_402poUGRVIhDWI4f_R1wvJkdKvs5MrTGU,2022
|
||||||
|
cattrs/preconf/json.py,sha256=zTrkfjOxXZFwwabNESeafy-C7MEpn7Cw5AdhkkeOjU4,2631
|
||||||
|
cattrs/preconf/msgpack.py,sha256=dZE9tsAA5qX3pSc3MZmlGuvJ5q_wI6mANDyugKXKj-E,2325
|
||||||
|
cattrs/preconf/msgspec.py,sha256=Ds0rPW4900zsBLqfupF-smkg9_Kwyx7D_Vh9a0yJB8M,7250
|
||||||
|
cattrs/preconf/orjson.py,sha256=5MBcUsyp3eGsHgLfLtt8-q90L2mxjD0ttnrWBUIwouo,3870
|
||||||
|
cattrs/preconf/pyyaml.py,sha256=w0aM_gJ6VhZf-Zpu_UlJki7rdgv4mfaSXElPofB3nlE,2378
|
||||||
|
cattrs/preconf/tomlkit.py,sha256=gJWGJjMONCViTMZuphOg2xXzjQt3SCEVFVdoKgDjqc8,3148
|
||||||
|
cattrs/preconf/ujson.py,sha256=wRLidBM8aWucFkCQ9haiktY8xYoCdanDhQuKJLQJgGM,2425
|
||||||
|
cattrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
cattrs/strategies/__init__.py,sha256=nkZWCzSRYcS-75FMfk52mioZSuWykaN8hB39Vig5Xkg,339
|
||||||
|
cattrs/strategies/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
cattrs/strategies/__pycache__/_class_methods.cpython-311.pyc,,
|
||||||
|
cattrs/strategies/__pycache__/_subclasses.cpython-311.pyc,,
|
||||||
|
cattrs/strategies/__pycache__/_unions.cpython-311.pyc,,
|
||||||
|
cattrs/strategies/_class_methods.py,sha256=O5xhQCzNpuFiDNDMlbcyeOVqyrV65NhMZNRsG3jnoBU,2591
|
||||||
|
cattrs/strategies/_subclasses.py,sha256=aCE2UQjevZQHMnPOPyl2qR_hgRpgRUt1j9lE4qZ3hNc,9365
|
||||||
|
cattrs/strategies/_unions.py,sha256=YBBklVSWJ-7DSkLDLpumwAJJ39ALuSGyB6W0Ptz5Rz4,9355
|
||||||
|
cattrs/typealiases.py,sha256=toHavC2kJsIcxThwvATPO5JShzKeC8kIl9KqteFohbw,1619
|
||||||
|
cattrs/types.py,sha256=cqvfmzliYfrvPswxlW_tN4DmhQ2xpAKQvVbNBJaxiWs,278
|
||||||
|
cattrs/v.py,sha256=IqUajgJFCKJYf-4S9TCKRtJcmmK4c3En69TGuf2FKOs,4126
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
Wheel-Version: 1.0
|
Wheel-Version: 1.0
|
||||||
Generator: hatchling 1.28.0
|
Generator: hatchling 1.27.0
|
||||||
Root-Is-Purelib: true
|
Root-Is-Purelib: true
|
||||||
Tag: py3-none-any
|
Tag: py3-none-any
|
||||||
@@ -1 +0,0 @@
|
|||||||
uv
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
cattr/__init__.py,sha256=bYrmwTYSdYC_ut1xW31V7mxhXBlJQKs8EECgtUBgAuc,906
|
|
||||||
cattr/converters.py,sha256=rQhY4J8r7QTZh5WICuFe4GWO1v0DS3DgQ9r569zd6jg,192
|
|
||||||
cattr/disambiguators.py,sha256=ugD1fq1Z5x1pGu5P1lMzcT-IEi1q7IfQJIHEdmg62vM,103
|
|
||||||
cattr/dispatch.py,sha256=uVEOgHWR9Hn5tm-wIw-bDccqrxJByVi8yRKaYyvL67k,125
|
|
||||||
cattr/errors.py,sha256=V4RhoCObwGrlaM3oyn1H_FYxGR8iAB9dG5NxFDYM548,343
|
|
||||||
cattr/gen.py,sha256=hWyKoZ_d2D36Jz_npspyGw8s9pWtUA69sXf0R3uOvgM,597
|
|
||||||
cattr/preconf/__init__.py,sha256=NqPE7uhVfcP-PggkUpsbfAutMo8oHjcoB1cvjgLft-s,78
|
|
||||||
cattr/preconf/bson.py,sha256=Bn4hJxac7OthGg_CR4LCPeBp_fz4kx3QniBVOZhguGs,195
|
|
||||||
cattr/preconf/json.py,sha256=LpqYuO3oePDxbQtKFKB0SaoeAi3Z_agIgyNn1VQSIVo,206
|
|
||||||
cattr/preconf/msgpack.py,sha256=pyJ9L9ekNlZ0IQHbJ9Ay_fi_NOqY5_rE_q-UnD94-RM,207
|
|
||||||
cattr/preconf/orjson.py,sha256=Adh-7csx4eqCjx22zipMFgSlDXbR554wvgNHEb8Q5JM,203
|
|
||||||
cattr/preconf/pyyaml.py,sha256=Fy40bejjp7uqgoLhTA_p4wZYF0uFaguHbUK9zs9LoC0,203
|
|
||||||
cattr/preconf/tomlkit.py,sha256=_gADJ_UYpj3EiNXGYjAfSOkcoFIkLpYVOFfLEqBfIJQ,207
|
|
||||||
cattr/preconf/ujson.py,sha256=IzEa7QUcYOaSUMiLQsFEWJnBihmmOLhehsM-5cPY9NI,199
|
|
||||||
cattr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
cattrs-26.1.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
|
||||||
cattrs-26.1.0.dist-info/METADATA,sha256=d94QudQ0gM0Zy_KNPtL7n2gEea7s-Vir2t4_rLD4zLA,8542
|
|
||||||
cattrs-26.1.0.dist-info/RECORD,,
|
|
||||||
cattrs-26.1.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
cattrs-26.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
||||||
cattrs-26.1.0.dist-info/licenses/LICENSE,sha256=9fudHt43qIykf0IMSZ3KD0oFvJk-Esd9I1IKrSkcAb8,1074
|
|
||||||
cattrs/__init__.py,sha256=UhiFdxf81gCuBBA6FutoE1oOzthzF_PkAdoE2AVslIo,1901
|
|
||||||
cattrs/_compat.py,sha256=5dOpD6O8zUVZcreaJ5wSt9n-Nut4n79ibrka5Td6l84,12251
|
|
||||||
cattrs/_generics.py,sha256=ERYo_kX-Z6UiwOcYI3WLrlHO6Ya66c_whpko6es5UeI,966
|
|
||||||
cattrs/cols.py,sha256=-KmFikvZu7cL_g68ytx0Ad6danfjVY5Ry3w1XRtTaWk,10336
|
|
||||||
cattrs/converters.py,sha256=BPpSPuhMuhQz2qFl_OAeIZ8CrI95A8gy6khuvU35bN8,55322
|
|
||||||
cattrs/disambiguators.py,sha256=uydD6QXPve1-0dY_3m_FoyOqowvTvEKXzrdb-5xkRPE,6863
|
|
||||||
cattrs/dispatch.py,sha256=9qA-pmsPvgrM6MGP8Ev2gVP6YL2rXvoBla_C0VgHxQ0,6780
|
|
||||||
cattrs/enums.py,sha256=noADdwXP9TSpyIyjE6WR2iIbGPmigirvArKAoHWJmDI,1130
|
|
||||||
cattrs/errors.py,sha256=CLW6Uev31cOxo6cWFb0-g-FAbYJD2mYmR5xTxDIVjDw,4344
|
|
||||||
cattrs/fns.py,sha256=z5z1VZOZv8t5LwG8cBM_tIXg-_PlQUOyZb9wIrXNqlw,626
|
|
||||||
cattrs/gen/__init__.py,sha256=wrmi87jGEfmWcbbNkZXk1Gqn9PLwXerKUDJoCQBCRek,42553
|
|
||||||
cattrs/gen/_consts.py,sha256=ZwT_m2J3S7p-UjltpbA1WtfQZLNj9KhmFYCAv6Zl-g0,511
|
|
||||||
cattrs/gen/_generics.py,sha256=_DyXCGql2QIxGhAv3_B1hsi80uPK8PhK2hhZa95YOlo,3011
|
|
||||||
cattrs/gen/_lc.py,sha256=4fjeUsmgQcCAIjnNndBic0gf5qKmxVS3CZHqUQ9Rw5g,882
|
|
||||||
cattrs/gen/_shared.py,sha256=QctJIsgfhZqgdALdD-L9FUEX1k2uNUpJQW4NbcGLVWQ,2747
|
|
||||||
cattrs/gen/typeddicts.py,sha256=svG8RjqNt411KzkRFEKp6zsJHJvrwZNe1UQIhQewv7w,22085
|
|
||||||
cattrs/literals.py,sha256=0kzAewmWk9ikJGoKq4ysnAR22DMawG3iNqLl8NLgpk0,331
|
|
||||||
cattrs/preconf/__init__.py,sha256=YMY1ADc7OzpuZ2cdQZbH5kUKZF30UYd6owf3BOT4TO0,1435
|
|
||||||
cattrs/preconf/bson.py,sha256=I0632WE3L7VN5aA9T3PJ1Jq_iy5XOfTsjJcKjQSEMAM,4211
|
|
||||||
cattrs/preconf/cbor2.py,sha256=o13uyALyX2dvxp6HRYq_xHeB84nEGyBOB0V8OcohY30,2035
|
|
||||||
cattrs/preconf/json.py,sha256=JiAetUezqQ_ixjRydLCavkvnIQxQCTyQXKzViC-vO6A,2644
|
|
||||||
cattrs/preconf/msgpack.py,sha256=12c3sE5tfIVVhxHD4QmZaSGmxTvwHfrtTdk0ohOCcLI,2338
|
|
||||||
cattrs/preconf/msgspec.py,sha256=Hea-jCkcqH7wtONAHjpe9EaaqhGodQFfkLtKuWdV09k,7384
|
|
||||||
cattrs/preconf/orjson.py,sha256=o1GZwvH2hlLbovmsB7OH3fDkcROhHOAQKMsrDW3VTsY,3883
|
|
||||||
cattrs/preconf/pyyaml.py,sha256=w0aM_gJ6VhZf-Zpu_UlJki7rdgv4mfaSXElPofB3nlE,2378
|
|
||||||
cattrs/preconf/tomlkit.py,sha256=sPrBZgq7Zg78y31SAPhBuSWQllGxXXysyI-2-T6MY6U,3311
|
|
||||||
cattrs/preconf/tomllib.py,sha256=wDOhVcIkBR0bw5zMACPlUcf2YXv-eamgg-GmGdv4cSU,3083
|
|
||||||
cattrs/preconf/ujson.py,sha256=ev1fdMvQdP-PSDqDbJ714Oy3Xxi2lbc6qVSZ-LGdgKg,2438
|
|
||||||
cattrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
cattrs/strategies/__init__.py,sha256=nkZWCzSRYcS-75FMfk52mioZSuWykaN8hB39Vig5Xkg,339
|
|
||||||
cattrs/strategies/_class_methods.py,sha256=O5xhQCzNpuFiDNDMlbcyeOVqyrV65NhMZNRsG3jnoBU,2591
|
|
||||||
cattrs/strategies/_subclasses.py,sha256=8fTr7CLbNlV2Nobi0yXkQ9OKpGyUENmEzyYAsy4SDJw,9852
|
|
||||||
cattrs/strategies/_unions.py,sha256=XQwrnPEE7KhBqOy5vyUZ06reQPfqcO85jdVjQ7i3HFk,10070
|
|
||||||
cattrs/subclasses.py,sha256=SyAaJ84sO3aen_dBQkuSAIg0eGOXYX-aF151d7IbiNk,783
|
|
||||||
cattrs/typealiases.py,sha256=toHavC2kJsIcxThwvATPO5JShzKeC8kIl9KqteFohbw,1619
|
|
||||||
cattrs/types.py,sha256=cqvfmzliYfrvPswxlW_tN4DmhQ2xpAKQvVbNBJaxiWs,278
|
|
||||||
cattrs/v.py,sha256=IqUajgJFCKJYf-4S9TCKRtJcmmK4c3En69TGuf2FKOs,4126
|
|
||||||
@@ -29,6 +29,7 @@ from typing import (
|
|||||||
_AnnotatedAlias,
|
_AnnotatedAlias,
|
||||||
_GenericAlias,
|
_GenericAlias,
|
||||||
_SpecialGenericAlias,
|
_SpecialGenericAlias,
|
||||||
|
_UnionGenericAlias,
|
||||||
get_args,
|
get_args,
|
||||||
get_origin,
|
get_origin,
|
||||||
get_type_hints,
|
get_type_hints,
|
||||||
@@ -208,7 +209,10 @@ def get_final_base(type) -> Optional[type]:
|
|||||||
OriginAbstractSet = AbcSet
|
OriginAbstractSet = AbcSet
|
||||||
OriginMutableSet = AbcMutableSet
|
OriginMutableSet = AbcMutableSet
|
||||||
|
|
||||||
signature = partial(_signature, eval_str=True)
|
signature = _signature
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 10):
|
||||||
|
signature = partial(_signature, eval_str=True)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -252,25 +256,10 @@ def is_tuple(type):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 14):
|
if sys.version_info >= (3, 10):
|
||||||
|
|
||||||
def is_union_type(obj):
|
def is_union_type(obj):
|
||||||
from types import UnionType # noqa: PLC0415
|
from types import UnionType
|
||||||
|
|
||||||
return obj is Union or isinstance(obj, UnionType)
|
|
||||||
|
|
||||||
def get_newtype_base(typ: Any) -> Optional[type]:
|
|
||||||
if typ is NewType or isinstance(typ, NewType):
|
|
||||||
return typ.__supertype__
|
|
||||||
return None
|
|
||||||
|
|
||||||
from typing import NotRequired, Required
|
|
||||||
|
|
||||||
else:
|
|
||||||
from typing import _UnionGenericAlias
|
|
||||||
|
|
||||||
def is_union_type(obj):
|
|
||||||
from types import UnionType # noqa: PLC0415
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
obj is Union
|
obj is Union
|
||||||
@@ -288,6 +277,25 @@ else:
|
|||||||
else:
|
else:
|
||||||
from typing_extensions import NotRequired, Required
|
from typing_extensions import NotRequired, Required
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 3.9
|
||||||
|
from typing_extensions import NotRequired, Required
|
||||||
|
|
||||||
|
def is_union_type(obj):
|
||||||
|
return obj is Union or (
|
||||||
|
isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_newtype_base(typ: Any) -> Optional[type]:
|
||||||
|
supertype = getattr(typ, "__supertype__", None)
|
||||||
|
if (
|
||||||
|
supertype is not None
|
||||||
|
and getattr(typ, "__qualname__", "") == "NewType.<locals>.new_type"
|
||||||
|
and typ.__module__ in ("typing", "typing_extensions")
|
||||||
|
):
|
||||||
|
return supertype
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_notrequired_base(type) -> Union[Any, NothingType]:
|
def get_notrequired_base(type) -> Union[Any, NothingType]:
|
||||||
if is_annotated(type):
|
if is_annotated(type):
|
||||||
@@ -298,25 +306,6 @@ def get_notrequired_base(type) -> Union[Any, NothingType]:
|
|||||||
return NOTHING
|
return NOTHING
|
||||||
|
|
||||||
|
|
||||||
def is_mutable_sequence(type: Any) -> bool:
|
|
||||||
"""A predicate function for mutable sequences.
|
|
||||||
|
|
||||||
Matches lists, mutable sequences, and deques.
|
|
||||||
"""
|
|
||||||
origin = getattr(type, "__origin__", None)
|
|
||||||
return (
|
|
||||||
type in (List, list, TypingMutableSequence, AbcMutableSequence, deque, Deque)
|
|
||||||
or (
|
|
||||||
type.__class__ is _GenericAlias
|
|
||||||
and (
|
|
||||||
((origin is not tuple) and is_subclass(origin, TypingMutableSequence))
|
|
||||||
or (origin is tuple and type.__args__[1] is ...)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
or (origin in (list, deque, AbcMutableSequence))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_sequence(type: Any) -> bool:
|
def is_sequence(type: Any) -> bool:
|
||||||
"""A predicate function for sequences.
|
"""A predicate function for sequences.
|
||||||
|
|
||||||
@@ -324,8 +313,19 @@ def is_sequence(type: Any) -> bool:
|
|||||||
tuples.
|
tuples.
|
||||||
"""
|
"""
|
||||||
origin = getattr(type, "__origin__", None)
|
origin = getattr(type, "__origin__", None)
|
||||||
return is_mutable_sequence(type) or (
|
return (
|
||||||
type in (TypingSequence, tuple, Tuple)
|
type
|
||||||
|
in (
|
||||||
|
List,
|
||||||
|
list,
|
||||||
|
TypingSequence,
|
||||||
|
TypingMutableSequence,
|
||||||
|
AbcMutableSequence,
|
||||||
|
tuple,
|
||||||
|
Tuple,
|
||||||
|
deque,
|
||||||
|
Deque,
|
||||||
|
)
|
||||||
or (
|
or (
|
||||||
type.__class__ is _GenericAlias
|
type.__class__ is _GenericAlias
|
||||||
and (
|
and (
|
||||||
@@ -333,7 +333,7 @@ def is_sequence(type: Any) -> bool:
|
|||||||
or (origin is tuple and type.__args__[1] is ...)
|
or (origin is tuple and type.__args__[1] is ...)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
or (origin is AbcSequence)
|
or (origin in (list, deque, AbcMutableSequence, AbcSequence))
|
||||||
or (origin is tuple and type.__args__[1] is ...)
|
or (origin is tuple and type.__args__[1] is ...)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -403,10 +403,8 @@ def is_generic(type) -> bool:
|
|||||||
"""Whether `type` is a generic type."""
|
"""Whether `type` is a generic type."""
|
||||||
# Inheriting from protocol will inject `Generic` into the MRO
|
# Inheriting from protocol will inject `Generic` into the MRO
|
||||||
# without `__orig_bases__`.
|
# without `__orig_bases__`.
|
||||||
return (
|
return isinstance(type, (_GenericAlias, GenericAlias)) or (
|
||||||
isinstance(type, (_GenericAlias, GenericAlias))
|
is_subclass(type, Generic) and hasattr(type, "__orig_bases__")
|
||||||
or (is_subclass(type, Generic) and hasattr(type, "__orig_bases__"))
|
|
||||||
or type.__class__ is Union # On 3.14, unions are no longer typing._GenericAlias
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any, get_args
|
from typing import Any
|
||||||
|
|
||||||
from attrs import NOTHING
|
from attrs import NOTHING
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from ._compat import copy_with, is_annotated, is_generic
|
from ._compat import copy_with, get_args, is_annotated, is_generic
|
||||||
|
|
||||||
|
|
||||||
def deep_copy_with(t, mapping: Mapping[str, Any], self_is=NOTHING):
|
def deep_copy_with(t, mapping: Mapping[str, Any], self_is=NOTHING):
|
||||||
|
|||||||
+10
-54
@@ -5,20 +5,25 @@ from __future__ import annotations
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, Any, DefaultDict, Literal, NamedTuple, TypeVar
|
from typing import (
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Any,
|
||||||
|
DefaultDict,
|
||||||
|
Literal,
|
||||||
|
NamedTuple,
|
||||||
|
TypeVar,
|
||||||
|
get_type_hints,
|
||||||
|
)
|
||||||
|
|
||||||
from attrs import NOTHING, Attribute, NothingType
|
from attrs import NOTHING, Attribute, NothingType
|
||||||
|
|
||||||
from ._compat import (
|
from ._compat import (
|
||||||
ANIES,
|
ANIES,
|
||||||
AbcSet,
|
|
||||||
get_args,
|
get_args,
|
||||||
get_full_type_hints,
|
|
||||||
get_origin,
|
get_origin,
|
||||||
is_bare,
|
is_bare,
|
||||||
is_frozenset,
|
is_frozenset,
|
||||||
is_mapping,
|
is_mapping,
|
||||||
is_mutable_sequence,
|
|
||||||
is_sequence,
|
is_sequence,
|
||||||
is_subclass,
|
is_subclass,
|
||||||
)
|
)
|
||||||
@@ -42,13 +47,10 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"defaultdict_structure_factory",
|
"defaultdict_structure_factory",
|
||||||
"homogenous_tuple_structure_factory",
|
|
||||||
"is_abstract_set",
|
|
||||||
"is_any_set",
|
"is_any_set",
|
||||||
"is_defaultdict",
|
"is_defaultdict",
|
||||||
"is_frozenset",
|
"is_frozenset",
|
||||||
"is_mapping",
|
"is_mapping",
|
||||||
"is_mutable_sequence",
|
|
||||||
"is_namedtuple",
|
"is_namedtuple",
|
||||||
"is_sequence",
|
"is_sequence",
|
||||||
"is_set",
|
"is_set",
|
||||||
@@ -68,11 +70,6 @@ def is_any_set(type) -> bool:
|
|||||||
return is_set(type) or is_frozenset(type)
|
return is_set(type) or is_frozenset(type)
|
||||||
|
|
||||||
|
|
||||||
def is_abstract_set(type) -> bool:
|
|
||||||
"""A predicate function for abstract (collection.abc) sets."""
|
|
||||||
return type is AbcSet or (getattr(type, "__origin__", None) is AbcSet)
|
|
||||||
|
|
||||||
|
|
||||||
def is_namedtuple(type: Any) -> bool:
|
def is_namedtuple(type: Any) -> bool:
|
||||||
"""A predicate function for named tuples."""
|
"""A predicate function for named tuples."""
|
||||||
|
|
||||||
@@ -154,47 +151,6 @@ def list_structure_factory(type: type, converter: BaseConverter) -> StructureHoo
|
|||||||
return structure_list
|
return structure_list
|
||||||
|
|
||||||
|
|
||||||
def homogenous_tuple_structure_factory(
|
|
||||||
type: type, converter: BaseConverter
|
|
||||||
) -> StructureHook:
|
|
||||||
"""A hook factory for homogenous (all elements the same, indeterminate length) tuples.
|
|
||||||
|
|
||||||
Converts any given iterable into a tuple.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if is_bare(type) or type.__args__[0] in ANIES:
|
|
||||||
|
|
||||||
def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
|
|
||||||
return tuple(obj)
|
|
||||||
|
|
||||||
return structure_tuple
|
|
||||||
|
|
||||||
elem_type = type.__args__[0]
|
|
||||||
|
|
||||||
try:
|
|
||||||
handler = converter.get_structure_hook(elem_type)
|
|
||||||
except RecursionError:
|
|
||||||
# Break the cycle by using late binding.
|
|
||||||
handler = converter.structure
|
|
||||||
|
|
||||||
if converter.detailed_validation:
|
|
||||||
|
|
||||||
# We have to structure into a list first anyway.
|
|
||||||
list_structure = list_structure_factory(type, converter)
|
|
||||||
|
|
||||||
def structure_tuple(obj: Iterable[T], _: type = type) -> tuple[T, ...]:
|
|
||||||
return tuple(list_structure(obj, _))
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def structure_tuple(
|
|
||||||
obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type
|
|
||||||
) -> tuple[T, ...]:
|
|
||||||
return tuple([_handler(e, _elem_type) for e in obj])
|
|
||||||
|
|
||||||
return structure_tuple
|
|
||||||
|
|
||||||
|
|
||||||
def namedtuple_unstructure_factory(
|
def namedtuple_unstructure_factory(
|
||||||
cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
|
cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
|
||||||
) -> UnstructureHook:
|
) -> UnstructureHook:
|
||||||
@@ -239,7 +195,7 @@ def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
|
|||||||
type=a,
|
type=a,
|
||||||
alias=name,
|
alias=name,
|
||||||
)
|
)
|
||||||
for name, a in get_full_type_hints(cl).items()
|
for name, a in get_type_hints(cl).items()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ from ._compat import (
|
|||||||
is_hetero_tuple,
|
is_hetero_tuple,
|
||||||
is_literal,
|
is_literal,
|
||||||
is_mapping,
|
is_mapping,
|
||||||
is_mutable_sequence,
|
|
||||||
is_mutable_set,
|
is_mutable_set,
|
||||||
is_optional,
|
is_optional,
|
||||||
is_protocol,
|
is_protocol,
|
||||||
is_subclass,
|
is_sequence,
|
||||||
is_tuple,
|
is_tuple,
|
||||||
is_typeddict,
|
is_typeddict,
|
||||||
is_union_type,
|
is_union_type,
|
||||||
@@ -55,11 +54,8 @@ from ._compat import (
|
|||||||
)
|
)
|
||||||
from .cols import (
|
from .cols import (
|
||||||
defaultdict_structure_factory,
|
defaultdict_structure_factory,
|
||||||
homogenous_tuple_structure_factory,
|
|
||||||
is_abstract_set,
|
|
||||||
is_defaultdict,
|
is_defaultdict,
|
||||||
is_namedtuple,
|
is_namedtuple,
|
||||||
is_sequence,
|
|
||||||
iterable_unstructure_factory,
|
iterable_unstructure_factory,
|
||||||
list_structure_factory,
|
list_structure_factory,
|
||||||
mapping_structure_factory,
|
mapping_structure_factory,
|
||||||
@@ -77,7 +73,6 @@ from .dispatch import (
|
|||||||
UnstructuredValue,
|
UnstructuredValue,
|
||||||
UnstructureHook,
|
UnstructureHook,
|
||||||
)
|
)
|
||||||
from .enums import enum_structure_factory, enum_unstructure_factory
|
|
||||||
from .errors import (
|
from .errors import (
|
||||||
IterableValidationError,
|
IterableValidationError,
|
||||||
IterableValidationNote,
|
IterableValidationNote,
|
||||||
@@ -230,10 +225,6 @@ class BaseConverter:
|
|||||||
)
|
)
|
||||||
self._unstructure_func.register_func_list(
|
self._unstructure_func.register_func_list(
|
||||||
[
|
[
|
||||||
(
|
|
||||||
lambda t: get_newtype_base(t) is not None,
|
|
||||||
lambda o: self.unstructure(o, unstructure_as=o.__class__),
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
is_protocol,
|
is_protocol,
|
||||||
lambda o: self.unstructure(o, unstructure_as=o.__class__),
|
lambda o: self.unstructure(o, unstructure_as=o.__class__),
|
||||||
@@ -248,12 +239,12 @@ class BaseConverter:
|
|||||||
lambda t: self.get_unstructure_hook(get_type_alias_base(t)),
|
lambda t: self.get_unstructure_hook(get_type_alias_base(t)),
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
|
(is_literal_containing_enums, self.unstructure),
|
||||||
(is_mapping, self._unstructure_mapping),
|
(is_mapping, self._unstructure_mapping),
|
||||||
(is_sequence, self._unstructure_seq),
|
(is_sequence, self._unstructure_seq),
|
||||||
(is_mutable_set, self._unstructure_seq),
|
(is_mutable_set, self._unstructure_seq),
|
||||||
(is_frozenset, self._unstructure_seq),
|
(is_frozenset, self._unstructure_seq),
|
||||||
(is_literal_containing_enums, self.unstructure),
|
(lambda t: issubclass(t, Enum), self._unstructure_enum),
|
||||||
(lambda t: is_subclass(t, Enum), enum_unstructure_factory, "extended"),
|
|
||||||
(has, self._unstructure_attrs),
|
(has, self._unstructure_attrs),
|
||||||
(is_union_type, self._unstructure_union),
|
(is_union_type, self._unstructure_union),
|
||||||
(lambda t: t in ANIES, self.unstructure),
|
(lambda t: t in ANIES, self.unstructure),
|
||||||
@@ -280,27 +271,20 @@ class BaseConverter:
|
|||||||
),
|
),
|
||||||
(is_literal, self._structure_simple_literal),
|
(is_literal, self._structure_simple_literal),
|
||||||
(is_literal_containing_enums, self._structure_enum_literal),
|
(is_literal_containing_enums, self._structure_enum_literal),
|
||||||
(is_sequence, homogenous_tuple_structure_factory, "extended"),
|
(is_sequence, list_structure_factory, "extended"),
|
||||||
(is_mutable_sequence, list_structure_factory, "extended"),
|
|
||||||
(is_deque, self._structure_deque),
|
(is_deque, self._structure_deque),
|
||||||
(is_mutable_set, self._structure_set),
|
(is_mutable_set, self._structure_set),
|
||||||
(is_abstract_set, self._structure_frozenset),
|
|
||||||
(is_frozenset, self._structure_frozenset),
|
(is_frozenset, self._structure_frozenset),
|
||||||
(is_tuple, self._structure_tuple),
|
(is_tuple, self._structure_tuple),
|
||||||
(is_namedtuple, namedtuple_structure_factory, "extended"),
|
(is_namedtuple, namedtuple_structure_factory, "extended"),
|
||||||
(is_mapping, self._structure_dict),
|
(is_mapping, self._structure_dict),
|
||||||
*(
|
(is_supported_union, self._gen_attrs_union_structure, True),
|
||||||
[(is_supported_union, self._gen_attrs_union_structure, True)]
|
|
||||||
if unstruct_strat is UnstructureStrategy.AS_DICT
|
|
||||||
else []
|
|
||||||
),
|
|
||||||
(is_optional, self._structure_optional),
|
(is_optional, self._structure_optional),
|
||||||
(
|
(
|
||||||
lambda t: is_union_type(t) and t in self._union_struct_registry,
|
lambda t: is_union_type(t) and t in self._union_struct_registry,
|
||||||
self._union_struct_registry.__getitem__,
|
self._union_struct_registry.__getitem__,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
(lambda t: is_subclass(t, Enum), enum_structure_factory, "extended"),
|
|
||||||
(has, self._structure_attrs),
|
(has, self._structure_attrs),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -311,6 +295,7 @@ class BaseConverter:
|
|||||||
(bytes, self._structure_call),
|
(bytes, self._structure_call),
|
||||||
(int, self._structure_call),
|
(int, self._structure_call),
|
||||||
(float, self._structure_call),
|
(float, self._structure_call),
|
||||||
|
(Enum, self._structure_call),
|
||||||
(Path, self._structure_call),
|
(Path, self._structure_call),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -632,6 +617,10 @@ class BaseConverter:
|
|||||||
res.append(dispatch(a.type or v.__class__)(v))
|
res.append(dispatch(a.type or v.__class__)(v))
|
||||||
return tuple(res)
|
return tuple(res)
|
||||||
|
|
||||||
|
def _unstructure_enum(self, obj: Enum) -> Any:
|
||||||
|
"""Convert an enum to its value."""
|
||||||
|
return obj.value
|
||||||
|
|
||||||
def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]:
|
def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]:
|
||||||
"""Convert a sequence to primitive equivalents."""
|
"""Convert a sequence to primitive equivalents."""
|
||||||
# We can reuse the sequence class, so tuples stay tuples.
|
# We can reuse the sequence class, so tuples stay tuples.
|
||||||
@@ -1045,7 +1034,6 @@ class Converter(BaseConverter):
|
|||||||
"forbid_extra_keys",
|
"forbid_extra_keys",
|
||||||
"omit_if_default",
|
"omit_if_default",
|
||||||
"type_overrides",
|
"type_overrides",
|
||||||
"use_alias",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -1062,7 +1050,6 @@ class Converter(BaseConverter):
|
|||||||
structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
|
structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
|
||||||
None, t
|
None, t
|
||||||
),
|
),
|
||||||
use_alias: bool = False,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param detailed_validation: Whether to use a slightly slower mode for detailed
|
:param detailed_validation: Whether to use a slightly slower mode for detailed
|
||||||
@@ -1071,15 +1058,12 @@ class Converter(BaseConverter):
|
|||||||
registered unstructuring hooks match.
|
registered unstructuring hooks match.
|
||||||
:param structure_fallback_factory: A hook factory to be called when no
|
:param structure_fallback_factory: A hook factory to be called when no
|
||||||
registered structuring hooks match.
|
registered structuring hooks match.
|
||||||
:param use_alias: Whether to use the field alias instead of the field name as
|
|
||||||
the un/structured dictionary key by default.
|
|
||||||
|
|
||||||
.. versionadded:: 23.2.0 *unstructure_fallback_factory*
|
.. versionadded:: 23.2.0 *unstructure_fallback_factory*
|
||||||
.. versionadded:: 23.2.0 *structure_fallback_factory*
|
.. versionadded:: 23.2.0 *structure_fallback_factory*
|
||||||
.. versionchanged:: 24.2.0
|
.. versionchanged:: 24.2.0
|
||||||
The default `structure_fallback_factory` now raises errors for missing handlers
|
The default `structure_fallback_factory` now raises errors for missing handlers
|
||||||
more eagerly, surfacing problems earlier.
|
more eagerly, surfacing problems earlier.
|
||||||
.. versionadded:: 25.2.0 *use_alias*
|
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
dict_factory=dict_factory,
|
dict_factory=dict_factory,
|
||||||
@@ -1092,7 +1076,6 @@ class Converter(BaseConverter):
|
|||||||
self.omit_if_default = omit_if_default
|
self.omit_if_default = omit_if_default
|
||||||
self.forbid_extra_keys = forbid_extra_keys
|
self.forbid_extra_keys = forbid_extra_keys
|
||||||
self.type_overrides = dict(type_overrides)
|
self.type_overrides = dict(type_overrides)
|
||||||
self.use_alias = use_alias
|
|
||||||
|
|
||||||
unstruct_collection_overrides = {
|
unstruct_collection_overrides = {
|
||||||
get_origin(k) or k: v for k, v in unstruct_collection_overrides.items()
|
get_origin(k) or k: v for k, v in unstruct_collection_overrides.items()
|
||||||
@@ -1263,7 +1246,7 @@ class Converter(BaseConverter):
|
|||||||
attribs = fields(origin or cl)
|
attribs = fields(origin or cl)
|
||||||
if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
|
if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
|
||||||
# PEP 563 annotations - need to be resolved.
|
# PEP 563 annotations - need to be resolved.
|
||||||
resolve_types(origin or cl)
|
resolve_types(cl)
|
||||||
attrib_overrides = {
|
attrib_overrides = {
|
||||||
a.name: self.type_overrides[a.type]
|
a.name: self.type_overrides[a.type]
|
||||||
for a in attribs
|
for a in attribs
|
||||||
@@ -1301,11 +1284,10 @@ class Converter(BaseConverter):
|
|||||||
def gen_structure_attrs_fromdict(
|
def gen_structure_attrs_fromdict(
|
||||||
self, cl: type[T]
|
self, cl: type[T]
|
||||||
) -> Callable[[Mapping[str, Any], Any], T]:
|
) -> Callable[[Mapping[str, Any], Any], T]:
|
||||||
origin = get_origin(cl)
|
attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl)
|
||||||
attribs = fields(origin or cl if is_generic(cl) else cl)
|
|
||||||
if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
|
if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
|
||||||
# PEP 563 annotations - need to be resolved.
|
# PEP 563 annotations - need to be resolved.
|
||||||
resolve_types(origin or cl)
|
resolve_types(cl)
|
||||||
attrib_overrides = {
|
attrib_overrides = {
|
||||||
a.name: self.type_overrides[a.type]
|
a.name: self.type_overrides[a.type]
|
||||||
for a in attribs
|
for a in attribs
|
||||||
@@ -1317,7 +1299,6 @@ class Converter(BaseConverter):
|
|||||||
_cattrs_forbid_extra_keys=self.forbid_extra_keys,
|
_cattrs_forbid_extra_keys=self.forbid_extra_keys,
|
||||||
_cattrs_prefer_attrib_converters=self._prefer_attrib_converters,
|
_cattrs_prefer_attrib_converters=self._prefer_attrib_converters,
|
||||||
_cattrs_detailed_validation=self.detailed_validation,
|
_cattrs_detailed_validation=self.detailed_validation,
|
||||||
_cattrs_use_alias=self.use_alias,
|
|
||||||
**attrib_overrides,
|
**attrib_overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1396,7 +1377,6 @@ class Converter(BaseConverter):
|
|||||||
unstruct_collection_overrides: Mapping[type, UnstructureHook] | None = None,
|
unstruct_collection_overrides: Mapping[type, UnstructureHook] | None = None,
|
||||||
prefer_attrib_converters: bool | None = None,
|
prefer_attrib_converters: bool | None = None,
|
||||||
detailed_validation: bool | None = None,
|
detailed_validation: bool | None = None,
|
||||||
use_alias: bool | None = None,
|
|
||||||
) -> Self:
|
) -> Self:
|
||||||
"""Create a copy of the converter, keeping all existing custom hooks.
|
"""Create a copy of the converter, keeping all existing custom hooks.
|
||||||
|
|
||||||
@@ -1436,7 +1416,6 @@ class Converter(BaseConverter):
|
|||||||
if detailed_validation is not None
|
if detailed_validation is not None
|
||||||
else self.detailed_validation
|
else self.detailed_validation
|
||||||
),
|
),
|
||||||
use_alias=(use_alias if use_alias is not None else self.use_alias),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self._unstructure_func.copy_to(
|
self._unstructure_func.copy_to(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from collections.abc import Mapping
|
|||||||
from dataclasses import MISSING
|
from dataclasses import MISSING
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from operator import or_
|
from operator import or_
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Literal, Union, get_origin
|
from typing import TYPE_CHECKING, Any, Callable, Literal, Union
|
||||||
|
|
||||||
from attrs import NOTHING, Attribute, AttrsInstance
|
from attrs import NOTHING, Attribute, AttrsInstance
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ from ._compat import (
|
|||||||
adapted_fields,
|
adapted_fields,
|
||||||
fields_dict,
|
fields_dict,
|
||||||
get_args,
|
get_args,
|
||||||
|
get_origin,
|
||||||
has,
|
has,
|
||||||
is_literal,
|
is_literal,
|
||||||
is_union_type,
|
is_union_type,
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
from collections.abc import Callable
|
|
||||||
from enum import Enum
|
|
||||||
from typing import TYPE_CHECKING, Any
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .converters import BaseConverter
|
|
||||||
|
|
||||||
|
|
||||||
def enum_unstructure_factory(
|
|
||||||
type: type[Enum], converter: "BaseConverter"
|
|
||||||
) -> Callable[[Enum], Any]:
|
|
||||||
"""A factory for generating enum unstructure hooks.
|
|
||||||
|
|
||||||
If the enum is a typed enum (has `_value_`), we use the underlying value's hook.
|
|
||||||
Otherwise, we use the value directly.
|
|
||||||
"""
|
|
||||||
if "_value_" in type.__annotations__:
|
|
||||||
return lambda e: converter.unstructure(e.value)
|
|
||||||
|
|
||||||
return lambda e: e.value
|
|
||||||
|
|
||||||
|
|
||||||
def enum_structure_factory(
|
|
||||||
type: type[Enum], converter: "BaseConverter"
|
|
||||||
) -> Callable[[Any, type[Enum]], Enum]:
|
|
||||||
"""A factory for generating enum structure hooks.
|
|
||||||
|
|
||||||
If the enum is a typed enum (has `_value_`), we structure the value first.
|
|
||||||
Otherwise, we use the value directly.
|
|
||||||
"""
|
|
||||||
if "_value_" in type.__annotations__:
|
|
||||||
val_type = type.__annotations__["_value_"]
|
|
||||||
val_hook = converter.get_structure_hook(val_type)
|
|
||||||
return lambda v, _: type(val_hook(v, val_type))
|
|
||||||
|
|
||||||
return lambda v, _: type(v)
|
|
||||||
@@ -13,18 +13,14 @@ class StructureHandlerNotFoundError(Exception):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message: str, type_: type) -> None:
|
def __init__(self, message: str, type_: type) -> None:
|
||||||
super().__init__(message, type_)
|
super().__init__(message)
|
||||||
self.message = message
|
|
||||||
self.type_ = type_
|
self.type_ = type_
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class BaseValidationError(ExceptionGroup):
|
class BaseValidationError(ExceptionGroup):
|
||||||
cl: type
|
cl: type
|
||||||
|
|
||||||
def __new__(cls, message: str, excs: Sequence[Exception], cl: type) -> Self:
|
def __new__(cls, message: str, excs: Sequence[Exception], cl: type):
|
||||||
obj = super().__new__(cls, message, excs)
|
obj = super().__new__(cls, message, excs)
|
||||||
obj.cl = cl
|
obj.cl = cl
|
||||||
return obj
|
return obj
|
||||||
@@ -39,7 +35,9 @@ class IterableValidationNote(str):
|
|||||||
index: Union[int, str] # Ints for list indices, strs for dict keys
|
index: Union[int, str] # Ints for list indices, strs for dict keys
|
||||||
type: Any
|
type: Any
|
||||||
|
|
||||||
def __new__(cls, string: str, index: Union[int, str], type: Any) -> Self:
|
def __new__(
|
||||||
|
cls, string: str, index: Union[int, str], type: Any
|
||||||
|
) -> "IterableValidationNote":
|
||||||
instance = str.__new__(cls, string)
|
instance = str.__new__(cls, string)
|
||||||
instance.index = index
|
instance.index = index
|
||||||
instance.type = type
|
instance.type = type
|
||||||
@@ -78,7 +76,7 @@ class AttributeValidationNote(str):
|
|||||||
name: str
|
name: str
|
||||||
type: Any
|
type: Any
|
||||||
|
|
||||||
def __new__(cls, string: str, name: str, type: Any) -> Self:
|
def __new__(cls, string: str, name: str, type: Any) -> "AttributeValidationNote":
|
||||||
instance = str.__new__(cls, string)
|
instance = str.__new__(cls, string)
|
||||||
instance.name = name
|
instance.name = name
|
||||||
instance.type = type
|
instance.type = type
|
||||||
@@ -124,15 +122,11 @@ class ForbiddenExtraKeysError(Exception):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, message: Optional[str], cl: type, extra_fields: set[str]
|
self, message: Optional[str], cl: type, extra_fields: set[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
self.message = message
|
|
||||||
self.cl = cl
|
self.cl = cl
|
||||||
self.extra_fields = extra_fields
|
self.extra_fields = extra_fields
|
||||||
|
cln = cl.__name__
|
||||||
|
|
||||||
super().__init__(message, cl, extra_fields)
|
super().__init__(
|
||||||
|
message
|
||||||
def __str__(self) -> str:
|
or f"Extra fields in constructor for {cln}: {', '.join(extra_fields)}"
|
||||||
return (
|
|
||||||
self.message
|
|
||||||
or f"Extra fields in constructor for {self.cl.__name__}: "
|
|
||||||
f"{', '.join(sorted(self.extra_fields))}"
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import re
|
|||||||
from collections.abc import Callable, Iterable, Mapping
|
from collections.abc import Callable, Iterable, Mapping
|
||||||
from typing import TYPE_CHECKING, Any, Final, Literal, TypeVar
|
from typing import TYPE_CHECKING, Any, Final, Literal, TypeVar
|
||||||
|
|
||||||
from attrs import NOTHING, Attribute, Converter, Factory, evolve
|
from attrs import NOTHING, Attribute, Factory
|
||||||
from typing_extensions import NoDefault
|
from typing_extensions import NoDefault
|
||||||
|
|
||||||
from .._compat import (
|
from .._compat import (
|
||||||
@@ -33,7 +33,7 @@ from ..types import SimpleStructureHook
|
|||||||
from ._consts import AttributeOverride, already_generating, neutral
|
from ._consts import AttributeOverride, already_generating, neutral
|
||||||
from ._generics import generate_mapping
|
from ._generics import generate_mapping
|
||||||
from ._lc import generate_unique_filename
|
from ._lc import generate_unique_filename
|
||||||
from ._shared import _annotated_override_or_default, find_structure_handler
|
from ._shared import find_structure_handler
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..converters import BaseConverter
|
from ..converters import BaseConverter
|
||||||
@@ -74,7 +74,7 @@ def make_dict_unstructure_fn_from_attrs(
|
|||||||
typevar_map: dict[str, Any] = {},
|
typevar_map: dict[str, Any] = {},
|
||||||
_cattrs_omit_if_default: bool = False,
|
_cattrs_omit_if_default: bool = False,
|
||||||
_cattrs_use_linecache: bool = True,
|
_cattrs_use_linecache: bool = True,
|
||||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
_cattrs_use_alias: bool = False,
|
||||||
_cattrs_include_init_false: bool = False,
|
_cattrs_include_init_false: bool = False,
|
||||||
**kwargs: AttributeOverride,
|
**kwargs: AttributeOverride,
|
||||||
) -> Callable[[T], dict[str, Any]]:
|
) -> Callable[[T], dict[str, Any]]:
|
||||||
@@ -95,17 +95,7 @@ def make_dict_unstructure_fn_from_attrs(
|
|||||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||||
will be included.
|
will be included.
|
||||||
|
|
||||||
.. versionadded:: 24.1.0
|
.. versionadded:: 24.1.0
|
||||||
.. versionchanged:: 25.2.0
|
|
||||||
The `_cattrs_use_alias` parameter takes its value from the given converter
|
|
||||||
by default.
|
|
||||||
.. versionchanged:: 26.1.0
|
|
||||||
`typing.Annotated[T, override()]` is now recognized and can be used to customize
|
|
||||||
unstructuring.
|
|
||||||
.. versionchanged:: 26.1.0
|
|
||||||
When `_cattrs_omit_if_default` is true and the attribute has an attrs converter
|
|
||||||
specified, the converter is applied to the default value before checking if it
|
|
||||||
is equal to the attribute's value.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fn_name = "unstructure_" + cl.__name__
|
fn_name = "unstructure_" + cl.__name__
|
||||||
@@ -114,27 +104,15 @@ def make_dict_unstructure_fn_from_attrs(
|
|||||||
invocation_lines = []
|
invocation_lines = []
|
||||||
internal_arg_parts = {}
|
internal_arg_parts = {}
|
||||||
|
|
||||||
if _cattrs_use_alias == "from_converter":
|
|
||||||
# BaseConverter doesn't have it so we're careful.
|
|
||||||
_cattrs_use_alias = getattr(converter, "use_alias", False)
|
|
||||||
|
|
||||||
for a in attrs:
|
for a in attrs:
|
||||||
attr_name = a.name
|
attr_name = a.name
|
||||||
if attr_name in kwargs:
|
override = kwargs.get(attr_name, neutral)
|
||||||
override = kwargs[attr_name]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(a.type, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[attr_name] = override
|
|
||||||
|
|
||||||
if override.omit:
|
if override.omit:
|
||||||
continue
|
continue
|
||||||
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
||||||
continue
|
continue
|
||||||
if override.rename is None:
|
if override.rename is None:
|
||||||
kn = attr_name if not _cattrs_use_alias else a.alias
|
kn = attr_name if not _cattrs_use_alias else a.alias
|
||||||
if kn != attr_name:
|
|
||||||
kwargs[attr_name] = evolve(override, rename=kn)
|
|
||||||
else:
|
else:
|
||||||
kn = override.rename
|
kn = override.rename
|
||||||
d = a.default
|
d = a.default
|
||||||
@@ -192,32 +170,16 @@ def make_dict_unstructure_fn_from_attrs(
|
|||||||
if isinstance(d, Factory):
|
if isinstance(d, Factory):
|
||||||
globs[def_name] = d.factory
|
globs[def_name] = d.factory
|
||||||
internal_arg_parts[def_name] = d.factory
|
internal_arg_parts[def_name] = d.factory
|
||||||
def_str = f"{def_name}(instance)" if d.takes_self else f"{def_name}()"
|
if d.takes_self:
|
||||||
|
lines.append(f" if instance.{attr_name} != {def_name}(instance):")
|
||||||
|
else:
|
||||||
|
lines.append(f" if instance.{attr_name} != {def_name}():")
|
||||||
|
lines.append(f" res['{kn}'] = {invoke}")
|
||||||
else:
|
else:
|
||||||
globs[def_name] = d
|
globs[def_name] = d
|
||||||
internal_arg_parts[def_name] = d
|
internal_arg_parts[def_name] = d
|
||||||
def_str = def_name
|
lines.append(f" if instance.{attr_name} != {def_name}:")
|
||||||
|
lines.append(f" res['{kn}'] = {invoke}")
|
||||||
c = a.converter
|
|
||||||
if c is not None:
|
|
||||||
conv_name = f"__c_conv_{attr_name}"
|
|
||||||
if isinstance(c, Converter):
|
|
||||||
globs[conv_name] = c
|
|
||||||
internal_arg_parts[conv_name] = c
|
|
||||||
field_name = f"__c_field_{attr_name}"
|
|
||||||
globs[field_name] = a
|
|
||||||
internal_arg_parts[field_name] = a
|
|
||||||
def_str = f"{conv_name}({def_str}, instance, {field_name})"
|
|
||||||
elif isinstance(d, Factory):
|
|
||||||
globs[conv_name] = c
|
|
||||||
internal_arg_parts[conv_name] = c
|
|
||||||
def_str = f"{conv_name}({def_str})"
|
|
||||||
else:
|
|
||||||
globs[def_name] = c(d)
|
|
||||||
internal_arg_parts[def_name] = c(d)
|
|
||||||
|
|
||||||
lines.append(f" if instance.{attr_name} != {def_str}:")
|
|
||||||
lines.append(f" res['{kn}'] = {invoke}")
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No default or no override.
|
# No default or no override.
|
||||||
@@ -255,7 +217,7 @@ def make_dict_unstructure_fn(
|
|||||||
converter: BaseConverter,
|
converter: BaseConverter,
|
||||||
_cattrs_omit_if_default: bool = False,
|
_cattrs_omit_if_default: bool = False,
|
||||||
_cattrs_use_linecache: bool = True,
|
_cattrs_use_linecache: bool = True,
|
||||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
_cattrs_use_alias: bool = False,
|
||||||
_cattrs_include_init_false: bool = False,
|
_cattrs_include_init_false: bool = False,
|
||||||
**kwargs: AttributeOverride,
|
**kwargs: AttributeOverride,
|
||||||
) -> Callable[[T], dict[str, Any]]:
|
) -> Callable[[T], dict[str, Any]]:
|
||||||
@@ -273,22 +235,13 @@ def make_dict_unstructure_fn(
|
|||||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||||
will be included.
|
will be included.
|
||||||
|
|
||||||
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
||||||
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
||||||
.. versionchanged:: 25.2.0
|
|
||||||
The `_cattrs_use_alias` parameter takes its value from the given converter
|
|
||||||
by default.
|
|
||||||
.. versionchanged:: 26.1.0
|
|
||||||
`typing.Annotated[T, override()]` is now recognized and can be used to customize
|
|
||||||
unstructuring.
|
|
||||||
"""
|
"""
|
||||||
origin = get_origin(cl)
|
origin = get_origin(cl)
|
||||||
attrs = adapted_fields(origin or cl) # type: ignore
|
attrs = adapted_fields(origin or cl) # type: ignore
|
||||||
|
|
||||||
mapping = {}
|
mapping = {}
|
||||||
if _cattrs_use_alias == "from_converter":
|
|
||||||
# BaseConverter doesn't have it so we're careful.
|
|
||||||
_cattrs_use_alias = getattr(converter, "use_alias", False)
|
|
||||||
if is_generic(cl):
|
if is_generic(cl):
|
||||||
mapping = generate_mapping(cl, mapping)
|
mapping = generate_mapping(cl, mapping)
|
||||||
|
|
||||||
@@ -336,7 +289,7 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
bool | Literal["from_converter"]
|
bool | Literal["from_converter"]
|
||||||
) = "from_converter",
|
) = "from_converter",
|
||||||
_cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
|
_cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
|
||||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
_cattrs_use_alias: bool = False,
|
||||||
_cattrs_include_init_false: bool = False,
|
_cattrs_include_init_false: bool = False,
|
||||||
**kwargs: AttributeOverride,
|
**kwargs: AttributeOverride,
|
||||||
) -> SimpleStructureHook[Mapping[str, Any], T]:
|
) -> SimpleStructureHook[Mapping[str, Any], T]:
|
||||||
@@ -361,13 +314,7 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||||
will be included.
|
will be included.
|
||||||
|
|
||||||
.. versionadded:: 24.1.0
|
.. versionadded:: 24.1.0
|
||||||
.. versionchanged:: 25.2.0
|
|
||||||
The `_cattrs_use_alias` parameter takes its value from the given converter
|
|
||||||
by default.
|
|
||||||
.. versionchanged:: 26.1.0
|
|
||||||
`typing.Annotated[T, override()]` is now recognized and can be used to customize
|
|
||||||
unstructuring.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cl_name = cl.__name__
|
cl_name = cl.__name__
|
||||||
@@ -403,9 +350,6 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
if _cattrs_forbid_extra_keys == "from_converter":
|
if _cattrs_forbid_extra_keys == "from_converter":
|
||||||
# BaseConverter doesn't have it so we're careful.
|
# BaseConverter doesn't have it so we're careful.
|
||||||
_cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False)
|
_cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False)
|
||||||
if _cattrs_use_alias == "from_converter":
|
|
||||||
# BaseConverter doesn't have it so we're careful.
|
|
||||||
_cattrs_use_alias = getattr(converter, "use_alias", False)
|
|
||||||
if _cattrs_detailed_validation == "from_converter":
|
if _cattrs_detailed_validation == "from_converter":
|
||||||
_cattrs_detailed_validation = converter.detailed_validation
|
_cattrs_detailed_validation = converter.detailed_validation
|
||||||
if _cattrs_prefer_attrib_converters == "from_converter":
|
if _cattrs_prefer_attrib_converters == "from_converter":
|
||||||
@@ -423,13 +367,7 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
internal_arg_parts["__c_avn"] = AttributeValidationNote
|
internal_arg_parts["__c_avn"] = AttributeValidationNote
|
||||||
for a in attrs:
|
for a in attrs:
|
||||||
an = a.name
|
an = a.name
|
||||||
if an in kwargs:
|
override = kwargs.get(an, neutral)
|
||||||
override = kwargs[an]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(a.type, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[an] = override
|
|
||||||
|
|
||||||
if override.omit:
|
if override.omit:
|
||||||
continue
|
continue
|
||||||
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
||||||
@@ -458,8 +396,6 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
ian = a.alias
|
ian = a.alias
|
||||||
if override.rename is None:
|
if override.rename is None:
|
||||||
kn = an if not _cattrs_use_alias else a.alias
|
kn = an if not _cattrs_use_alias else a.alias
|
||||||
if kn != an:
|
|
||||||
kwargs[an] = evolve(override, rename=kn)
|
|
||||||
else:
|
else:
|
||||||
kn = override.rename
|
kn = override.rename
|
||||||
|
|
||||||
@@ -560,24 +496,14 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
# The first loop deals with required args.
|
# The first loop deals with required args.
|
||||||
for a in attrs:
|
for a in attrs:
|
||||||
an = a.name
|
an = a.name
|
||||||
|
override = kwargs.get(an, neutral)
|
||||||
if an in kwargs:
|
|
||||||
override = kwargs[an]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(a.type, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[an] = override
|
|
||||||
|
|
||||||
if override.omit:
|
if override.omit:
|
||||||
continue
|
continue
|
||||||
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if a.default is not NOTHING:
|
if a.default is not NOTHING:
|
||||||
non_required.append(a)
|
non_required.append(a)
|
||||||
# The next loop will handle it.
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
t = a.type
|
t = a.type
|
||||||
if isinstance(t, TypeVar):
|
if isinstance(t, TypeVar):
|
||||||
t = typevar_map.get(t.__name__, t)
|
t = typevar_map.get(t.__name__, t)
|
||||||
@@ -597,8 +523,6 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
|
|
||||||
if override.rename is None:
|
if override.rename is None:
|
||||||
kn = an if not _cattrs_use_alias else a.alias
|
kn = an if not _cattrs_use_alias else a.alias
|
||||||
if kn != an:
|
|
||||||
kwargs[an] = evolve(override, rename=kn)
|
|
||||||
else:
|
else:
|
||||||
kn = override.rename
|
kn = override.rename
|
||||||
allowed_fields.add(kn)
|
allowed_fields.add(kn)
|
||||||
@@ -668,8 +592,6 @@ def make_dict_structure_fn_from_attrs(
|
|||||||
|
|
||||||
if override.rename is None:
|
if override.rename is None:
|
||||||
kn = an if not _cattrs_use_alias else a.alias
|
kn = an if not _cattrs_use_alias else a.alias
|
||||||
if kn != an:
|
|
||||||
kwargs[an] = evolve(override, rename=kn)
|
|
||||||
else:
|
else:
|
||||||
kn = override.rename
|
kn = override.rename
|
||||||
allowed_fields.add(kn)
|
allowed_fields.add(kn)
|
||||||
@@ -760,7 +682,7 @@ def make_dict_structure_fn(
|
|||||||
bool | Literal["from_converter"]
|
bool | Literal["from_converter"]
|
||||||
) = "from_converter",
|
) = "from_converter",
|
||||||
_cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
|
_cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
|
||||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
_cattrs_use_alias: bool = False,
|
||||||
_cattrs_include_init_false: bool = False,
|
_cattrs_include_init_false: bool = False,
|
||||||
**kwargs: AttributeOverride,
|
**kwargs: AttributeOverride,
|
||||||
) -> SimpleStructureHook[Mapping[str, Any], T]:
|
) -> SimpleStructureHook[Mapping[str, Any], T]:
|
||||||
@@ -784,20 +706,14 @@ def make_dict_structure_fn(
|
|||||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||||
will be included.
|
will be included.
|
||||||
|
|
||||||
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
||||||
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
||||||
.. versionchanged:: 23.2.0
|
.. versionchanged:: 23.2.0
|
||||||
The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters
|
The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters
|
||||||
take their values from the given converter by default.
|
take their values from the given converter by default.
|
||||||
.. versionchanged:: 24.1.0
|
.. versionchanged:: 24.1.0
|
||||||
The `_cattrs_prefer_attrib_converters` parameter takes its value from the given
|
The `_cattrs_prefer_attrib_converters` parameter takes its value from the given
|
||||||
converter by default.
|
converter by default.
|
||||||
.. versionchanged:: 25.2.0
|
|
||||||
The `_cattrs_use_alias` parameter takes its value from the given converter
|
|
||||||
by default.
|
|
||||||
.. versionchanged:: 26.1.0
|
|
||||||
`typing.Annotated[T, override()]` is now recognized and can be used to customize
|
|
||||||
unstructuring.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mapping = {}
|
mapping = {}
|
||||||
|
|||||||
@@ -4,31 +4,15 @@ from typing import TYPE_CHECKING, Any
|
|||||||
|
|
||||||
from attrs import NOTHING, Attribute, Factory
|
from attrs import NOTHING, Attribute, Factory
|
||||||
|
|
||||||
from .._compat import get_args, is_annotated, is_bare_final
|
from .._compat import is_bare_final
|
||||||
from ..dispatch import StructureHook
|
from ..dispatch import StructureHook
|
||||||
from ..errors import StructureHandlerNotFoundError
|
from ..errors import StructureHandlerNotFoundError
|
||||||
from ..fns import raise_error
|
from ..fns import raise_error
|
||||||
from ._consts import AttributeOverride
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..converters import BaseConverter
|
from ..converters import BaseConverter
|
||||||
|
|
||||||
|
|
||||||
def _annotated_override_or_default(
|
|
||||||
type: Any, default: AttributeOverride
|
|
||||||
) -> AttributeOverride:
|
|
||||||
"""
|
|
||||||
If the type is Annotated containing an AttributeOverride, return it.
|
|
||||||
Otherwise, return the default.
|
|
||||||
"""
|
|
||||||
if is_annotated(type):
|
|
||||||
for arg in get_args(type):
|
|
||||||
if isinstance(arg, AttributeOverride):
|
|
||||||
return arg
|
|
||||||
|
|
||||||
return default
|
|
||||||
|
|
||||||
|
|
||||||
def find_structure_handler(
|
def find_structure_handler(
|
||||||
a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False
|
a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False
|
||||||
) -> StructureHook | None:
|
) -> StructureHook | None:
|
||||||
|
|||||||
@@ -3,12 +3,23 @@ from __future__ import annotations
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from inspect import get_annotations
|
|
||||||
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
|
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
|
||||||
|
|
||||||
from attrs import NOTHING, Attribute
|
from attrs import NOTHING, Attribute
|
||||||
from typing_extensions import _TypedDictMeta
|
from typing_extensions import _TypedDictMeta
|
||||||
|
|
||||||
|
try:
|
||||||
|
from inspect import get_annotations
|
||||||
|
|
||||||
|
def get_annots(cl) -> dict[str, Any]:
|
||||||
|
return get_annotations(cl, eval_str=True)
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
|
||||||
|
def get_annots(cl) -> dict[str, Any]:
|
||||||
|
return cl.__dict__.get("__annotations__", {})
|
||||||
|
|
||||||
|
|
||||||
from .._compat import (
|
from .._compat import (
|
||||||
get_full_type_hints,
|
get_full_type_hints,
|
||||||
get_notrequired_base,
|
get_notrequired_base,
|
||||||
@@ -29,7 +40,7 @@ from . import AttributeOverride
|
|||||||
from ._consts import already_generating, neutral
|
from ._consts import already_generating, neutral
|
||||||
from ._generics import generate_mapping
|
from ._generics import generate_mapping
|
||||||
from ._lc import generate_unique_filename
|
from ._lc import generate_unique_filename
|
||||||
from ._shared import _annotated_override_or_default, find_structure_handler
|
from ._shared import find_structure_handler
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..converters import BaseConverter
|
from ..converters import BaseConverter
|
||||||
@@ -39,10 +50,6 @@ __all__ = ["make_dict_structure_fn", "make_dict_unstructure_fn"]
|
|||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
def get_annots(cl) -> dict[str, Any]:
|
|
||||||
return get_annotations(cl, eval_str=True)
|
|
||||||
|
|
||||||
|
|
||||||
def make_dict_unstructure_fn(
|
def make_dict_unstructure_fn(
|
||||||
cl: type[T],
|
cl: type[T],
|
||||||
converter: BaseConverter,
|
converter: BaseConverter,
|
||||||
@@ -102,20 +109,11 @@ def make_dict_unstructure_fn(
|
|||||||
# * all attributes resolve to `converter._unstructure_identity`
|
# * all attributes resolve to `converter._unstructure_identity`
|
||||||
for a in attrs:
|
for a in attrs:
|
||||||
attr_name = a.name
|
attr_name = a.name
|
||||||
t = a.type
|
override = kwargs.get(attr_name, neutral)
|
||||||
nrb = get_notrequired_base(t)
|
|
||||||
if nrb is not NOTHING:
|
|
||||||
t = nrb
|
|
||||||
|
|
||||||
if attr_name in kwargs:
|
|
||||||
override = kwargs[attr_name]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(t, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[attr_name] = override
|
|
||||||
if override != neutral:
|
if override != neutral:
|
||||||
break
|
break
|
||||||
handler = None
|
handler = None
|
||||||
|
t = a.type
|
||||||
|
|
||||||
if isinstance(t, TypeVar):
|
if isinstance(t, TypeVar):
|
||||||
if t.__name__ in mapping:
|
if t.__name__ in mapping:
|
||||||
@@ -127,6 +125,9 @@ def make_dict_unstructure_fn(
|
|||||||
t = deep_copy_with(t, mapping, cl)
|
t = deep_copy_with(t, mapping, cl)
|
||||||
|
|
||||||
if handler is None:
|
if handler is None:
|
||||||
|
nrb = get_notrequired_base(t)
|
||||||
|
if nrb is not NOTHING:
|
||||||
|
t = nrb
|
||||||
try:
|
try:
|
||||||
handler = converter.get_unstructure_hook(t)
|
handler = converter.get_unstructure_hook(t)
|
||||||
except RecursionError:
|
except RecursionError:
|
||||||
@@ -141,22 +142,10 @@ def make_dict_unstructure_fn(
|
|||||||
|
|
||||||
for ix, a in enumerate(attrs):
|
for ix, a in enumerate(attrs):
|
||||||
attr_name = a.name
|
attr_name = a.name
|
||||||
t = a.type
|
override = kwargs.get(attr_name, neutral)
|
||||||
nrb = get_notrequired_base(t)
|
|
||||||
if nrb is not NOTHING:
|
|
||||||
t = nrb
|
|
||||||
|
|
||||||
if attr_name in kwargs:
|
|
||||||
override = kwargs[attr_name]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(t, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[attr_name] = override
|
|
||||||
|
|
||||||
if override.omit:
|
if override.omit:
|
||||||
lines.append(f" res.pop('{attr_name}', None)")
|
lines.append(f" res.pop('{attr_name}', None)")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if override.rename is not None:
|
if override.rename is not None:
|
||||||
# We also need to pop when renaming, since we're copying
|
# We also need to pop when renaming, since we're copying
|
||||||
# the original.
|
# the original.
|
||||||
@@ -171,6 +160,8 @@ def make_dict_unstructure_fn(
|
|||||||
if override.unstruct_hook is not None:
|
if override.unstruct_hook is not None:
|
||||||
handler = override.unstruct_hook
|
handler = override.unstruct_hook
|
||||||
else:
|
else:
|
||||||
|
t = a.type
|
||||||
|
|
||||||
if isinstance(t, TypeVar):
|
if isinstance(t, TypeVar):
|
||||||
if t.__name__ in mapping:
|
if t.__name__ in mapping:
|
||||||
t = mapping[t.__name__]
|
t = mapping[t.__name__]
|
||||||
@@ -180,6 +171,9 @@ def make_dict_unstructure_fn(
|
|||||||
t = deep_copy_with(t, mapping, cl)
|
t = deep_copy_with(t, mapping, cl)
|
||||||
|
|
||||||
if handler is None:
|
if handler is None:
|
||||||
|
nrb = get_notrequired_base(t)
|
||||||
|
if nrb is not NOTHING:
|
||||||
|
t = nrb
|
||||||
try:
|
try:
|
||||||
handler = converter.get_unstructure_hook(t)
|
handler = converter.get_unstructure_hook(t)
|
||||||
except RecursionError:
|
except RecursionError:
|
||||||
@@ -226,15 +220,12 @@ def make_dict_unstructure_fn(
|
|||||||
)
|
)
|
||||||
|
|
||||||
eval(compile(script, fname, "exec"), globs)
|
eval(compile(script, fname, "exec"), globs)
|
||||||
|
|
||||||
res = globs[fn_name]
|
|
||||||
res.overrides = kwargs
|
|
||||||
finally:
|
finally:
|
||||||
working_set.remove(cl)
|
working_set.remove(cl)
|
||||||
if not working_set:
|
if not working_set:
|
||||||
del already_generating.working_set
|
del already_generating.working_set
|
||||||
|
|
||||||
return res
|
return globs[fn_name]
|
||||||
|
|
||||||
|
|
||||||
def make_dict_structure_fn(
|
def make_dict_structure_fn(
|
||||||
@@ -335,25 +326,20 @@ def make_dict_structure_fn(
|
|||||||
for ix, a in enumerate(attrs):
|
for ix, a in enumerate(attrs):
|
||||||
an = a.name
|
an = a.name
|
||||||
attr_required = an in req_keys
|
attr_required = an in req_keys
|
||||||
t = a.type
|
override = kwargs.get(an, neutral)
|
||||||
nrb = get_notrequired_base(t)
|
|
||||||
if nrb is not NOTHING:
|
|
||||||
t = nrb
|
|
||||||
|
|
||||||
if an in kwargs:
|
|
||||||
override = kwargs[an]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(t, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[an] = override
|
|
||||||
if override.omit:
|
if override.omit:
|
||||||
continue
|
continue
|
||||||
|
t = a.type
|
||||||
|
|
||||||
if isinstance(t, TypeVar):
|
if isinstance(t, TypeVar):
|
||||||
t = mapping.get(t.__name__, t)
|
t = mapping.get(t.__name__, t)
|
||||||
elif is_generic(t) and not is_bare(t) and not is_annotated(t):
|
elif is_generic(t) and not is_bare(t) and not is_annotated(t):
|
||||||
t = deep_copy_with(t, mapping, cl)
|
t = deep_copy_with(t, mapping, cl)
|
||||||
|
|
||||||
|
nrb = get_notrequired_base(t)
|
||||||
|
if nrb is not NOTHING:
|
||||||
|
t = nrb
|
||||||
|
|
||||||
if is_generic(t) and not is_bare(t) and not is_annotated(t):
|
if is_generic(t) and not is_bare(t) and not is_annotated(t):
|
||||||
t = deep_copy_with(t, mapping, cl)
|
t = deep_copy_with(t, mapping, cl)
|
||||||
|
|
||||||
@@ -413,12 +399,7 @@ def make_dict_structure_fn(
|
|||||||
for ix, a in enumerate(attrs):
|
for ix, a in enumerate(attrs):
|
||||||
an = a.name
|
an = a.name
|
||||||
attr_required = an in req_keys
|
attr_required = an in req_keys
|
||||||
if an in kwargs:
|
override = kwargs.get(an, neutral)
|
||||||
override = kwargs[an]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(a.type, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[an] = override
|
|
||||||
if override.omit:
|
if override.omit:
|
||||||
continue
|
continue
|
||||||
if not attr_required:
|
if not attr_required:
|
||||||
@@ -467,18 +448,13 @@ def make_dict_structure_fn(
|
|||||||
if non_required:
|
if non_required:
|
||||||
for ix, a in non_required:
|
for ix, a in non_required:
|
||||||
an = a.name
|
an = a.name
|
||||||
|
override = kwargs.get(an, neutral)
|
||||||
t = a.type
|
t = a.type
|
||||||
|
|
||||||
nrb = get_notrequired_base(t)
|
nrb = get_notrequired_base(t)
|
||||||
if nrb is not NOTHING:
|
if nrb is not NOTHING:
|
||||||
t = nrb
|
t = nrb
|
||||||
|
|
||||||
if an in kwargs:
|
|
||||||
override = kwargs[an]
|
|
||||||
else:
|
|
||||||
override = _annotated_override_or_default(t, neutral)
|
|
||||||
if override != neutral:
|
|
||||||
kwargs[an] = override
|
|
||||||
|
|
||||||
if isinstance(t, TypeVar):
|
if isinstance(t, TypeVar):
|
||||||
t = mapping.get(t.__name__, t)
|
t = mapping.get(t.__name__, t)
|
||||||
elif is_generic(t) and not is_bare(t) and not is_annotated(t):
|
elif is_generic(t) and not is_bare(t) and not is_annotated(t):
|
||||||
@@ -538,9 +514,7 @@ def make_dict_structure_fn(
|
|||||||
)
|
)
|
||||||
|
|
||||||
eval(compile(script, fname, "exec"), globs)
|
eval(compile(script, fname, "exec"), globs)
|
||||||
res = globs[fn_name]
|
return globs[fn_name]
|
||||||
res.overrides = kwargs
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def _adapted_fields(cls: Any) -> list[Attribute]:
|
def _adapted_fields(cls: Any) -> list[Attribute]:
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
from collections.abc import Callable
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, ParamSpec, TypeVar, get_args
|
from typing import Any, Callable, TypeVar, get_args
|
||||||
|
|
||||||
from .._compat import is_subclass
|
from .._compat import is_subclass
|
||||||
from ..converters import Converter, UnstructureHook
|
from ..converters import Converter, UnstructureHook
|
||||||
from ..fns import identity
|
from ..fns import identity
|
||||||
|
|
||||||
|
if sys.version_info[:2] < (3, 10):
|
||||||
|
from typing_extensions import ParamSpec
|
||||||
|
else:
|
||||||
|
from typing import ParamSpec
|
||||||
|
|
||||||
|
|
||||||
def validate_datetime(v, _):
|
def validate_datetime(v, _):
|
||||||
if not isinstance(v, datetime):
|
if not isinstance(v, datetime):
|
||||||
|
|||||||
@@ -99,11 +99,11 @@ def configure_converter(converter: BaseConverter):
|
|||||||
|
|
||||||
# datetime inherits from date, so identity unstructure hook used
|
# datetime inherits from date, so identity unstructure hook used
|
||||||
# here to prevent the date unstructure hook running.
|
# here to prevent the date unstructure hook running.
|
||||||
converter.register_unstructure_hook(datetime, identity)
|
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||||
converter.register_structure_hook(datetime, validate_datetime)
|
converter.register_structure_hook(datetime, validate_datetime)
|
||||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_factory(
|
||||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def configure_converter(converter: BaseConverter):
|
|||||||
)
|
)
|
||||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_factory(
|
||||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def configure_converter(converter: BaseConverter) -> None:
|
|||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_factory(
|
||||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||||
)
|
)
|
||||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda _: identity)
|
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||||
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ def configure_converter(converter: BaseConverter) -> None:
|
|||||||
converter.register_structure_hook(
|
converter.register_structure_hook(
|
||||||
date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date()
|
date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date()
|
||||||
)
|
)
|
||||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_factory(
|
||||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,27 +3,18 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from collections.abc import Callable
|
|
||||||
from dataclasses import is_dataclass
|
from dataclasses import is_dataclass
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, TypeVar, Union, get_type_hints
|
from typing import Any, Callable, TypeVar, Union, get_type_hints
|
||||||
|
|
||||||
from attrs import has as attrs_has
|
from attrs import has as attrs_has
|
||||||
from attrs import resolve_types
|
from attrs import resolve_types
|
||||||
from msgspec import Struct, convert, to_builtins
|
from msgspec import Struct, convert, to_builtins
|
||||||
from msgspec.json import Encoder, decode
|
from msgspec.json import Encoder, decode
|
||||||
|
|
||||||
from .._compat import (
|
from .._compat import fields, get_args, get_origin, is_bare, is_mapping, is_sequence
|
||||||
fields,
|
|
||||||
get_args,
|
|
||||||
get_origin,
|
|
||||||
is_bare,
|
|
||||||
is_mapping,
|
|
||||||
is_sequence,
|
|
||||||
is_subclass,
|
|
||||||
)
|
|
||||||
from ..cols import is_namedtuple
|
from ..cols import is_namedtuple
|
||||||
from ..converters import BaseConverter, Converter
|
from ..converters import BaseConverter, Converter
|
||||||
from ..dispatch import UnstructureHook
|
from ..dispatch import UnstructureHook
|
||||||
@@ -83,9 +74,7 @@ def configure_converter(converter: Converter) -> None:
|
|||||||
configure_passthroughs(converter)
|
configure_passthroughs(converter)
|
||||||
|
|
||||||
converter.register_unstructure_hook(Struct, to_builtins)
|
converter.register_unstructure_hook(Struct, to_builtins)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook(Enum, identity)
|
||||||
lambda t: is_subclass(t, Enum), lambda t, c: identity
|
|
||||||
)
|
|
||||||
|
|
||||||
converter.register_structure_hook(Struct, convert)
|
converter.register_structure_hook(Struct, convert)
|
||||||
converter.register_structure_hook(bytes, lambda v, _: b64decode(v))
|
converter.register_structure_hook(bytes, lambda v, _: b64decode(v))
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ def configure_converter(converter: Converter) -> None:
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_func(
|
||||||
partial(is_primitive_enum, include_bare_enums=True), lambda t: identity
|
partial(is_primitive_enum, include_bare_enums=True), identity
|
||||||
)
|
)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_factory(
|
||||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from tomlkit.items import Float, Integer, String
|
|||||||
|
|
||||||
from .._compat import is_mapping, is_subclass
|
from .._compat import is_mapping, is_subclass
|
||||||
from ..converters import BaseConverter, Converter
|
from ..converters import BaseConverter, Converter
|
||||||
from ..fns import identity
|
|
||||||
from ..strategies import configure_union_passthrough
|
from ..strategies import configure_union_passthrough
|
||||||
from . import validate_datetime, wrap
|
from . import validate_datetime, wrap
|
||||||
|
|
||||||
@@ -38,9 +37,6 @@ def configure_converter(converter: BaseConverter):
|
|||||||
* sets are serialized as lists
|
* sets are serialized as lists
|
||||||
* tuples are serializas as lists
|
* tuples are serializas as lists
|
||||||
* mapping keys are coerced into strings when unstructuring
|
* mapping keys are coerced into strings when unstructuring
|
||||||
|
|
||||||
.. versionchanged:: 26.1.0
|
|
||||||
date objects are now passed through to tomlkit without unstructuring.
|
|
||||||
"""
|
"""
|
||||||
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
||||||
converter.register_unstructure_hook(
|
converter.register_unstructure_hook(
|
||||||
@@ -71,12 +67,10 @@ def configure_converter(converter: BaseConverter):
|
|||||||
|
|
||||||
# datetime inherits from date, so identity unstructure hook used
|
# datetime inherits from date, so identity unstructure hook used
|
||||||
# here to prevent the date unstructure hook running.
|
# here to prevent the date unstructure hook running.
|
||||||
converter.register_unstructure_hook(datetime, identity)
|
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||||
converter.register_structure_hook(datetime, validate_datetime)
|
converter.register_structure_hook(datetime, validate_datetime)
|
||||||
converter.register_unstructure_hook(date, identity)
|
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||||
converter.register_structure_hook(
|
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||||
date, lambda v, _: v if isinstance(v, date) else date.fromisoformat(v)
|
|
||||||
)
|
|
||||||
configure_union_passthrough(
|
configure_union_passthrough(
|
||||||
Union[str, String, bool, int, Integer, float, Float], converter
|
Union[str, String, bool, int, Integer, float, Float], converter
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
"""Preconfigured converters for tomllib."""
|
|
||||||
|
|
||||||
from base64 import b85decode, b85encode
|
|
||||||
from collections.abc import Set
|
|
||||||
from datetime import date, datetime
|
|
||||||
from enum import Enum
|
|
||||||
from operator import attrgetter
|
|
||||||
from typing import Any, TypeVar, Union
|
|
||||||
|
|
||||||
try:
|
|
||||||
from tomllib import loads
|
|
||||||
except ImportError:
|
|
||||||
from tomli import loads
|
|
||||||
|
|
||||||
try:
|
|
||||||
from tomli_w import dumps
|
|
||||||
except ImportError: # pragma: nocover
|
|
||||||
dumps = None
|
|
||||||
|
|
||||||
from .._compat import is_mapping, is_subclass
|
|
||||||
from ..converters import BaseConverter, Converter
|
|
||||||
from ..fns import identity
|
|
||||||
from ..strategies import configure_union_passthrough
|
|
||||||
from . import validate_datetime, wrap
|
|
||||||
|
|
||||||
__all__ = ["TomllibConverter", "configure_converter", "make_converter"]
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
_enum_value_getter = attrgetter("_value_")
|
|
||||||
|
|
||||||
|
|
||||||
class TomllibConverter(Converter):
|
|
||||||
"""A converter subclass specialized for tomllib."""
|
|
||||||
|
|
||||||
if dumps is not None:
|
|
||||||
|
|
||||||
def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str:
|
|
||||||
return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs)
|
|
||||||
|
|
||||||
def loads(self, data: str, cl: type[T], **kwargs: Any) -> T:
|
|
||||||
return self.structure(loads(data, **kwargs), cl)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_converter(converter: BaseConverter):
|
|
||||||
"""
|
|
||||||
Configure the converter for use with the tomllib library.
|
|
||||||
|
|
||||||
* bytes are serialized as base85 strings
|
|
||||||
* sets are serialized as lists
|
|
||||||
* tuples are serializas as lists
|
|
||||||
* mapping keys are coerced into strings when unstructuring
|
|
||||||
* dates and datetimes are left for tomllib to handle
|
|
||||||
"""
|
|
||||||
converter.register_structure_hook(bytes, lambda v, _: b85decode(v))
|
|
||||||
converter.register_unstructure_hook(
|
|
||||||
bytes, lambda v: (b85encode(v) if v else b"").decode("utf8")
|
|
||||||
)
|
|
||||||
|
|
||||||
@converter.register_unstructure_hook_factory(is_mapping)
|
|
||||||
def gen_unstructure_mapping(cl: Any, unstructure_to=None):
|
|
||||||
key_handler = str
|
|
||||||
args = getattr(cl, "__args__", None)
|
|
||||||
if args:
|
|
||||||
if is_subclass(args[0], str):
|
|
||||||
key_handler = _enum_value_getter if is_subclass(args[0], Enum) else None
|
|
||||||
elif is_subclass(args[0], bytes):
|
|
||||||
|
|
||||||
def key_handler(k: bytes):
|
|
||||||
return b85encode(k).decode("utf8")
|
|
||||||
|
|
||||||
return converter.gen_unstructure_mapping(
|
|
||||||
cl, unstructure_to=unstructure_to, key_handler=key_handler
|
|
||||||
)
|
|
||||||
|
|
||||||
converter.register_unstructure_hook(datetime, identity)
|
|
||||||
converter.register_structure_hook(datetime, validate_datetime)
|
|
||||||
converter.register_unstructure_hook(date, identity)
|
|
||||||
converter.register_structure_hook(
|
|
||||||
date, lambda v, _: v if isinstance(v, date) else date.fromisoformat(v)
|
|
||||||
)
|
|
||||||
configure_union_passthrough(Union[str, int, float, bool], converter)
|
|
||||||
|
|
||||||
|
|
||||||
@wrap(TomllibConverter)
|
|
||||||
def make_converter(*args: Any, **kwargs: Any) -> TomllibConverter:
|
|
||||||
kwargs["unstruct_collection_overrides"] = {
|
|
||||||
Set: list,
|
|
||||||
tuple: list,
|
|
||||||
**kwargs.get("unstruct_collection_overrides", {}),
|
|
||||||
}
|
|
||||||
res = TomllibConverter(*args, **kwargs)
|
|
||||||
configure_converter(res)
|
|
||||||
|
|
||||||
return res
|
|
||||||
@@ -47,7 +47,7 @@ def configure_converter(converter: BaseConverter):
|
|||||||
converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
|
converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v))
|
||||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||||
converter.register_unstructure_hook_factory(
|
converter.register_unstructure_hook_factory(
|
||||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,25 +9,21 @@ from typing import Any, Callable, TypeVar, Union
|
|||||||
from ..converters import BaseConverter
|
from ..converters import BaseConverter
|
||||||
from ..gen import AttributeOverride, make_dict_structure_fn, make_dict_unstructure_fn
|
from ..gen import AttributeOverride, make_dict_structure_fn, make_dict_unstructure_fn
|
||||||
from ..gen._consts import already_generating
|
from ..gen._consts import already_generating
|
||||||
from ..subclasses import subclasses
|
|
||||||
|
|
||||||
|
|
||||||
def _make_subclasses_tree(cl: type) -> list[type]:
|
def _make_subclasses_tree(cl: type) -> list[type]:
|
||||||
# get class origin for accessing subclasses (see #648 for more info)
|
# get class origin for accessing subclasses (see #648 for more info)
|
||||||
cls_origin = typing.get_origin(cl) or cl
|
cls_origin = typing.get_origin(cl) or cl
|
||||||
|
return [cl] + [
|
||||||
# Use a dict to deduplicate and keep insertion order.
|
sscl
|
||||||
seen = {cl: None}
|
for scl in cls_origin.__subclasses__()
|
||||||
for scl in subclasses(cls_origin):
|
for sscl in _make_subclasses_tree(scl)
|
||||||
for sscl in _make_subclasses_tree(scl):
|
]
|
||||||
seen[sscl] = None
|
|
||||||
return list(seen)
|
|
||||||
|
|
||||||
|
|
||||||
def _has_subclasses(cl: type, given_subclasses: tuple[type, ...]) -> bool:
|
def _has_subclasses(cl: type, given_subclasses: tuple[type, ...]) -> bool:
|
||||||
"""Whether the given class has subclasses from `given_subclasses`."""
|
"""Whether the given class has subclasses from `given_subclasses`."""
|
||||||
cls_origin = typing.get_origin(cl) or cl
|
actual = set(cl.__subclasses__())
|
||||||
actual = set(subclasses(cls_origin))
|
|
||||||
given = set(given_subclasses)
|
given = set(given_subclasses)
|
||||||
return bool(actual & given)
|
return bool(actual & given)
|
||||||
|
|
||||||
@@ -72,9 +68,6 @@ def include_subclasses(
|
|||||||
.. versionchanged:: 24.1.0
|
.. versionchanged:: 24.1.0
|
||||||
When overrides are not provided, hooks for individual classes are retrieved from
|
When overrides are not provided, hooks for individual classes are retrieved from
|
||||||
the converter instead of generated with no overrides, using converter defaults.
|
the converter instead of generated with no overrides, using converter defaults.
|
||||||
.. versionchanged:: 25.2.0
|
|
||||||
Slotted dataclasses work on Python 3.14 via :func:`cattrs.subclasses.subclasses`,
|
|
||||||
which filters out duplicate classes caused by slotting.
|
|
||||||
"""
|
"""
|
||||||
# Due to https://github.com/python-attrs/attrs/issues/1047
|
# Due to https://github.com/python-attrs/attrs/issues/1047
|
||||||
collect()
|
collect()
|
||||||
@@ -238,13 +231,7 @@ def _include_subclasses_with_union_strategy(
|
|||||||
return cls is _cl
|
return cls is _cl
|
||||||
|
|
||||||
converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook)
|
converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook)
|
||||||
subclasses = tuple(
|
subclasses = tuple([c for c in union_classes if issubclass(c, cl)])
|
||||||
[
|
|
||||||
c
|
|
||||||
for c in union_classes
|
|
||||||
if issubclass(typing.get_origin(c) or c, typing.get_origin(cl) or cl)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if len(subclasses) > 1:
|
if len(subclasses) > 1:
|
||||||
u = Union[subclasses] # type: ignore
|
u = Union[subclasses] # type: ignore
|
||||||
union_strategy(u, converter)
|
union_strategy(u, converter)
|
||||||
|
|||||||
@@ -52,10 +52,23 @@ def configure_tagged_union(
|
|||||||
if is_type_alias(union):
|
if is_type_alias(union):
|
||||||
union = union.__value__
|
union = union.__value__
|
||||||
args = union.__args__
|
args = union.__args__
|
||||||
|
|
||||||
tag_to_hook = {}
|
tag_to_hook = {}
|
||||||
exact_cl_unstruct_hooks = {}
|
exact_cl_unstruct_hooks = {}
|
||||||
cl_to_tag = {}
|
for cl in args:
|
||||||
|
tag = tag_generator(cl)
|
||||||
|
struct_handler = converter.get_structure_hook(cl)
|
||||||
|
unstruct_handler = converter.get_unstructure_hook(cl)
|
||||||
|
|
||||||
|
def structure_union_member(val: dict, _cl=cl, _h=struct_handler) -> cl:
|
||||||
|
return _h(val, _cl)
|
||||||
|
|
||||||
|
def unstructure_union_member(val: union, _h=unstruct_handler) -> dict:
|
||||||
|
return _h(val)
|
||||||
|
|
||||||
|
tag_to_hook[tag] = structure_union_member
|
||||||
|
exact_cl_unstruct_hooks[cl] = unstructure_union_member
|
||||||
|
|
||||||
|
cl_to_tag = {cl: tag_generator(cl) for cl in args}
|
||||||
|
|
||||||
if default is not NOTHING:
|
if default is not NOTHING:
|
||||||
default_handler = converter.get_structure_hook(default)
|
default_handler = converter.get_structure_hook(default)
|
||||||
@@ -63,9 +76,36 @@ def configure_tagged_union(
|
|||||||
def structure_default(val: dict, _cl=default, _h=default_handler):
|
def structure_default(val: dict, _cl=default, _h=default_handler):
|
||||||
return _h(val, _cl)
|
return _h(val, _cl)
|
||||||
|
|
||||||
tag_to_hook = defaultdict(lambda: structure_default)
|
tag_to_hook = defaultdict(lambda: structure_default, tag_to_hook)
|
||||||
cl_to_tag = defaultdict(lambda: default)
|
cl_to_tag = defaultdict(lambda: default, cl_to_tag)
|
||||||
|
|
||||||
|
def unstructure_tagged_union(
|
||||||
|
val: union,
|
||||||
|
_exact_cl_unstruct_hooks=exact_cl_unstruct_hooks,
|
||||||
|
_cl_to_tag=cl_to_tag,
|
||||||
|
_tag_name=tag_name,
|
||||||
|
) -> dict:
|
||||||
|
res = _exact_cl_unstruct_hooks[val.__class__](val)
|
||||||
|
res[_tag_name] = _cl_to_tag[val.__class__]
|
||||||
|
return res
|
||||||
|
|
||||||
|
if default is NOTHING:
|
||||||
|
if getattr(converter, "forbid_extra_keys", False):
|
||||||
|
|
||||||
|
def structure_tagged_union(
|
||||||
|
val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
|
||||||
|
) -> union:
|
||||||
|
val = val.copy()
|
||||||
|
return _tag_to_cl[val.pop(_tag_name)](val)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def structure_tagged_union(
|
||||||
|
val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
|
||||||
|
) -> union:
|
||||||
|
return _tag_to_cl[val[_tag_name]](val)
|
||||||
|
|
||||||
|
else:
|
||||||
if getattr(converter, "forbid_extra_keys", False):
|
if getattr(converter, "forbid_extra_keys", False):
|
||||||
|
|
||||||
def structure_tagged_union(
|
def structure_tagged_union(
|
||||||
@@ -95,54 +135,11 @@ def configure_tagged_union(
|
|||||||
return _tag_to_hook[val[_tag_name]](val)
|
return _tag_to_hook[val[_tag_name]](val)
|
||||||
return _dh(val, _default)
|
return _dh(val, _default)
|
||||||
|
|
||||||
else:
|
|
||||||
if getattr(converter, "forbid_extra_keys", False):
|
|
||||||
|
|
||||||
def structure_tagged_union(
|
|
||||||
val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
|
|
||||||
) -> union:
|
|
||||||
val = val.copy()
|
|
||||||
return _tag_to_cl[val.pop(_tag_name)](val)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def structure_tagged_union(
|
|
||||||
val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name
|
|
||||||
) -> union:
|
|
||||||
return _tag_to_cl[val[_tag_name]](val)
|
|
||||||
|
|
||||||
def unstructure_tagged_union(
|
|
||||||
val: union,
|
|
||||||
_exact_cl_unstruct_hooks=exact_cl_unstruct_hooks,
|
|
||||||
_cl_to_tag=cl_to_tag,
|
|
||||||
_tag_name=tag_name,
|
|
||||||
) -> dict:
|
|
||||||
res = _exact_cl_unstruct_hooks[val.__class__](val)
|
|
||||||
res[_tag_name] = _cl_to_tag[val.__class__]
|
|
||||||
return res
|
|
||||||
|
|
||||||
converter.register_unstructure_hook(union, unstructure_tagged_union)
|
converter.register_unstructure_hook(union, unstructure_tagged_union)
|
||||||
converter.register_structure_hook(union, structure_tagged_union)
|
converter.register_structure_hook(union, structure_tagged_union)
|
||||||
|
|
||||||
for cl in args:
|
|
||||||
tag = tag_generator(cl)
|
|
||||||
struct_handler = converter.get_structure_hook(cl)
|
|
||||||
unstruct_handler = converter.get_unstructure_hook(cl)
|
|
||||||
|
|
||||||
def structure_union_member(val: dict, _cl=cl, _h=struct_handler) -> cl:
|
def configure_union_passthrough(union: Any, converter: BaseConverter) -> None:
|
||||||
return _h(val, _cl)
|
|
||||||
|
|
||||||
def unstructure_union_member(val: union, _h=unstruct_handler) -> dict:
|
|
||||||
return _h(val)
|
|
||||||
|
|
||||||
tag_to_hook[tag] = structure_union_member
|
|
||||||
exact_cl_unstruct_hooks[cl] = unstructure_union_member
|
|
||||||
cl_to_tag[cl] = tag
|
|
||||||
|
|
||||||
|
|
||||||
def configure_union_passthrough(
|
|
||||||
union: Any, converter: BaseConverter, accept_ints_as_floats: bool = True
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Configure the converter to support validating and passing through unions of the
|
Configure the converter to support validating and passing through unions of the
|
||||||
provided types and their subsets.
|
provided types and their subsets.
|
||||||
@@ -165,14 +162,7 @@ def configure_union_passthrough(
|
|||||||
If the union contains a class and one or more of its subclasses, the subclasses
|
If the union contains a class and one or more of its subclasses, the subclasses
|
||||||
will also be included when validating the superclass.
|
will also be included when validating the superclass.
|
||||||
|
|
||||||
:param accept_ints_as_floats: When set (the default), if the provided union
|
|
||||||
contains both ints and floats, actual unions containing only floats will also accept
|
|
||||||
ints. See https://typing.python.org/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
.. versionadded:: 23.2.0
|
.. versionadded:: 23.2.0
|
||||||
.. versionchanged:: 25.2.0
|
|
||||||
Introduced the `accept_ints_as_floats` parameter.
|
|
||||||
"""
|
"""
|
||||||
args = set(union.__args__)
|
args = set(union.__args__)
|
||||||
|
|
||||||
@@ -215,16 +205,6 @@ def configure_union_passthrough(
|
|||||||
and not is_literal(a)
|
and not is_literal(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
# By default, when floats are part of the union, accept ints too.
|
|
||||||
if (
|
|
||||||
accept_ints_as_floats
|
|
||||||
and int in args
|
|
||||||
and float in args
|
|
||||||
and float in non_literal_classes
|
|
||||||
and int not in non_literal_classes
|
|
||||||
):
|
|
||||||
non_literal_classes.add(int)
|
|
||||||
|
|
||||||
if spillover:
|
if spillover:
|
||||||
spillover_type = (
|
spillover_type = (
|
||||||
Union[tuple(spillover)] if len(spillover) > 1 else next(iter(spillover))
|
Union[tuple(spillover)] if len(spillover) > 1 else next(iter(spillover))
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
if sys.version_info <= (3, 13):
|
|
||||||
|
|
||||||
def subclasses(cls: type) -> list[type]:
|
|
||||||
"""A proxy for `cls.__subclasses__()` on older Pythons."""
|
|
||||||
return cls.__subclasses__()
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def subclasses(cls: type) -> list[type]:
|
|
||||||
"""A helper for getting subclasses of a class.
|
|
||||||
|
|
||||||
Filters out duplicate subclasses of slot dataclasses and attrs classes.
|
|
||||||
"""
|
|
||||||
return [
|
|
||||||
cl
|
|
||||||
for cl in cls.__subclasses__()
|
|
||||||
if (
|
|
||||||
not (
|
|
||||||
"__slots__" not in cl.__dict__
|
|
||||||
and hasattr(cls, "__dataclass_params__")
|
|
||||||
and cls.__dataclass_params__.slots
|
|
||||||
)
|
|
||||||
and not hasattr(cls, "__attrs_base_of_slotted__")
|
|
||||||
)
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
class ExceptionGroup(Exception):
|
|
||||||
"""Minimal backport used by bundled libs on Python < 3.11."""
|
|
||||||
|
|
||||||
def __new__(cls, message, exceptions):
|
|
||||||
obj = super().__new__(cls, message)
|
|
||||||
obj.message = message
|
|
||||||
obj.exceptions = tuple(exceptions)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __init__(self, message, exceptions):
|
|
||||||
super().__init__(message)
|
|
||||||
self.message = message
|
|
||||||
self.exceptions = tuple(exceptions)
|
|
||||||
|
|
||||||
def derive(self, exceptions):
|
|
||||||
return self.__class__(self.message, exceptions)
|
|
||||||
@@ -1 +1 @@
|
|||||||
uv
|
pip
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
importlib_metadata-6.8.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
importlib_metadata-6.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
importlib_metadata-6.8.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
importlib_metadata-6.8.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
||||||
importlib_metadata-6.8.0.dist-info/METADATA,sha256=X79qGRh7gqvuaL_utK5X-MnwHJuIWke0e3eAx0IiLhc,5067
|
importlib_metadata-6.8.0.dist-info/METADATA,sha256=X79qGRh7gqvuaL_utK5X-MnwHJuIWke0e3eAx0IiLhc,5067
|
||||||
importlib_metadata-6.8.0.dist-info/RECORD,,
|
importlib_metadata-6.8.0.dist-info/RECORD,,
|
||||||
@@ -6,6 +6,15 @@ importlib_metadata-6.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
|||||||
importlib_metadata-6.8.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
importlib_metadata-6.8.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
||||||
importlib_metadata-6.8.0.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
|
importlib_metadata-6.8.0.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
|
||||||
importlib_metadata/__init__.py,sha256=EiH0qTKP_6oa6pRGJgPrq0kvjnL3hJ18BJH8VaAYSBA,30749
|
importlib_metadata/__init__.py,sha256=EiH0qTKP_6oa6pRGJgPrq0kvjnL3hJ18BJH8VaAYSBA,30749
|
||||||
|
importlib_metadata/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_adapters.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_collections.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_compat.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_functools.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_itertools.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_meta.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_py39compat.cpython-311.pyc,,
|
||||||
|
importlib_metadata/__pycache__/_text.cpython-311.pyc,,
|
||||||
importlib_metadata/_adapters.py,sha256=i8S6Ib1OQjcILA-l4gkzktMZe18TaeUNI49PLRp6OBU,2454
|
importlib_metadata/_adapters.py,sha256=i8S6Ib1OQjcILA-l4gkzktMZe18TaeUNI49PLRp6OBU,2454
|
||||||
importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
|
importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
|
||||||
importlib_metadata/_compat.py,sha256=zhjcWMfA9SNExFVVVBozOYbuiok0A4tdMsNk9ZDZi-A,1554
|
importlib_metadata/_compat.py,sha256=zhjcWMfA9SNExFVVVBozOYbuiok0A4tdMsNk9ZDZi-A,1554
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
uv
|
pip
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
lsprotocol-2023.0.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
lsprotocol-2023.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
lsprotocol-2023.0.1.dist-info/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
|
lsprotocol-2023.0.1.dist-info/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
|
||||||
lsprotocol-2023.0.1.dist-info/METADATA,sha256=oh7M_V0nCX-lx8MCik5z0_J8Wyd7ApJtdl30wWs4Tb8,2237
|
lsprotocol-2023.0.1.dist-info/METADATA,sha256=oh7M_V0nCX-lx8MCik5z0_J8Wyd7ApJtdl30wWs4Tb8,2237
|
||||||
lsprotocol-2023.0.1.dist-info/RECORD,,
|
lsprotocol-2023.0.1.dist-info/RECORD,,
|
||||||
lsprotocol-2023.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
lsprotocol-2023.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
lsprotocol-2023.0.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
lsprotocol-2023.0.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
||||||
lsprotocol/__init__.py,sha256=zoT6Do2JtGHGb7pOeKpahg4ocXIsSpyowjhOrhUhx8g,94
|
lsprotocol/__init__.py,sha256=zoT6Do2JtGHGb7pOeKpahg4ocXIsSpyowjhOrhUhx8g,94
|
||||||
|
lsprotocol/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
lsprotocol/__pycache__/_hooks.cpython-311.pyc,,
|
||||||
|
lsprotocol/__pycache__/converters.cpython-311.pyc,,
|
||||||
|
lsprotocol/__pycache__/types.cpython-311.pyc,,
|
||||||
|
lsprotocol/__pycache__/validators.cpython-311.pyc,,
|
||||||
lsprotocol/_hooks.py,sha256=PCTq4Ve_dDd02DMcWQ8afu9gj_oyX0B4nDOomPghqYs,41570
|
lsprotocol/_hooks.py,sha256=PCTq4Ve_dDd02DMcWQ8afu9gj_oyX0B4nDOomPghqYs,41570
|
||||||
lsprotocol/converters.py,sha256=404tQOVoZL31R9CBrDe6Gx9Nok5cRph3glKlnSx00fo,433
|
lsprotocol/converters.py,sha256=404tQOVoZL31R9CBrDe6Gx9Nok5cRph3glKlnSx00fo,433
|
||||||
lsprotocol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
lsprotocol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
+6
-13
@@ -1,13 +1,14 @@
|
|||||||
Metadata-Version: 2.4
|
Metadata-Version: 2.4
|
||||||
Name: packaging
|
Name: packaging
|
||||||
Version: 26.2
|
Version: 25.0
|
||||||
Summary: Core utilities for Python packages
|
Summary: Core utilities for Python packages
|
||||||
Author-email: Donald Stufft <donald@stufft.io>
|
Author-email: Donald Stufft <donald@stufft.io>
|
||||||
Requires-Python: >=3.8
|
Requires-Python: >=3.8
|
||||||
Description-Content-Type: text/x-rst
|
Description-Content-Type: text/x-rst
|
||||||
License-Expression: Apache-2.0 OR BSD-2-Clause
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
Classifier: Intended Audience :: Developers
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: Apache Software License
|
||||||
|
Classifier: License :: OSI Approved :: BSD License
|
||||||
Classifier: Programming Language :: Python
|
Classifier: Programming Language :: Python
|
||||||
Classifier: Programming Language :: Python :: 3
|
Classifier: Programming Language :: Python :: 3
|
||||||
Classifier: Programming Language :: Python :: 3 :: Only
|
Classifier: Programming Language :: Python :: 3 :: Only
|
||||||
@@ -17,10 +18,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|||||||
Classifier: Programming Language :: Python :: 3.11
|
Classifier: Programming Language :: Python :: 3.11
|
||||||
Classifier: Programming Language :: Python :: 3.12
|
Classifier: Programming Language :: Python :: 3.12
|
||||||
Classifier: Programming Language :: Python :: 3.13
|
Classifier: Programming Language :: Python :: 3.13
|
||||||
Classifier: Programming Language :: Python :: 3.14
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
Classifier: Programming Language :: Python :: Free Threading :: 4 - Resilient
|
|
||||||
Classifier: Typing :: Typed
|
Classifier: Typing :: Typed
|
||||||
License-File: LICENSE
|
License-File: LICENSE
|
||||||
License-File: LICENSE.APACHE
|
License-File: LICENSE.APACHE
|
||||||
@@ -43,7 +42,7 @@ or benefit greatly from having a single shared implementation (eg: :pep:`425`).
|
|||||||
.. end-intro
|
.. end-intro
|
||||||
|
|
||||||
The ``packaging`` project includes the following: version handling, specifiers,
|
The ``packaging`` project includes the following: version handling, specifiers,
|
||||||
markers, requirements, tags, metadata, lockfiles, utilities.
|
markers, requirements, tags, utilities.
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
@@ -53,14 +52,8 @@ The `documentation`_ provides information and the API for the following:
|
|||||||
- Version Handling
|
- Version Handling
|
||||||
- Specifiers
|
- Specifiers
|
||||||
- Markers
|
- Markers
|
||||||
- Licenses
|
|
||||||
- Requirements
|
- Requirements
|
||||||
- Metadata
|
|
||||||
- Tags
|
- Tags
|
||||||
- Lockfiles (pylock)
|
|
||||||
- Direct URL helpers
|
|
||||||
- Dependency groups
|
|
||||||
- Errors
|
|
||||||
- Utilities
|
- Utilities
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
@@ -77,11 +70,11 @@ Discussion
|
|||||||
|
|
||||||
If you run into bugs, you can file them in our `issue tracker`_.
|
If you run into bugs, you can file them in our `issue tracker`_.
|
||||||
|
|
||||||
You can also join discussions on `GitHub Discussions`_ to ask questions or get involved.
|
You can also join ``#pypa`` on Freenode to ask questions or get involved.
|
||||||
|
|
||||||
|
|
||||||
.. _`documentation`: https://packaging.pypa.io/
|
.. _`documentation`: https://packaging.pypa.io/
|
||||||
.. _`issue tracker`: https://github.com/pypa/packaging/issues
|
.. _`issue tracker`: https://github.com/pypa/packaging/issues
|
||||||
.. _`GitHub Discussions`: https://github.com/pypa/packaging/discussions
|
|
||||||
|
|
||||||
|
|
||||||
Code of Conduct
|
Code of Conduct
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
packaging-25.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
packaging-25.0.dist-info/METADATA,sha256=W2EaYJw4_vw9YWv0XSCuyY-31T8kXayp4sMPyFx6woI,3281
|
||||||
|
packaging-25.0.dist-info/RECORD,,
|
||||||
|
packaging-25.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
packaging-25.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||||
|
packaging-25.0.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
||||||
|
packaging-25.0.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
||||||
|
packaging-25.0.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
||||||
|
packaging/__init__.py,sha256=_0cDiPVf2S-bNfVmZguxxzmrIYWlyASxpqph4qsJWUc,494
|
||||||
|
packaging/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/_elffile.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/_manylinux.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/_musllinux.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/_parser.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/_structures.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/_tokenizer.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/markers.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/metadata.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/requirements.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/specifiers.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/tags.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/utils.cpython-311.pyc,,
|
||||||
|
packaging/__pycache__/version.cpython-311.pyc,,
|
||||||
|
packaging/_elffile.py,sha256=UkrbDtW7aeq3qqoAfU16ojyHZ1xsTvGke_WqMTKAKd0,3286
|
||||||
|
packaging/_manylinux.py,sha256=t4y_-dTOcfr36gLY-ztiOpxxJFGO2ikC11HgfysGxiM,9596
|
||||||
|
packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
|
||||||
|
packaging/_parser.py,sha256=gYfnj0pRHflVc4RHZit13KNTyN9iiVcU2RUCGi22BwM,10221
|
||||||
|
packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
|
||||||
|
packaging/_tokenizer.py,sha256=OYzt7qKxylOAJ-q0XyK1qAycyPRYLfMPdGQKRXkZWyI,5310
|
||||||
|
packaging/licenses/__init__.py,sha256=VsK4o27CJXWfTi8r2ybJmsBoCdhpnBWuNrskaCVKP7U,5715
|
||||||
|
packaging/licenses/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
packaging/licenses/__pycache__/_spdx.cpython-311.pyc,,
|
||||||
|
packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398
|
||||||
|
packaging/markers.py,sha256=P0we27jm1xUzgGMJxBjtUFCIWeBxTsMeJTOJ6chZmAY,12049
|
||||||
|
packaging/metadata.py,sha256=8IZErqQQnNm53dZZuYq4FGU4_dpyinMeH1QFBIWIkfE,34739
|
||||||
|
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
|
||||||
|
packaging/specifiers.py,sha256=gtPu5DTc-F9baLq3FTGEK6dPhHGCuwwZetaY0PSV2gs,40055
|
||||||
|
packaging/tags.py,sha256=41s97W9Zatrq2Ed7Rc3qeBDaHe8pKKvYq2mGjwahfXk,22745
|
||||||
|
packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050
|
||||||
|
packaging/version.py,sha256=olfyuk_DPbflNkJ4wBWetXQ17c74x3DB501degUv7DY,16676
|
||||||
@@ -1 +0,0 @@
|
|||||||
uv
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
packaging-26.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
|
||||||
packaging-26.2.dist-info/METADATA,sha256=T5y815M0FaR5P3dnyYoralEsgj_IHIczeBVwXyMOyr8,3543
|
|
||||||
packaging-26.2.dist-info/RECORD,,
|
|
||||||
packaging-26.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
packaging-26.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
||||||
packaging-26.2.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
|
||||||
packaging-26.2.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
|
||||||
packaging-26.2.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
|
||||||
packaging/__init__.py,sha256=QhMEdPu2XogrJzV3S0KWS6t7l0I9k8EeDRJl4fnw87s,494
|
|
||||||
packaging/_elffile.py,sha256=-sKkptYqzYw2-x3QByJa5mB4rfPWu1pxkZHRx1WAFCY,3211
|
|
||||||
packaging/_manylinux.py,sha256=Hf6nB0cOrayEs96-p3oIXAgGnFquv20DO5l-o2_Xnv0,9559
|
|
||||||
packaging/_musllinux.py,sha256=Z6swjH3MA7XS3qXnmMN7QPhqP3fnoYI0eQ18e9-HgAE,2707
|
|
||||||
packaging/_parser.py,sha256=Kf2nsDw4c54X82pY8ba4F02Bve6OygGMAjL-Begqcew,11698
|
|
||||||
packaging/_structures.py,sha256=60jRbF78p8z5MKnNd6cAprgOadCJHV0DlmUmRBqFZcs,1109
|
|
||||||
packaging/_tokenizer.py,sha256=tFU2Wr-ZZJdAbkXLEJo7qUQDJaIkfft9DqaifiEND7A,5391
|
|
||||||
packaging/dependency_groups.py,sha256=XZIAVFK9uHG4RCGprmJn3VInUWMesxha_kytJuMO9eY,10218
|
|
||||||
packaging/direct_url.py,sha256=eKmbDiPP1sLV4Mj_kCSZqqknrIyVO9Sr7JpF8KCjp4U,10917
|
|
||||||
packaging/errors.py,sha256=6hfEYXAf8v_IF65-lFadJOMIieBP2xIKtyEXjG1nGIs,2680
|
|
||||||
packaging/licenses/__init__.py,sha256=_Jx0XRiD_58palsWnyLrLuh59ZpGCPIPXLKdZo9OJvQ,7293
|
|
||||||
packaging/licenses/_spdx.py,sha256=WW7DXiyg68up_YND_wpRYlr1SHhiV4FfJLQffghhMxQ,51122
|
|
||||||
packaging/markers.py,sha256=8fDIUhAF6YMnCNB5FSiwh9pEIusiFzAF73J-0OB8bTk,17055
|
|
||||||
packaging/metadata.py,sha256=crAh0E3GVGVqPlu6EdRFsaG-Y6UYznTUqjuGKRGPv6c,38770
|
|
||||||
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
packaging/pylock.py,sha256=G_1gncTmDbRLY1jo4VDI9Uw-b5IErh_Q9V_BbVJTmD8,33890
|
|
||||||
packaging/requirements.py,sha256=dd1c9aa1gp5NI6btF6UFRQjPn1nxQXnE_T34yDDTEpc,4383
|
|
||||||
packaging/specifiers.py,sha256=Mfp8avQg0lVot17to9lVKBtZD1FsWBTItoGwFUZ3wtg,71514
|
|
||||||
packaging/tags.py,sha256=NQ1weo69_Sjte3xBZ1I_G63CIgCmaN0C24mz-z3hGYo,34224
|
|
||||||
packaging/utils.py,sha256=M7-JMKic2sP1YtV_8aW7eVGB-x3ADuKCiSrsVeCd2Uo,9848
|
|
||||||
packaging/version.py,sha256=Y1aTtxe3sn2xOMa5BdI85-AcHuybbanOVkEvvSRRC8I,38369
|
|
||||||
@@ -6,7 +6,7 @@ __title__ = "packaging"
|
|||||||
__summary__ = "Core utilities for Python packages"
|
__summary__ = "Core utilities for Python packages"
|
||||||
__uri__ = "https://github.com/pypa/packaging"
|
__uri__ = "https://github.com/pypa/packaging"
|
||||||
|
|
||||||
__version__ = "26.2"
|
__version__ = "25.0"
|
||||||
|
|
||||||
__author__ = "Donald Stufft and individual contributors"
|
__author__ = "Donald Stufft and individual contributors"
|
||||||
__email__ = "donald@stufft.io"
|
__email__ = "donald@stufft.io"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ ELF file parser.
|
|||||||
This provides a class ``ELFFile`` that parses an ELF executable in a similar
|
This provides a class ``ELFFile`` that parses an ELF executable in a similar
|
||||||
interface to ``ZipFile``. Only the read interface is implemented.
|
interface to ``ZipFile``. Only the read interface is implemented.
|
||||||
|
|
||||||
|
Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca
|
||||||
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,6 @@ EF_ARM_ABIMASK = 0xFF000000
|
|||||||
EF_ARM_ABI_VER5 = 0x05000000
|
EF_ARM_ABI_VER5 = 0x05000000
|
||||||
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
EF_ARM_ABI_FLOAT_HARD = 0x00000400
|
||||||
|
|
||||||
_ALLOWED_ARCHS = {
|
|
||||||
"x86_64",
|
|
||||||
"aarch64",
|
|
||||||
"ppc64",
|
|
||||||
"ppc64le",
|
|
||||||
"s390x",
|
|
||||||
"loongarch64",
|
|
||||||
"riscv64",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
|
||||||
# as the type for `path` until then.
|
# as the type for `path` until then.
|
||||||
@@ -67,7 +57,16 @@ def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
|
|||||||
return _is_linux_armhf(executable)
|
return _is_linux_armhf(executable)
|
||||||
if "i686" in archs:
|
if "i686" in archs:
|
||||||
return _is_linux_i686(executable)
|
return _is_linux_i686(executable)
|
||||||
return any(arch in _ALLOWED_ARCHS for arch in archs)
|
allowed_archs = {
|
||||||
|
"x86_64",
|
||||||
|
"aarch64",
|
||||||
|
"ppc64",
|
||||||
|
"ppc64le",
|
||||||
|
"s390x",
|
||||||
|
"loongarch64",
|
||||||
|
"riscv64",
|
||||||
|
}
|
||||||
|
return any(arch in allowed_archs for arch in archs)
|
||||||
|
|
||||||
|
|
||||||
# If glibc ever changes its major version, we need to know what the last
|
# If glibc ever changes its major version, we need to know what the last
|
||||||
@@ -107,7 +106,7 @@ def _glibc_version_string_ctypes() -> str | None:
|
|||||||
Fallback implementation of glibc_version_string using ctypes.
|
Fallback implementation of glibc_version_string using ctypes.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import ctypes # noqa: PLC0415
|
import ctypes
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -151,7 +150,7 @@ def _glibc_version_string() -> str | None:
|
|||||||
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
|
return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
|
||||||
|
|
||||||
|
|
||||||
def _parse_glibc_version(version_str: str) -> _GLibCVersion:
|
def _parse_glibc_version(version_str: str) -> tuple[int, int]:
|
||||||
"""Parse glibc version.
|
"""Parse glibc version.
|
||||||
|
|
||||||
We use a regexp instead of str.split because we want to discard any
|
We use a regexp instead of str.split because we want to discard any
|
||||||
@@ -166,15 +165,15 @@ def _parse_glibc_version(version_str: str) -> _GLibCVersion:
|
|||||||
RuntimeWarning,
|
RuntimeWarning,
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
return _GLibCVersion(-1, -1)
|
return -1, -1
|
||||||
return _GLibCVersion(int(m.group("major")), int(m.group("minor")))
|
return int(m.group("major")), int(m.group("minor"))
|
||||||
|
|
||||||
|
|
||||||
@functools.lru_cache
|
@functools.lru_cache
|
||||||
def _get_glibc_version() -> _GLibCVersion:
|
def _get_glibc_version() -> tuple[int, int]:
|
||||||
version_str = _glibc_version_string()
|
version_str = _glibc_version_string()
|
||||||
if version_str is None:
|
if version_str is None:
|
||||||
return _GLibCVersion(-1, -1)
|
return (-1, -1)
|
||||||
return _parse_glibc_version(version_str)
|
return _parse_glibc_version(version_str)
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +184,7 @@ def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
|
|||||||
return False
|
return False
|
||||||
# Check for presence of _manylinux module.
|
# Check for presence of _manylinux module.
|
||||||
try:
|
try:
|
||||||
import _manylinux # noqa: PLC0415
|
import _manylinux
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return True
|
return True
|
||||||
if hasattr(_manylinux, "manylinux_compatible"):
|
if hasattr(_manylinux, "manylinux_compatible"):
|
||||||
@@ -193,26 +192,25 @@ def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
return bool(result)
|
return bool(result)
|
||||||
return True
|
return True
|
||||||
if version == _GLibCVersion(2, 5) and hasattr(_manylinux, "manylinux1_compatible"):
|
if version == _GLibCVersion(2, 5):
|
||||||
return bool(_manylinux.manylinux1_compatible)
|
if hasattr(_manylinux, "manylinux1_compatible"):
|
||||||
if version == _GLibCVersion(2, 12) and hasattr(
|
return bool(_manylinux.manylinux1_compatible)
|
||||||
_manylinux, "manylinux2010_compatible"
|
if version == _GLibCVersion(2, 12):
|
||||||
):
|
if hasattr(_manylinux, "manylinux2010_compatible"):
|
||||||
return bool(_manylinux.manylinux2010_compatible)
|
return bool(_manylinux.manylinux2010_compatible)
|
||||||
if version == _GLibCVersion(2, 17) and hasattr(
|
if version == _GLibCVersion(2, 17):
|
||||||
_manylinux, "manylinux2014_compatible"
|
if hasattr(_manylinux, "manylinux2014_compatible"):
|
||||||
):
|
return bool(_manylinux.manylinux2014_compatible)
|
||||||
return bool(_manylinux.manylinux2014_compatible)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
_LEGACY_MANYLINUX_MAP: dict[_GLibCVersion, str] = {
|
_LEGACY_MANYLINUX_MAP = {
|
||||||
# CentOS 7 w/ glibc 2.17 (PEP 599)
|
# CentOS 7 w/ glibc 2.17 (PEP 599)
|
||||||
_GLibCVersion(2, 17): "manylinux2014",
|
(2, 17): "manylinux2014",
|
||||||
# CentOS 6 w/ glibc 2.12 (PEP 571)
|
# CentOS 6 w/ glibc 2.12 (PEP 571)
|
||||||
_GLibCVersion(2, 12): "manylinux2010",
|
(2, 12): "manylinux2010",
|
||||||
# CentOS 5 w/ glibc 2.5 (PEP 513)
|
# CentOS 5 w/ glibc 2.5 (PEP 513)
|
||||||
_GLibCVersion(2, 5): "manylinux1",
|
(2, 5): "manylinux1",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -254,9 +252,11 @@ def platform_tags(archs: Sequence[str]) -> Iterator[str]:
|
|||||||
min_minor = -1
|
min_minor = -1
|
||||||
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
for glibc_minor in range(glibc_max.minor, min_minor, -1):
|
||||||
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
|
||||||
|
tag = "manylinux_{}_{}".format(*glibc_version)
|
||||||
if _is_compatible(arch, glibc_version):
|
if _is_compatible(arch, glibc_version):
|
||||||
yield "manylinux_{}_{}_{}".format(*glibc_version, arch)
|
yield f"{tag}_{arch}"
|
||||||
|
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
||||||
# Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
|
if glibc_version in _LEGACY_MANYLINUX_MAP:
|
||||||
if legacy_tag := _LEGACY_MANYLINUX_MAP.get(glibc_version):
|
legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version]
|
||||||
|
if _is_compatible(arch, glibc_version):
|
||||||
yield f"{legacy_tag}_{arch}"
|
yield f"{legacy_tag}_{arch}"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ def _get_musl_version(executable: str) -> _MuslVersion | None:
|
|||||||
return None
|
return None
|
||||||
if ld is None or "musl" not in ld:
|
if ld is None or "musl" not in ld:
|
||||||
return None
|
return None
|
||||||
proc = subprocess.run([ld], check=False, stderr=subprocess.PIPE, text=True)
|
proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True)
|
||||||
return _parse_musl_version(proc.stderr)
|
return _parse_musl_version(proc.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ the implementation.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
from typing import List, Literal, NamedTuple, Sequence, Tuple, Union
|
from typing import NamedTuple, Sequence, Tuple, Union
|
||||||
|
|
||||||
from ._tokenizer import DEFAULT_RULES, Tokenizer
|
from ._tokenizer import DEFAULT_RULES, Tokenizer
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
__slots__ = ("value",)
|
|
||||||
|
|
||||||
def __init__(self, value: str) -> None:
|
def __init__(self, value: str) -> None:
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
@@ -22,66 +20,31 @@ class Node:
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.__class__.__name__}({self.value!r})>"
|
return f"<{self.__class__.__name__}('{self}')>"
|
||||||
|
|
||||||
def serialize(self) -> str:
|
def serialize(self) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __getstate__(self) -> str:
|
|
||||||
# Return just the value string for compactness and stability.
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def _restore_value(self, value: object) -> None:
|
|
||||||
if not isinstance(value, str):
|
|
||||||
raise TypeError(
|
|
||||||
f"Cannot restore {self.__class__.__name__} value from {value!r}"
|
|
||||||
)
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __setstate__(self, state: object) -> None:
|
|
||||||
if isinstance(state, str):
|
|
||||||
# New format (26.2+): just the value string.
|
|
||||||
self._restore_value(state)
|
|
||||||
return
|
|
||||||
if isinstance(state, tuple) and len(state) == 2:
|
|
||||||
# Old format (packaging <= 26.0, __slots__): (None, {slot: value}).
|
|
||||||
_, slot_dict = state
|
|
||||||
if isinstance(slot_dict, dict) and "value" in slot_dict:
|
|
||||||
self._restore_value(slot_dict["value"])
|
|
||||||
return
|
|
||||||
if isinstance(state, dict) and "value" in state:
|
|
||||||
# Old format (packaging <= 25.0, no __slots__): plain __dict__.
|
|
||||||
self._restore_value(state["value"])
|
|
||||||
return
|
|
||||||
raise TypeError(f"Cannot restore {self.__class__.__name__} from {state!r}")
|
|
||||||
|
|
||||||
|
|
||||||
class Variable(Node):
|
class Variable(Node):
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def serialize(self) -> str:
|
def serialize(self) -> str:
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
class Value(Node):
|
class Value(Node):
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def serialize(self) -> str:
|
def serialize(self) -> str:
|
||||||
return f'"{self}"'
|
return f'"{self}"'
|
||||||
|
|
||||||
|
|
||||||
class Op(Node):
|
class Op(Node):
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def serialize(self) -> str:
|
def serialize(self) -> str:
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
MarkerLogical = Literal["and", "or"]
|
|
||||||
MarkerVar = Union[Variable, Value]
|
MarkerVar = Union[Variable, Value]
|
||||||
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
|
MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
|
||||||
MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
|
MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
|
||||||
MarkerList = List[Union["MarkerList", MarkerAtom, MarkerLogical]]
|
MarkerList = Sequence[Union["MarkerList", MarkerAtom, str]]
|
||||||
|
|
||||||
|
|
||||||
class ParsedRequirement(NamedTuple):
|
class ParsedRequirement(NamedTuple):
|
||||||
@@ -148,9 +111,7 @@ def _parse_requirement_details(
|
|||||||
return (url, specifier, marker)
|
return (url, specifier, marker)
|
||||||
|
|
||||||
marker = _parse_requirement_marker(
|
marker = _parse_requirement_marker(
|
||||||
tokenizer,
|
tokenizer, span_start=url_start, after="URL and whitespace"
|
||||||
span_start=url_start,
|
|
||||||
expected="semicolon (after URL and whitespace)",
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
specifier_start = tokenizer.position
|
specifier_start = tokenizer.position
|
||||||
@@ -163,10 +124,10 @@ def _parse_requirement_details(
|
|||||||
marker = _parse_requirement_marker(
|
marker = _parse_requirement_marker(
|
||||||
tokenizer,
|
tokenizer,
|
||||||
span_start=specifier_start,
|
span_start=specifier_start,
|
||||||
expected=(
|
after=(
|
||||||
"comma (within version specifier), semicolon (after version specifier)"
|
"version specifier"
|
||||||
if specifier
|
if specifier
|
||||||
else "semicolon (after name with no version specifier)"
|
else "name and no valid version specifier"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,7 +135,7 @@ def _parse_requirement_details(
|
|||||||
|
|
||||||
|
|
||||||
def _parse_requirement_marker(
|
def _parse_requirement_marker(
|
||||||
tokenizer: Tokenizer, *, span_start: int, expected: str
|
tokenizer: Tokenizer, *, span_start: int, after: str
|
||||||
) -> MarkerList:
|
) -> MarkerList:
|
||||||
"""
|
"""
|
||||||
requirement_marker = SEMICOLON marker WS?
|
requirement_marker = SEMICOLON marker WS?
|
||||||
@@ -182,9 +143,8 @@ def _parse_requirement_marker(
|
|||||||
|
|
||||||
if not tokenizer.check("SEMICOLON"):
|
if not tokenizer.check("SEMICOLON"):
|
||||||
tokenizer.raise_syntax_error(
|
tokenizer.raise_syntax_error(
|
||||||
f"Expected {expected} or end",
|
f"Expected end or semicolon (after {after})",
|
||||||
span_start=span_start,
|
span_start=span_start,
|
||||||
span_end=None,
|
|
||||||
)
|
)
|
||||||
tokenizer.read()
|
tokenizer.read()
|
||||||
|
|
||||||
@@ -347,7 +307,7 @@ def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
|
|||||||
return (marker_var_left, marker_op, marker_var_right)
|
return (marker_var_left, marker_op, marker_var_right)
|
||||||
|
|
||||||
|
|
||||||
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: # noqa: RET503
|
def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:
|
||||||
"""
|
"""
|
||||||
marker_var = VARIABLE | QUOTED_STRING
|
marker_var = VARIABLE | QUOTED_STRING
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,32 +2,60 @@
|
|||||||
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
|
||||||
# for complete details.
|
# for complete details.
|
||||||
|
|
||||||
"""Backward-compatibility shim for unpickling Version objects serialized before
|
|
||||||
packaging 26.1.
|
|
||||||
|
|
||||||
Old pickles reference ``packaging._structures.InfinityType`` and
|
|
||||||
``packaging._structures.NegativeInfinityType``. This module provides minimal
|
|
||||||
stand-in classes so that ``pickle.loads()`` can resolve those references.
|
|
||||||
The deserialized objects are not used for comparisons — ``Version.__setstate__``
|
|
||||||
discards the stale ``_key`` cache and recomputes it from the core version fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
class InfinityType:
|
class InfinityType:
|
||||||
"""Stand-in for the removed ``InfinityType`` used in old comparison keys."""
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "Infinity"
|
return "Infinity"
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(repr(self))
|
||||||
|
|
||||||
class NegativeInfinityType:
|
def __lt__(self, other: object) -> bool:
|
||||||
"""Stand-in for the removed ``NegativeInfinityType`` used in old comparison keys."""
|
return False
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __le__(self, other: object) -> bool:
|
||||||
return "-Infinity"
|
return False
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, self.__class__)
|
||||||
|
|
||||||
|
def __gt__(self, other: object) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __ge__(self, other: object) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __neg__(self: object) -> "NegativeInfinityType":
|
||||||
|
return NegativeInfinity
|
||||||
|
|
||||||
|
|
||||||
Infinity = InfinityType()
|
Infinity = InfinityType()
|
||||||
|
|
||||||
|
|
||||||
|
class NegativeInfinityType:
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "-Infinity"
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash(repr(self))
|
||||||
|
|
||||||
|
def __lt__(self, other: object) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __le__(self, other: object) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __eq__(self, other: object) -> bool:
|
||||||
|
return isinstance(other, self.__class__)
|
||||||
|
|
||||||
|
def __gt__(self, other: object) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __ge__(self, other: object) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __neg__(self: object) -> InfinityType:
|
||||||
|
return Infinity
|
||||||
|
|
||||||
|
|
||||||
NegativeInfinity = NegativeInfinityType()
|
NegativeInfinity = NegativeInfinityType()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import contextlib
|
import contextlib
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Generator, Mapping, NoReturn
|
from typing import Iterator, NoReturn
|
||||||
|
|
||||||
from .specifiers import Specifier
|
from .specifiers import Specifier
|
||||||
|
|
||||||
@@ -33,16 +33,16 @@ class ParserSyntaxError(Exception):
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
|
marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
|
||||||
return f"{self.message}\n {self.source}\n {marker}"
|
return "\n ".join([self.message, self.source, marker])
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_RULES: dict[str, re.Pattern[str]] = {
|
DEFAULT_RULES: dict[str, str | re.Pattern[str]] = {
|
||||||
"LEFT_PARENTHESIS": re.compile(r"\("),
|
"LEFT_PARENTHESIS": r"\(",
|
||||||
"RIGHT_PARENTHESIS": re.compile(r"\)"),
|
"RIGHT_PARENTHESIS": r"\)",
|
||||||
"LEFT_BRACKET": re.compile(r"\["),
|
"LEFT_BRACKET": r"\[",
|
||||||
"RIGHT_BRACKET": re.compile(r"\]"),
|
"RIGHT_BRACKET": r"\]",
|
||||||
"SEMICOLON": re.compile(r";"),
|
"SEMICOLON": r";",
|
||||||
"COMMA": re.compile(r","),
|
"COMMA": r",",
|
||||||
"QUOTED_STRING": re.compile(
|
"QUOTED_STRING": re.compile(
|
||||||
r"""
|
r"""
|
||||||
(
|
(
|
||||||
@@ -53,10 +53,10 @@ DEFAULT_RULES: dict[str, re.Pattern[str]] = {
|
|||||||
""",
|
""",
|
||||||
re.VERBOSE,
|
re.VERBOSE,
|
||||||
),
|
),
|
||||||
"OP": re.compile(r"(===|==|~=|!=|<=|>=|<|>)"),
|
"OP": r"(===|==|~=|!=|<=|>=|<|>)",
|
||||||
"BOOLOP": re.compile(r"\b(or|and)\b"),
|
"BOOLOP": r"\b(or|and)\b",
|
||||||
"IN": re.compile(r"\bin\b"),
|
"IN": r"\bin\b",
|
||||||
"NOT": re.compile(r"\bnot\b"),
|
"NOT": r"\bnot\b",
|
||||||
"VARIABLE": re.compile(
|
"VARIABLE": re.compile(
|
||||||
r"""
|
r"""
|
||||||
\b(
|
\b(
|
||||||
@@ -75,16 +75,16 @@ DEFAULT_RULES: dict[str, re.Pattern[str]] = {
|
|||||||
re.VERBOSE,
|
re.VERBOSE,
|
||||||
),
|
),
|
||||||
"SPECIFIER": re.compile(
|
"SPECIFIER": re.compile(
|
||||||
Specifier._specifier_regex_str,
|
Specifier._operator_regex_str + Specifier._version_regex_str,
|
||||||
re.VERBOSE | re.IGNORECASE,
|
re.VERBOSE | re.IGNORECASE,
|
||||||
),
|
),
|
||||||
"AT": re.compile(r"\@"),
|
"AT": r"\@",
|
||||||
"URL": re.compile(r"[^ \t]+"),
|
"URL": r"[^ \t]+",
|
||||||
"IDENTIFIER": re.compile(r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b"),
|
"IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b",
|
||||||
"VERSION_PREFIX_TRAIL": re.compile(r"\.\*"),
|
"VERSION_PREFIX_TRAIL": r"\.\*",
|
||||||
"VERSION_LOCAL_LABEL_TRAIL": re.compile(r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*"),
|
"VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*",
|
||||||
"WS": re.compile(r"[ \t]+"),
|
"WS": r"[ \t]+",
|
||||||
"END": re.compile(r"$"),
|
"END": r"$",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -99,10 +99,12 @@ class Tokenizer:
|
|||||||
self,
|
self,
|
||||||
source: str,
|
source: str,
|
||||||
*,
|
*,
|
||||||
rules: Mapping[str, re.Pattern[str]],
|
rules: dict[str, str | re.Pattern[str]],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.source = source
|
self.source = source
|
||||||
self.rules = rules
|
self.rules: dict[str, re.Pattern[str]] = {
|
||||||
|
name: re.compile(pattern) for name, pattern in rules.items()
|
||||||
|
}
|
||||||
self.next_token: Token | None = None
|
self.next_token: Token | None = None
|
||||||
self.position = 0
|
self.position = 0
|
||||||
|
|
||||||
@@ -172,7 +174,7 @@ class Tokenizer:
|
|||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def enclosing_tokens(
|
def enclosing_tokens(
|
||||||
self, open_token: str, close_token: str, *, around: str
|
self, open_token: str, close_token: str, *, around: str
|
||||||
) -> Generator[None, None, None]:
|
) -> Iterator[None]:
|
||||||
if self.check(open_token):
|
if self.check(open_token):
|
||||||
open_position = self.position
|
open_position = self.position
|
||||||
self.read()
|
self.read()
|
||||||
|
|||||||
@@ -1,302 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import re
|
|
||||||
from collections.abc import Mapping, Sequence
|
|
||||||
|
|
||||||
from .errors import _ErrorCollector
|
|
||||||
from .requirements import Requirement
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"CyclicDependencyGroup",
|
|
||||||
"DependencyGroupInclude",
|
|
||||||
"DependencyGroupResolver",
|
|
||||||
"DuplicateGroupNames",
|
|
||||||
"InvalidDependencyGroupObject",
|
|
||||||
"resolve_dependency_groups",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
# -----------
|
|
||||||
# Error Types
|
|
||||||
# -----------
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateGroupNames(ValueError):
|
|
||||||
"""
|
|
||||||
The same dependency groups were defined twice, with different non-normalized names.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class CyclicDependencyGroup(ValueError):
|
|
||||||
"""
|
|
||||||
The dependency group includes form a cycle.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, requested_group: str, group: str, include_group: str) -> None:
|
|
||||||
self.requested_group = requested_group
|
|
||||||
self.group = group
|
|
||||||
self.include_group = include_group
|
|
||||||
|
|
||||||
if include_group == group:
|
|
||||||
reason = f"{group} includes itself"
|
|
||||||
else:
|
|
||||||
reason = f"{include_group} -> {group}, {group} -> {include_group}"
|
|
||||||
super().__init__(
|
|
||||||
"Cyclic dependency group include while resolving "
|
|
||||||
f"{requested_group}: {reason}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# in the PEP 735 spec, the tables in dependency group lists were described as
|
|
||||||
# "Dependency Object Specifiers", but the only defined type of object was a
|
|
||||||
# "Dependency Group Include" -- hence the naming of this error as "Object"
|
|
||||||
class InvalidDependencyGroupObject(ValueError):
|
|
||||||
"""
|
|
||||||
A member of a dependency group was identified as a dict, but was not in a valid
|
|
||||||
format.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------
|
|
||||||
# Object Model & Interface
|
|
||||||
# ------------------------
|
|
||||||
|
|
||||||
|
|
||||||
class DependencyGroupInclude:
|
|
||||||
__slots__ = ("include_group",)
|
|
||||||
|
|
||||||
def __init__(self, include_group: str) -> None:
|
|
||||||
"""
|
|
||||||
Initialize a DependencyGroupInclude.
|
|
||||||
|
|
||||||
:param include_group: The name of the group referred to by this include.
|
|
||||||
"""
|
|
||||||
self.include_group = include_group
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"{self.__class__.__name__}({self.include_group!r})"
|
|
||||||
|
|
||||||
|
|
||||||
class DependencyGroupResolver:
|
|
||||||
"""
|
|
||||||
A resolver for Dependency Group data.
|
|
||||||
|
|
||||||
This class handles caching, name normalization, cycle detection, and other
|
|
||||||
parsing requirements. There are only two public methods for exploring the data:
|
|
||||||
``lookup()`` and ``resolve()``.
|
|
||||||
|
|
||||||
:param dependency_groups: A mapping, as provided via pyproject
|
|
||||||
``[dependency-groups]``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
|
|
||||||
) -> None:
|
|
||||||
errors = _ErrorCollector()
|
|
||||||
|
|
||||||
self.dependency_groups = _normalize_group_names(dependency_groups, errors)
|
|
||||||
|
|
||||||
# a map of group names to parsed data
|
|
||||||
self._parsed_groups: dict[
|
|
||||||
str, tuple[Requirement | DependencyGroupInclude, ...]
|
|
||||||
] = {}
|
|
||||||
# a map of group names to their ancestors, used for cycle detection
|
|
||||||
self._include_graph_ancestors: dict[str, tuple[str, ...]] = {}
|
|
||||||
# a cache of completed resolutions to Requirement lists
|
|
||||||
self._resolve_cache: dict[str, tuple[Requirement, ...]] = {}
|
|
||||||
|
|
||||||
errors.finalize("[dependency-groups] data was invalid")
|
|
||||||
|
|
||||||
def lookup(self, group: str) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
|
||||||
"""
|
|
||||||
Lookup a group name, returning the parsed dependency data for that group.
|
|
||||||
This will not resolve includes.
|
|
||||||
|
|
||||||
:param group: the name of the group to lookup
|
|
||||||
"""
|
|
||||||
group = _normalize_name(group)
|
|
||||||
|
|
||||||
with _ErrorCollector().on_exit(
|
|
||||||
f"[dependency-groups] data for {group!r} was malformed"
|
|
||||||
) as errors:
|
|
||||||
return self._parse_group(group, errors)
|
|
||||||
|
|
||||||
def resolve(self, group: str) -> tuple[Requirement, ...]:
|
|
||||||
"""
|
|
||||||
Resolve a dependency group to a list of requirements.
|
|
||||||
|
|
||||||
:param group: the name of the group to resolve
|
|
||||||
"""
|
|
||||||
group = _normalize_name(group)
|
|
||||||
|
|
||||||
with _ErrorCollector().on_exit(
|
|
||||||
f"[dependency-groups] data for {group!r} was malformed"
|
|
||||||
) as errors:
|
|
||||||
return self._resolve(group, group, errors)
|
|
||||||
|
|
||||||
def _resolve(
|
|
||||||
self, group: str, requested_group: str, errors: _ErrorCollector
|
|
||||||
) -> tuple[Requirement, ...]:
|
|
||||||
"""
|
|
||||||
This is a helper for cached resolution to strings. It preserves the name of the
|
|
||||||
group which the user initially requested in order to present a clearer error in
|
|
||||||
the event that a cycle is detected.
|
|
||||||
|
|
||||||
:param group: The normalized name of the group to resolve.
|
|
||||||
:param requested_group: The group which was used in the original, user-facing
|
|
||||||
request.
|
|
||||||
"""
|
|
||||||
if group in self._resolve_cache:
|
|
||||||
return self._resolve_cache[group]
|
|
||||||
|
|
||||||
parsed = self._parse_group(group, errors)
|
|
||||||
|
|
||||||
resolved_group = []
|
|
||||||
|
|
||||||
for item in parsed:
|
|
||||||
if isinstance(item, Requirement):
|
|
||||||
resolved_group.append(item)
|
|
||||||
elif isinstance(item, DependencyGroupInclude):
|
|
||||||
include_group = _normalize_name(item.include_group)
|
|
||||||
|
|
||||||
# if a group is cyclic, record the error
|
|
||||||
# otherwise, follow the include_group reference
|
|
||||||
#
|
|
||||||
# this allows us to examine all includes in a group, even in the
|
|
||||||
# presence of errors
|
|
||||||
if include_group in self._include_graph_ancestors.get(group, ()):
|
|
||||||
errors.error(
|
|
||||||
CyclicDependencyGroup(
|
|
||||||
requested_group, group, item.include_group
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._include_graph_ancestors[include_group] = (
|
|
||||||
*self._include_graph_ancestors.get(group, ()),
|
|
||||||
group,
|
|
||||||
)
|
|
||||||
resolved_group.extend(
|
|
||||||
self._resolve(include_group, requested_group, errors)
|
|
||||||
)
|
|
||||||
else: # pragma: no cover
|
|
||||||
raise NotImplementedError(
|
|
||||||
f"Invalid dependency group item after parse: {item}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# in the event that errors were detected, present the group as empty and do not
|
|
||||||
# cache the result
|
|
||||||
# this ensures that repeated access to a cyclic group will raise multiple errors
|
|
||||||
if errors.errors:
|
|
||||||
return ()
|
|
||||||
|
|
||||||
self._resolve_cache[group] = tuple(resolved_group)
|
|
||||||
return self._resolve_cache[group]
|
|
||||||
|
|
||||||
def _parse_group(
|
|
||||||
self, group: str, errors: _ErrorCollector
|
|
||||||
) -> tuple[Requirement | DependencyGroupInclude, ...]:
|
|
||||||
# short circuit -- never do the work twice
|
|
||||||
if group in self._parsed_groups:
|
|
||||||
return self._parsed_groups[group]
|
|
||||||
|
|
||||||
if group not in self.dependency_groups:
|
|
||||||
errors.error(LookupError(f"Dependency group '{group}' not found"))
|
|
||||||
return ()
|
|
||||||
|
|
||||||
raw_group = self.dependency_groups[group]
|
|
||||||
if isinstance(raw_group, str):
|
|
||||||
errors.error(
|
|
||||||
TypeError(
|
|
||||||
f"Dependency group {group!r} contained a string rather than a list."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return ()
|
|
||||||
|
|
||||||
if not isinstance(raw_group, Sequence):
|
|
||||||
errors.error(
|
|
||||||
TypeError(f"Dependency group {group!r} is not a sequence type.")
|
|
||||||
)
|
|
||||||
return ()
|
|
||||||
|
|
||||||
elements: list[Requirement | DependencyGroupInclude] = []
|
|
||||||
for item in raw_group:
|
|
||||||
if isinstance(item, str):
|
|
||||||
# packaging.requirements.Requirement parsing ensures that this is a
|
|
||||||
# valid PEP 508 Dependency Specifier
|
|
||||||
# raises InvalidRequirement on failure
|
|
||||||
elements.append(Requirement(item))
|
|
||||||
elif isinstance(item, Mapping):
|
|
||||||
if tuple(item.keys()) != ("include-group",):
|
|
||||||
errors.error(
|
|
||||||
InvalidDependencyGroupObject(
|
|
||||||
f"Invalid dependency group item: {item!r}"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
include_group = item["include-group"]
|
|
||||||
elements.append(DependencyGroupInclude(include_group=include_group))
|
|
||||||
else:
|
|
||||||
errors.error(TypeError(f"Invalid dependency group item: {item!r}"))
|
|
||||||
|
|
||||||
self._parsed_groups[group] = tuple(elements)
|
|
||||||
return self._parsed_groups[group]
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------
|
|
||||||
# Functional Interface
|
|
||||||
# --------------------
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_dependency_groups(
|
|
||||||
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]], /, *groups: str
|
|
||||||
) -> tuple[str, ...]:
|
|
||||||
"""
|
|
||||||
Resolve a dependency group to a tuple of requirements, as strings.
|
|
||||||
|
|
||||||
:param dependency_groups: the parsed contents of the ``[dependency-groups]`` table
|
|
||||||
from ``pyproject.toml``
|
|
||||||
:param groups: the name of the group(s) to resolve
|
|
||||||
"""
|
|
||||||
resolver = DependencyGroupResolver(dependency_groups)
|
|
||||||
return tuple(str(r) for group in groups for r in resolver.resolve(group))
|
|
||||||
|
|
||||||
|
|
||||||
# ----------------
|
|
||||||
# internal helpers
|
|
||||||
# ----------------
|
|
||||||
|
|
||||||
|
|
||||||
_NORMALIZE_PATTERN = re.compile(r"[-_.]+")
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_name(name: str) -> str:
|
|
||||||
return _NORMALIZE_PATTERN.sub("-", name).lower()
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_group_names(
|
|
||||||
dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
|
|
||||||
errors: _ErrorCollector,
|
|
||||||
) -> dict[str, Sequence[str | Mapping[str, str]]]:
|
|
||||||
original_names: dict[str, list[str]] = {}
|
|
||||||
normalized_groups: dict[str, Sequence[str | Mapping[str, str]]] = {}
|
|
||||||
|
|
||||||
for group_name, value in dependency_groups.items():
|
|
||||||
normed_group_name = _normalize_name(group_name)
|
|
||||||
original_names.setdefault(normed_group_name, []).append(group_name)
|
|
||||||
normalized_groups[normed_group_name] = value
|
|
||||||
|
|
||||||
for normed_name, names in original_names.items():
|
|
||||||
if len(names) > 1:
|
|
||||||
errors.error(
|
|
||||||
DuplicateGroupNames(
|
|
||||||
"Duplicate dependency group names: "
|
|
||||||
f"{normed_name} ({', '.join(names)})"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return normalized_groups
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import dataclasses
|
|
||||||
import re
|
|
||||||
import urllib.parse
|
|
||||||
from collections.abc import Mapping
|
|
||||||
from typing import TYPE_CHECKING, Any, Protocol, TypeVar
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
|
||||||
import sys
|
|
||||||
from collections.abc import Collection
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 11):
|
|
||||||
from typing import Self
|
|
||||||
else:
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"ArchiveInfo",
|
|
||||||
"DirInfo",
|
|
||||||
"DirectUrl",
|
|
||||||
"DirectUrlValidationError",
|
|
||||||
"VcsInfo",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
|
||||||
|
|
||||||
|
|
||||||
class _FromMappingProtocol(Protocol): # pragma: no cover
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
|
|
||||||
|
|
||||||
|
|
||||||
_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
|
|
||||||
|
|
||||||
|
|
||||||
def _json_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
||||||
return {key: value for key, value in data if value is not None}
|
|
||||||
|
|
||||||
|
|
||||||
def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
|
||||||
"""Get a value from the dictionary and verify it's the expected type."""
|
|
||||||
if (value := d.get(key)) is None:
|
|
||||||
return None
|
|
||||||
if not isinstance(value, expected_type):
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
f"Unexpected type {type(value).__name__} "
|
|
||||||
f"(expected {expected_type.__name__})",
|
|
||||||
context=key,
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
|
|
||||||
"""Get a required value from the dictionary and verify it's the expected type."""
|
|
||||||
if (value := _get(d, expected_type, key)) is None:
|
|
||||||
raise _DirectUrlRequiredKeyError(key)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_object(
|
|
||||||
d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
|
|
||||||
) -> _FromMappingProtocolT | None:
|
|
||||||
"""Get a dictionary value from the dictionary and convert it to a dataclass."""
|
|
||||||
if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return target_type._from_dict(value)
|
|
||||||
except Exception as e:
|
|
||||||
raise DirectUrlValidationError(e, context=key) from e
|
|
||||||
|
|
||||||
|
|
||||||
_PEP610_USER_PASS_ENV_VARS_REGEX = re.compile(
|
|
||||||
r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _strip_auth_from_netloc(netloc: str, safe_user_passwords: Collection[str]) -> str:
|
|
||||||
if "@" not in netloc:
|
|
||||||
return netloc
|
|
||||||
user_pass, netloc_no_user_pass = netloc.split("@", 1)
|
|
||||||
if user_pass in safe_user_passwords:
|
|
||||||
return netloc
|
|
||||||
if _PEP610_USER_PASS_ENV_VARS_REGEX.match(user_pass):
|
|
||||||
return netloc
|
|
||||||
return netloc_no_user_pass
|
|
||||||
|
|
||||||
|
|
||||||
def _strip_url(url: str, safe_user_passwords: Collection[str]) -> str:
|
|
||||||
"""url with user:password part removed unless it is formed with
|
|
||||||
environment variables as specified in PEP 610, or it is a safe user:password
|
|
||||||
such as `git`.
|
|
||||||
"""
|
|
||||||
parsed_url = urllib.parse.urlsplit(url)
|
|
||||||
netloc = _strip_auth_from_netloc(parsed_url.netloc, safe_user_passwords)
|
|
||||||
return urllib.parse.urlunsplit(
|
|
||||||
(
|
|
||||||
parsed_url.scheme,
|
|
||||||
netloc,
|
|
||||||
parsed_url.path,
|
|
||||||
parsed_url.query,
|
|
||||||
parsed_url.fragment,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DirectUrlValidationError(Exception):
|
|
||||||
"""Raised when when input data is not spec-compliant."""
|
|
||||||
|
|
||||||
context: str | None = None
|
|
||||||
message: str
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
cause: str | Exception,
|
|
||||||
*,
|
|
||||||
context: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
if isinstance(cause, DirectUrlValidationError):
|
|
||||||
if cause.context:
|
|
||||||
self.context = (
|
|
||||||
f"{context}.{cause.context}" if context else cause.context
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.context = context # pragma: no cover
|
|
||||||
self.message = cause.message
|
|
||||||
else:
|
|
||||||
self.context = context
|
|
||||||
self.message = str(cause)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
if self.context:
|
|
||||||
return f"{self.message} in {self.context!r}"
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class _DirectUrlRequiredKeyError(DirectUrlValidationError):
|
|
||||||
def __init__(self, key: str) -> None:
|
|
||||||
super().__init__("Missing required value", context=key)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True, init=False)
|
|
||||||
class VcsInfo:
|
|
||||||
vcs: str
|
|
||||||
commit_id: str
|
|
||||||
requested_revision: str | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
vcs: str,
|
|
||||||
commit_id: str,
|
|
||||||
requested_revision: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
object.__setattr__(self, "vcs", vcs)
|
|
||||||
object.__setattr__(self, "commit_id", commit_id)
|
|
||||||
object.__setattr__(self, "requested_revision", requested_revision)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
# We can't validate vcs value because is not closed.
|
|
||||||
return cls(
|
|
||||||
vcs=_get_required(d, str, "vcs"),
|
|
||||||
requested_revision=_get(d, str, "requested_revision"),
|
|
||||||
commit_id=_get_required(d, str, "commit_id"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True, init=False)
|
|
||||||
class ArchiveInfo:
|
|
||||||
hashes: Mapping[str, str] | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
hashes: Mapping[str, str] | None = None,
|
|
||||||
) -> None:
|
|
||||||
object.__setattr__(self, "hashes", hashes)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
hashes = _get(d, Mapping, "hashes") # type: ignore[type-abstract]
|
|
||||||
if hashes is not None and not all(isinstance(h, str) for h in hashes.values()):
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
"Hash values must be strings", context="hashes"
|
|
||||||
)
|
|
||||||
legacy_hash = _get(d, str, "hash")
|
|
||||||
if legacy_hash is not None:
|
|
||||||
if "=" not in legacy_hash:
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
"Invalid hash format (expected '<algorithm>=<hash>')",
|
|
||||||
context="hash",
|
|
||||||
)
|
|
||||||
hash_algorithm, hash_value = legacy_hash.split("=", 1)
|
|
||||||
if hashes is None:
|
|
||||||
# if `hashes` are not present, we can derive it from the legacy `hash`
|
|
||||||
hashes = {hash_algorithm: hash_value}
|
|
||||||
else:
|
|
||||||
# if `hashes` are present, the legacy `hash` must match one of them
|
|
||||||
if hash_algorithm not in hashes:
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
f"Algorithm {hash_algorithm!r} used in hash field "
|
|
||||||
f"is not present in hashes field",
|
|
||||||
context="hashes",
|
|
||||||
)
|
|
||||||
if hashes[hash_algorithm] != hash_value:
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
f"Algorithm {hash_algorithm!r} used in hash field "
|
|
||||||
f"has different value in hashes field",
|
|
||||||
context="hash",
|
|
||||||
)
|
|
||||||
return cls(hashes=hashes)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True, init=False)
|
|
||||||
class DirInfo:
|
|
||||||
editable: bool | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
editable: bool | None = None,
|
|
||||||
) -> None:
|
|
||||||
object.__setattr__(self, "editable", editable)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
return cls(
|
|
||||||
editable=_get(d, bool, "editable"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True, init=False)
|
|
||||||
class DirectUrl:
|
|
||||||
"""A class representing a direct URL."""
|
|
||||||
|
|
||||||
url: str
|
|
||||||
archive_info: ArchiveInfo | None = None
|
|
||||||
vcs_info: VcsInfo | None = None
|
|
||||||
dir_info: DirInfo | None = None
|
|
||||||
subdirectory: str | None = None # XXX Path or str?
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
url: str,
|
|
||||||
archive_info: ArchiveInfo | None = None,
|
|
||||||
vcs_info: VcsInfo | None = None,
|
|
||||||
dir_info: DirInfo | None = None,
|
|
||||||
subdirectory: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
object.__setattr__(self, "url", url)
|
|
||||||
object.__setattr__(self, "archive_info", archive_info)
|
|
||||||
object.__setattr__(self, "vcs_info", vcs_info)
|
|
||||||
object.__setattr__(self, "dir_info", dir_info)
|
|
||||||
object.__setattr__(self, "subdirectory", subdirectory)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
direct_url = cls(
|
|
||||||
url=_get_required(d, str, "url"),
|
|
||||||
archive_info=_get_object(d, ArchiveInfo, "archive_info"),
|
|
||||||
vcs_info=_get_object(d, VcsInfo, "vcs_info"),
|
|
||||||
dir_info=_get_object(d, DirInfo, "dir_info"),
|
|
||||||
subdirectory=_get(d, str, "subdirectory"),
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
bool(direct_url.vcs_info)
|
|
||||||
+ bool(direct_url.archive_info)
|
|
||||||
+ bool(direct_url.dir_info)
|
|
||||||
) != 1:
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
"Exactly one of vcs_info, archive_info, dir_info must be present"
|
|
||||||
)
|
|
||||||
if direct_url.dir_info is not None and not direct_url.url.startswith("file://"):
|
|
||||||
raise DirectUrlValidationError(
|
|
||||||
"URL scheme must be file:// when dir_info is present",
|
|
||||||
context="url",
|
|
||||||
)
|
|
||||||
# XXX subdirectory must be relative, can we, should we validate that here?
|
|
||||||
return direct_url
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d: Mapping[str, Any], /) -> Self:
|
|
||||||
"""Create and validate a DirectUrl instance from a JSON dictionary."""
|
|
||||||
return cls._from_dict(d)
|
|
||||||
|
|
||||||
def to_dict(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
generate_legacy_hash: bool = False,
|
|
||||||
strip_user_password: bool = True,
|
|
||||||
safe_user_passwords: Collection[str] = ("git",),
|
|
||||||
) -> Mapping[str, Any]:
|
|
||||||
"""Convert the DirectUrl instance to a JSON dictionary.
|
|
||||||
|
|
||||||
:param generate_legacy_hash: If True, include a legacy `hash` field in
|
|
||||||
`archive_info` for backward compatibility with tools that don't
|
|
||||||
support the `hashes` field.
|
|
||||||
:param strip_user_password: If True, strip user:password from the URL
|
|
||||||
unless it is formed with environment variables as specified in PEP
|
|
||||||
610, or it is a safe user:password such as `git`.
|
|
||||||
:param safe_user_passwords: A collection of user:password strings that
|
|
||||||
should not be stripped from the URL even if `strip_user_password` is
|
|
||||||
True.
|
|
||||||
"""
|
|
||||||
res = dataclasses.asdict(self, dict_factory=_json_dict_factory)
|
|
||||||
if generate_legacy_hash and self.archive_info and self.archive_info.hashes:
|
|
||||||
hash_algorithm, hash_value = next(iter(self.archive_info.hashes.items()))
|
|
||||||
res["archive_info"]["hash"] = f"{hash_algorithm}={hash_value}"
|
|
||||||
if strip_user_password:
|
|
||||||
res["url"] = _strip_url(self.url, safe_user_passwords)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def validate(self) -> None:
|
|
||||||
"""Validate the DirectUrl instance against the specification.
|
|
||||||
|
|
||||||
Raises :class:`DirectUrlValidationError` if invalid.
|
|
||||||
"""
|
|
||||||
self.from_dict(self.to_dict())
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import dataclasses
|
|
||||||
import sys
|
|
||||||
import typing
|
|
||||||
|
|
||||||
__all__ = ["ExceptionGroup"]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 11): # pragma: no cover
|
|
||||||
from builtins import ExceptionGroup
|
|
||||||
else: # pragma: no cover
|
|
||||||
|
|
||||||
class ExceptionGroup(Exception):
|
|
||||||
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
|
|
||||||
|
|
||||||
If :external:exc:`ExceptionGroup` is already defined by Python itself,
|
|
||||||
that version is used instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
message: str
|
|
||||||
exceptions: list[Exception]
|
|
||||||
|
|
||||||
def __init__(self, message: str, exceptions: list[Exception]) -> None:
|
|
||||||
self.message = message
|
|
||||||
self.exceptions = exceptions
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass
|
|
||||||
class _ErrorCollector:
|
|
||||||
"""
|
|
||||||
Collect errors into ExceptionGroups.
|
|
||||||
|
|
||||||
Used like this:
|
|
||||||
|
|
||||||
collector = _ErrorCollector()
|
|
||||||
# Add a single exception
|
|
||||||
collector.error(ValueError("one"))
|
|
||||||
|
|
||||||
# Supports nesting, including combining ExceptionGroups
|
|
||||||
with collector.collect():
|
|
||||||
raise ValueError("two")
|
|
||||||
collector.finalize("Found some errors")
|
|
||||||
|
|
||||||
Since making a collector and then calling finalize later is a common pattern,
|
|
||||||
a convenience method ``on_exit`` is provided.
|
|
||||||
"""
|
|
||||||
|
|
||||||
errors: list[Exception] = dataclasses.field(default_factory=list, init=False)
|
|
||||||
|
|
||||||
def finalize(self, msg: str) -> None:
|
|
||||||
"""Raise a group exception if there are any errors."""
|
|
||||||
if self.errors:
|
|
||||||
raise ExceptionGroup(msg, self.errors)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def on_exit(self, msg: str) -> typing.Generator[_ErrorCollector, None, None]:
|
|
||||||
"""
|
|
||||||
Calls finalize if no uncollected errors were present.
|
|
||||||
|
|
||||||
Uncollected errors are raised normally.
|
|
||||||
"""
|
|
||||||
yield self
|
|
||||||
self.finalize(msg)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def collect(self, *err_cls: type[Exception]) -> typing.Generator[None, None, None]:
|
|
||||||
"""
|
|
||||||
Context manager to collect errors into the error list.
|
|
||||||
|
|
||||||
Must be inside loops, as only one error can be collected at a time.
|
|
||||||
"""
|
|
||||||
error_classes = err_cls or (Exception,)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
except ExceptionGroup as error:
|
|
||||||
self.errors.extend(error.exceptions)
|
|
||||||
except error_classes as error:
|
|
||||||
self.errors.append(error)
|
|
||||||
|
|
||||||
def error(
|
|
||||||
self,
|
|
||||||
error: Exception,
|
|
||||||
) -> None:
|
|
||||||
"""Add an error to the list."""
|
|
||||||
self.errors.append(error)
|
|
||||||
@@ -34,7 +34,7 @@ from __future__ import annotations
|
|||||||
import re
|
import re
|
||||||
from typing import NewType, cast
|
from typing import NewType, cast
|
||||||
|
|
||||||
from ._spdx import EXCEPTIONS, LICENSES
|
from packaging.licenses._spdx import EXCEPTIONS, LICENSES
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"InvalidLicenseExpression",
|
"InvalidLicenseExpression",
|
||||||
@@ -42,25 +42,14 @@ __all__ = [
|
|||||||
"canonicalize_license_expression",
|
"canonicalize_license_expression",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Simple __dir__ implementation since there are no public submodules
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$")
|
license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$")
|
||||||
|
|
||||||
NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str)
|
NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str)
|
||||||
"""
|
|
||||||
A :class:`typing.NewType` of :class:`str`, representing a normalized
|
|
||||||
License-Expression.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidLicenseExpression(ValueError):
|
class InvalidLicenseExpression(ValueError):
|
||||||
"""Raised when a license-expression string is invalid
|
"""Raised when a license-expression string is invalid
|
||||||
|
|
||||||
>>> from packaging.licenses import canonicalize_license_expression
|
|
||||||
>>> canonicalize_license_expression("invalid")
|
>>> canonicalize_license_expression("invalid")
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
@@ -71,34 +60,6 @@ class InvalidLicenseExpression(ValueError):
|
|||||||
def canonicalize_license_expression(
|
def canonicalize_license_expression(
|
||||||
raw_license_expression: str,
|
raw_license_expression: str,
|
||||||
) -> NormalizedLicenseExpression:
|
) -> NormalizedLicenseExpression:
|
||||||
"""
|
|
||||||
This function takes a valid License-Expression, and returns the normalized
|
|
||||||
form of it.
|
|
||||||
|
|
||||||
The return type is typed as :class:`NormalizedLicenseExpression`. This
|
|
||||||
allows type checkers to help require that a string has passed through this
|
|
||||||
function before use.
|
|
||||||
|
|
||||||
:param str raw_license_expression: The License-Expression to canonicalize.
|
|
||||||
:raises InvalidLicenseExpression: If the License-Expression is invalid due to an
|
|
||||||
invalid/unknown license identifier or invalid syntax.
|
|
||||||
|
|
||||||
.. doctest::
|
|
||||||
|
|
||||||
>>> from packaging.licenses import canonicalize_license_expression
|
|
||||||
>>> canonicalize_license_expression("mit")
|
|
||||||
'MIT'
|
|
||||||
>>> canonicalize_license_expression("mit and (apache-2.0 or bsd-2-clause)")
|
|
||||||
'MIT AND (Apache-2.0 OR BSD-2-Clause)'
|
|
||||||
>>> canonicalize_license_expression("(mit")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
InvalidLicenseExpression: Invalid license expression: '(mit'
|
|
||||||
>>> canonicalize_license_expression("Use-it-after-midnight")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
InvalidLicenseExpression: Unknown license: 'Use-it-after-midnight'
|
|
||||||
"""
|
|
||||||
if not raw_license_expression:
|
if not raw_license_expression:
|
||||||
message = f"Invalid license expression: {raw_license_expression!r}"
|
message = f"Invalid license expression: {raw_license_expression!r}"
|
||||||
raise InvalidLicenseExpression(message)
|
raise InvalidLicenseExpression(message)
|
||||||
@@ -119,21 +80,16 @@ def canonicalize_license_expression(
|
|||||||
|
|
||||||
tokens = license_expression.split()
|
tokens = license_expression.split()
|
||||||
|
|
||||||
# Rather than implementing a parenthesis/boolean logic parser, create an
|
# Rather than implementing boolean logic, we create an expression that Python can
|
||||||
# expression that Python can parse. Everything that is not involved with the
|
# parse. Everything that is not involved with the grammar itself is treated as
|
||||||
# grammar itself is replaced with the placeholder `False` and the resultant
|
# `False` and the expression should evaluate as such.
|
||||||
# expression should become a valid Python expression.
|
|
||||||
python_tokens = []
|
python_tokens = []
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
if token not in {"or", "and", "with", "(", ")"}:
|
if token not in {"or", "and", "with", "(", ")"}:
|
||||||
python_tokens.append("False")
|
python_tokens.append("False")
|
||||||
elif token == "with":
|
elif token == "with":
|
||||||
python_tokens.append("or")
|
python_tokens.append("or")
|
||||||
elif (
|
elif token == "(" and python_tokens and python_tokens[-1] not in {"or", "and"}:
|
||||||
token == "("
|
|
||||||
and python_tokens
|
|
||||||
and python_tokens[-1] not in {"or", "and", "("}
|
|
||||||
) or (token == ")" and python_tokens and python_tokens[-1] == "("):
|
|
||||||
message = f"Invalid license expression: {raw_license_expression!r}"
|
message = f"Invalid license expression: {raw_license_expression!r}"
|
||||||
raise InvalidLicenseExpression(message)
|
raise InvalidLicenseExpression(message)
|
||||||
else:
|
else:
|
||||||
@@ -141,8 +97,11 @@ def canonicalize_license_expression(
|
|||||||
|
|
||||||
python_expression = " ".join(python_tokens)
|
python_expression = " ".join(python_tokens)
|
||||||
try:
|
try:
|
||||||
compile(python_expression, "", "eval")
|
invalid = eval(python_expression, globals(), locals())
|
||||||
except SyntaxError:
|
except Exception:
|
||||||
|
invalid = True
|
||||||
|
|
||||||
|
if invalid is not False:
|
||||||
message = f"Invalid license expression: {raw_license_expression!r}"
|
message = f"Invalid license expression: {raw_license_expression!r}"
|
||||||
raise InvalidLicenseExpression(message) from None
|
raise InvalidLicenseExpression(message) from None
|
||||||
|
|
||||||
@@ -181,6 +140,6 @@ def canonicalize_license_expression(
|
|||||||
normalized_expression = " ".join(normalized_tokens)
|
normalized_expression = " ".join(normalized_tokens)
|
||||||
|
|
||||||
return cast(
|
return cast(
|
||||||
"NormalizedLicenseExpression",
|
NormalizedLicenseExpression,
|
||||||
normalized_expression.replace("( ", "(").replace(" )", ")"),
|
normalized_expression.replace("( ", "(").replace(" )", ")"),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class SPDXException(TypedDict):
|
|||||||
deprecated: bool
|
deprecated: bool
|
||||||
|
|
||||||
|
|
||||||
VERSION = '3.27.0'
|
VERSION = '3.25.0'
|
||||||
|
|
||||||
LICENSES: dict[str, SPDXLicense] = {
|
LICENSES: dict[str, SPDXLicense] = {
|
||||||
'0bsd': {'id': '0BSD', 'deprecated': False},
|
'0bsd': {'id': '0BSD', 'deprecated': False},
|
||||||
@@ -46,7 +46,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False},
|
'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False},
|
||||||
'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False},
|
'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False},
|
||||||
'any-osi': {'id': 'any-OSI', 'deprecated': False},
|
'any-osi': {'id': 'any-OSI', 'deprecated': False},
|
||||||
'any-osi-perl-modules': {'id': 'any-OSI-perl-modules', 'deprecated': False},
|
|
||||||
'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False},
|
'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False},
|
||||||
'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False},
|
'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False},
|
||||||
'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False},
|
'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False},
|
||||||
@@ -62,8 +61,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False},
|
'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False},
|
||||||
'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False},
|
'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False},
|
||||||
'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False},
|
'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False},
|
||||||
'artistic-dist': {'id': 'Artistic-dist', 'deprecated': False},
|
|
||||||
'aspell-ru': {'id': 'Aspell-RU', 'deprecated': False},
|
|
||||||
'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False},
|
'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False},
|
||||||
'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False},
|
'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False},
|
||||||
'baekmuk': {'id': 'Baekmuk', 'deprecated': False},
|
'baekmuk': {'id': 'Baekmuk', 'deprecated': False},
|
||||||
@@ -78,7 +75,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'blessing': {'id': 'blessing', 'deprecated': False},
|
'blessing': {'id': 'blessing', 'deprecated': False},
|
||||||
'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False},
|
'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False},
|
||||||
'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False},
|
'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False},
|
||||||
'boehm-gc-without-fee': {'id': 'Boehm-GC-without-fee', 'deprecated': False},
|
|
||||||
'borceux': {'id': 'Borceux', 'deprecated': False},
|
'borceux': {'id': 'Borceux', 'deprecated': False},
|
||||||
'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False},
|
'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False},
|
||||||
'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False},
|
'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False},
|
||||||
@@ -89,7 +85,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True},
|
'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True},
|
||||||
'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True},
|
'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True},
|
||||||
'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False},
|
'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False},
|
||||||
'bsd-2-clause-pkgconf-disclaimer': {'id': 'BSD-2-Clause-pkgconf-disclaimer', 'deprecated': False},
|
|
||||||
'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False},
|
'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False},
|
||||||
'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False},
|
'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False},
|
||||||
'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False},
|
'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False},
|
||||||
@@ -181,8 +176,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False},
|
'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False},
|
||||||
'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False},
|
'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False},
|
||||||
'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False},
|
'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False},
|
||||||
'cc-pdm-1.0': {'id': 'CC-PDM-1.0', 'deprecated': False},
|
|
||||||
'cc-sa-1.0': {'id': 'CC-SA-1.0', 'deprecated': False},
|
|
||||||
'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False},
|
'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False},
|
||||||
'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False},
|
'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False},
|
||||||
'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False},
|
'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False},
|
||||||
@@ -222,7 +215,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False},
|
'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False},
|
||||||
'cronyx': {'id': 'Cronyx', 'deprecated': False},
|
'cronyx': {'id': 'Cronyx', 'deprecated': False},
|
||||||
'crossword': {'id': 'Crossword', 'deprecated': False},
|
'crossword': {'id': 'Crossword', 'deprecated': False},
|
||||||
'cryptoswift': {'id': 'CryptoSwift', 'deprecated': False},
|
|
||||||
'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False},
|
'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False},
|
||||||
'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False},
|
'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False},
|
||||||
'cube': {'id': 'Cube', 'deprecated': False},
|
'cube': {'id': 'Cube', 'deprecated': False},
|
||||||
@@ -234,9 +226,7 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False},
|
'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False},
|
||||||
'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False},
|
'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False},
|
||||||
'doc': {'id': 'DOC', 'deprecated': False},
|
'doc': {'id': 'DOC', 'deprecated': False},
|
||||||
'docbook-dtd': {'id': 'DocBook-DTD', 'deprecated': False},
|
|
||||||
'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False},
|
'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False},
|
||||||
'docbook-stylesheet': {'id': 'DocBook-Stylesheet', 'deprecated': False},
|
|
||||||
'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False},
|
'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False},
|
||||||
'dotseqn': {'id': 'Dotseqn', 'deprecated': False},
|
'dotseqn': {'id': 'Dotseqn', 'deprecated': False},
|
||||||
'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False},
|
'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False},
|
||||||
@@ -273,17 +263,12 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False},
|
'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False},
|
||||||
'fsful': {'id': 'FSFUL', 'deprecated': False},
|
'fsful': {'id': 'FSFUL', 'deprecated': False},
|
||||||
'fsfullr': {'id': 'FSFULLR', 'deprecated': False},
|
'fsfullr': {'id': 'FSFULLR', 'deprecated': False},
|
||||||
'fsfullrsd': {'id': 'FSFULLRSD', 'deprecated': False},
|
|
||||||
'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False},
|
'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False},
|
||||||
'fsl-1.1-alv2': {'id': 'FSL-1.1-ALv2', 'deprecated': False},
|
|
||||||
'fsl-1.1-mit': {'id': 'FSL-1.1-MIT', 'deprecated': False},
|
|
||||||
'ftl': {'id': 'FTL', 'deprecated': False},
|
'ftl': {'id': 'FTL', 'deprecated': False},
|
||||||
'furuseth': {'id': 'Furuseth', 'deprecated': False},
|
'furuseth': {'id': 'Furuseth', 'deprecated': False},
|
||||||
'fwlw': {'id': 'fwlw', 'deprecated': False},
|
'fwlw': {'id': 'fwlw', 'deprecated': False},
|
||||||
'game-programming-gems': {'id': 'Game-Programming-Gems', 'deprecated': False},
|
|
||||||
'gcr-docs': {'id': 'GCR-docs', 'deprecated': False},
|
'gcr-docs': {'id': 'GCR-docs', 'deprecated': False},
|
||||||
'gd': {'id': 'GD', 'deprecated': False},
|
'gd': {'id': 'GD', 'deprecated': False},
|
||||||
'generic-xts': {'id': 'generic-xts', 'deprecated': False},
|
|
||||||
'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True},
|
'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True},
|
||||||
'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False},
|
'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False},
|
||||||
'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False},
|
'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False},
|
||||||
@@ -335,7 +320,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'gtkbook': {'id': 'gtkbook', 'deprecated': False},
|
'gtkbook': {'id': 'gtkbook', 'deprecated': False},
|
||||||
'gutmann': {'id': 'Gutmann', 'deprecated': False},
|
'gutmann': {'id': 'Gutmann', 'deprecated': False},
|
||||||
'haskellreport': {'id': 'HaskellReport', 'deprecated': False},
|
'haskellreport': {'id': 'HaskellReport', 'deprecated': False},
|
||||||
'hdf5': {'id': 'HDF5', 'deprecated': False},
|
|
||||||
'hdparm': {'id': 'hdparm', 'deprecated': False},
|
'hdparm': {'id': 'hdparm', 'deprecated': False},
|
||||||
'hidapi': {'id': 'HIDAPI', 'deprecated': False},
|
'hidapi': {'id': 'HIDAPI', 'deprecated': False},
|
||||||
'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False},
|
'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False},
|
||||||
@@ -376,7 +360,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'imlib2': {'id': 'Imlib2', 'deprecated': False},
|
'imlib2': {'id': 'Imlib2', 'deprecated': False},
|
||||||
'info-zip': {'id': 'Info-ZIP', 'deprecated': False},
|
'info-zip': {'id': 'Info-ZIP', 'deprecated': False},
|
||||||
'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False},
|
'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False},
|
||||||
'innosetup': {'id': 'InnoSetup', 'deprecated': False},
|
|
||||||
'intel': {'id': 'Intel', 'deprecated': False},
|
'intel': {'id': 'Intel', 'deprecated': False},
|
||||||
'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False},
|
'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False},
|
||||||
'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False},
|
'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False},
|
||||||
@@ -386,7 +369,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False},
|
'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False},
|
||||||
'jam': {'id': 'Jam', 'deprecated': False},
|
'jam': {'id': 'Jam', 'deprecated': False},
|
||||||
'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False},
|
'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False},
|
||||||
'jove': {'id': 'jove', 'deprecated': False},
|
|
||||||
'jpl-image': {'id': 'JPL-image', 'deprecated': False},
|
'jpl-image': {'id': 'JPL-image', 'deprecated': False},
|
||||||
'jpnic': {'id': 'JPNIC', 'deprecated': False},
|
'jpnic': {'id': 'JPNIC', 'deprecated': False},
|
||||||
'json': {'id': 'JSON', 'deprecated': False},
|
'json': {'id': 'JSON', 'deprecated': False},
|
||||||
@@ -412,7 +394,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False},
|
'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False},
|
||||||
'lgpllr': {'id': 'LGPLLR', 'deprecated': False},
|
'lgpllr': {'id': 'LGPLLR', 'deprecated': False},
|
||||||
'libpng': {'id': 'Libpng', 'deprecated': False},
|
'libpng': {'id': 'Libpng', 'deprecated': False},
|
||||||
'libpng-1.6.35': {'id': 'libpng-1.6.35', 'deprecated': False},
|
|
||||||
'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False},
|
'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False},
|
||||||
'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False},
|
'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False},
|
||||||
'libtiff': {'id': 'libtiff', 'deprecated': False},
|
'libtiff': {'id': 'libtiff', 'deprecated': False},
|
||||||
@@ -443,17 +424,14 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'magaz': {'id': 'magaz', 'deprecated': False},
|
'magaz': {'id': 'magaz', 'deprecated': False},
|
||||||
'mailprio': {'id': 'mailprio', 'deprecated': False},
|
'mailprio': {'id': 'mailprio', 'deprecated': False},
|
||||||
'makeindex': {'id': 'MakeIndex', 'deprecated': False},
|
'makeindex': {'id': 'MakeIndex', 'deprecated': False},
|
||||||
'man2html': {'id': 'man2html', 'deprecated': False},
|
|
||||||
'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False},
|
'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False},
|
||||||
'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False},
|
'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False},
|
||||||
'metamail': {'id': 'metamail', 'deprecated': False},
|
'metamail': {'id': 'metamail', 'deprecated': False},
|
||||||
'minpack': {'id': 'Minpack', 'deprecated': False},
|
'minpack': {'id': 'Minpack', 'deprecated': False},
|
||||||
'mips': {'id': 'MIPS', 'deprecated': False},
|
|
||||||
'miros': {'id': 'MirOS', 'deprecated': False},
|
'miros': {'id': 'MirOS', 'deprecated': False},
|
||||||
'mit': {'id': 'MIT', 'deprecated': False},
|
'mit': {'id': 'MIT', 'deprecated': False},
|
||||||
'mit-0': {'id': 'MIT-0', 'deprecated': False},
|
'mit-0': {'id': 'MIT-0', 'deprecated': False},
|
||||||
'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False},
|
'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False},
|
||||||
'mit-click': {'id': 'MIT-Click', 'deprecated': False},
|
|
||||||
'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False},
|
'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False},
|
||||||
'mit-enna': {'id': 'MIT-enna', 'deprecated': False},
|
'mit-enna': {'id': 'MIT-enna', 'deprecated': False},
|
||||||
'mit-feh': {'id': 'MIT-feh', 'deprecated': False},
|
'mit-feh': {'id': 'MIT-feh', 'deprecated': False},
|
||||||
@@ -494,7 +472,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'netcdf': {'id': 'NetCDF', 'deprecated': False},
|
'netcdf': {'id': 'NetCDF', 'deprecated': False},
|
||||||
'newsletr': {'id': 'Newsletr', 'deprecated': False},
|
'newsletr': {'id': 'Newsletr', 'deprecated': False},
|
||||||
'ngpl': {'id': 'NGPL', 'deprecated': False},
|
'ngpl': {'id': 'NGPL', 'deprecated': False},
|
||||||
'ngrep': {'id': 'ngrep', 'deprecated': False},
|
|
||||||
'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False},
|
'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False},
|
||||||
'nist-pd': {'id': 'NIST-PD', 'deprecated': False},
|
'nist-pd': {'id': 'NIST-PD', 'deprecated': False},
|
||||||
'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False},
|
'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False},
|
||||||
@@ -509,7 +486,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False},
|
'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False},
|
||||||
'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False},
|
'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False},
|
||||||
'nrl': {'id': 'NRL', 'deprecated': False},
|
'nrl': {'id': 'NRL', 'deprecated': False},
|
||||||
'ntia-pd': {'id': 'NTIA-PD', 'deprecated': False},
|
|
||||||
'ntp': {'id': 'NTP', 'deprecated': False},
|
'ntp': {'id': 'NTP', 'deprecated': False},
|
||||||
'ntp-0': {'id': 'NTP-0', 'deprecated': False},
|
'ntp-0': {'id': 'NTP-0', 'deprecated': False},
|
||||||
'nunit': {'id': 'Nunit', 'deprecated': True},
|
'nunit': {'id': 'Nunit', 'deprecated': True},
|
||||||
@@ -604,7 +580,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'schemereport': {'id': 'SchemeReport', 'deprecated': False},
|
'schemereport': {'id': 'SchemeReport', 'deprecated': False},
|
||||||
'sendmail': {'id': 'Sendmail', 'deprecated': False},
|
'sendmail': {'id': 'Sendmail', 'deprecated': False},
|
||||||
'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False},
|
'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False},
|
||||||
'sendmail-open-source-1.1': {'id': 'Sendmail-Open-Source-1.1', 'deprecated': False},
|
|
||||||
'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False},
|
'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False},
|
||||||
'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False},
|
'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False},
|
||||||
'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False},
|
'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False},
|
||||||
@@ -617,12 +592,10 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False},
|
'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False},
|
||||||
'sl': {'id': 'SL', 'deprecated': False},
|
'sl': {'id': 'SL', 'deprecated': False},
|
||||||
'sleepycat': {'id': 'Sleepycat', 'deprecated': False},
|
'sleepycat': {'id': 'Sleepycat', 'deprecated': False},
|
||||||
'smail-gpl': {'id': 'SMAIL-GPL', 'deprecated': False},
|
|
||||||
'smlnj': {'id': 'SMLNJ', 'deprecated': False},
|
'smlnj': {'id': 'SMLNJ', 'deprecated': False},
|
||||||
'smppl': {'id': 'SMPPL', 'deprecated': False},
|
'smppl': {'id': 'SMPPL', 'deprecated': False},
|
||||||
'snia': {'id': 'SNIA', 'deprecated': False},
|
'snia': {'id': 'SNIA', 'deprecated': False},
|
||||||
'snprintf': {'id': 'snprintf', 'deprecated': False},
|
'snprintf': {'id': 'snprintf', 'deprecated': False},
|
||||||
'sofa': {'id': 'SOFA', 'deprecated': False},
|
|
||||||
'softsurfer': {'id': 'softSurfer', 'deprecated': False},
|
'softsurfer': {'id': 'softSurfer', 'deprecated': False},
|
||||||
'soundex': {'id': 'Soundex', 'deprecated': False},
|
'soundex': {'id': 'Soundex', 'deprecated': False},
|
||||||
'spencer-86': {'id': 'Spencer-86', 'deprecated': False},
|
'spencer-86': {'id': 'Spencer-86', 'deprecated': False},
|
||||||
@@ -636,7 +609,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False},
|
'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False},
|
||||||
'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True},
|
'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True},
|
||||||
'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False},
|
'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False},
|
||||||
'sul-1.0': {'id': 'SUL-1.0', 'deprecated': False},
|
|
||||||
'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False},
|
'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False},
|
||||||
'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False},
|
'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False},
|
||||||
'sunpro': {'id': 'SunPro', 'deprecated': False},
|
'sunpro': {'id': 'SunPro', 'deprecated': False},
|
||||||
@@ -648,14 +620,12 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False},
|
'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False},
|
||||||
'termreadkey': {'id': 'TermReadKey', 'deprecated': False},
|
'termreadkey': {'id': 'TermReadKey', 'deprecated': False},
|
||||||
'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False},
|
'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False},
|
||||||
'thirdeye': {'id': 'ThirdEye', 'deprecated': False},
|
|
||||||
'threeparttable': {'id': 'threeparttable', 'deprecated': False},
|
'threeparttable': {'id': 'threeparttable', 'deprecated': False},
|
||||||
'tmate': {'id': 'TMate', 'deprecated': False},
|
'tmate': {'id': 'TMate', 'deprecated': False},
|
||||||
'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False},
|
'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False},
|
||||||
'tosl': {'id': 'TOSL', 'deprecated': False},
|
'tosl': {'id': 'TOSL', 'deprecated': False},
|
||||||
'tpdl': {'id': 'TPDL', 'deprecated': False},
|
'tpdl': {'id': 'TPDL', 'deprecated': False},
|
||||||
'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False},
|
'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False},
|
||||||
'trustedqsl': {'id': 'TrustedQSL', 'deprecated': False},
|
|
||||||
'ttwl': {'id': 'TTWL', 'deprecated': False},
|
'ttwl': {'id': 'TTWL', 'deprecated': False},
|
||||||
'ttyp0': {'id': 'TTYP0', 'deprecated': False},
|
'ttyp0': {'id': 'TTYP0', 'deprecated': False},
|
||||||
'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False},
|
'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False},
|
||||||
@@ -671,8 +641,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False},
|
'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False},
|
||||||
'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False},
|
'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False},
|
||||||
'unlicense': {'id': 'Unlicense', 'deprecated': False},
|
'unlicense': {'id': 'Unlicense', 'deprecated': False},
|
||||||
'unlicense-libtelnet': {'id': 'Unlicense-libtelnet', 'deprecated': False},
|
|
||||||
'unlicense-libwhirlpool': {'id': 'Unlicense-libwhirlpool', 'deprecated': False},
|
|
||||||
'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False},
|
'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False},
|
||||||
'urt-rle': {'id': 'URT-RLE', 'deprecated': False},
|
'urt-rle': {'id': 'URT-RLE', 'deprecated': False},
|
||||||
'vim': {'id': 'Vim', 'deprecated': False},
|
'vim': {'id': 'Vim', 'deprecated': False},
|
||||||
@@ -686,7 +654,6 @@ LICENSES: dict[str, SPDXLicense] = {
|
|||||||
'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False},
|
'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False},
|
||||||
'wsuipa': {'id': 'Wsuipa', 'deprecated': False},
|
'wsuipa': {'id': 'Wsuipa', 'deprecated': False},
|
||||||
'wtfpl': {'id': 'WTFPL', 'deprecated': False},
|
'wtfpl': {'id': 'WTFPL', 'deprecated': False},
|
||||||
'wwl': {'id': 'wwl', 'deprecated': False},
|
|
||||||
'wxwindows': {'id': 'wxWindows', 'deprecated': True},
|
'wxwindows': {'id': 'wxWindows', 'deprecated': True},
|
||||||
'x11': {'id': 'X11', 'deprecated': False},
|
'x11': {'id': 'X11', 'deprecated': False},
|
||||||
'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False},
|
'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False},
|
||||||
@@ -728,11 +695,9 @@ EXCEPTIONS: dict[str, SPDXException] = {
|
|||||||
'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False},
|
'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False},
|
||||||
'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False},
|
'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False},
|
||||||
'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False},
|
'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False},
|
||||||
'cgal-linking-exception': {'id': 'CGAL-linking-exception', 'deprecated': False},
|
|
||||||
'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False},
|
'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False},
|
||||||
'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False},
|
'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False},
|
||||||
'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False},
|
'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False},
|
||||||
'digia-qt-lgpl-exception-1.1': {'id': 'Digia-Qt-LGPL-exception-1.1', 'deprecated': False},
|
|
||||||
'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False},
|
'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False},
|
||||||
'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False},
|
'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False},
|
||||||
'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False},
|
'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False},
|
||||||
@@ -749,16 +714,13 @@ EXCEPTIONS: dict[str, SPDXException] = {
|
|||||||
'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False},
|
'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False},
|
||||||
'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False},
|
'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False},
|
||||||
'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False},
|
'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False},
|
||||||
'gpl-3.0-389-ds-base-exception': {'id': 'GPL-3.0-389-ds-base-exception', 'deprecated': False},
|
|
||||||
'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False},
|
'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False},
|
||||||
'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False},
|
'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False},
|
||||||
'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False},
|
'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False},
|
||||||
'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False},
|
'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False},
|
||||||
'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False},
|
'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False},
|
||||||
'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False},
|
'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False},
|
||||||
'harbour-exception': {'id': 'harbour-exception', 'deprecated': False},
|
|
||||||
'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False},
|
'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False},
|
||||||
'independent-modules-exception': {'id': 'Independent-modules-exception', 'deprecated': False},
|
|
||||||
'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False},
|
'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False},
|
||||||
'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False},
|
'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False},
|
||||||
'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False},
|
'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False},
|
||||||
@@ -768,14 +730,12 @@ EXCEPTIONS: dict[str, SPDXException] = {
|
|||||||
'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False},
|
'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False},
|
||||||
'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False},
|
'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False},
|
||||||
'mif-exception': {'id': 'mif-exception', 'deprecated': False},
|
'mif-exception': {'id': 'mif-exception', 'deprecated': False},
|
||||||
'mxml-exception': {'id': 'mxml-exception', 'deprecated': False},
|
|
||||||
'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True},
|
'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True},
|
||||||
'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False},
|
'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False},
|
||||||
'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False},
|
'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False},
|
||||||
'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False},
|
'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False},
|
||||||
'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False},
|
'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False},
|
||||||
'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False},
|
'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False},
|
||||||
'polyparse-exception': {'id': 'polyparse-exception', 'deprecated': False},
|
|
||||||
'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False},
|
'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False},
|
||||||
'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False},
|
'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False},
|
||||||
'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False},
|
'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False},
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import operator
|
|||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
from typing import AbstractSet, Callable, Literal, Mapping, TypedDict, Union, cast
|
from typing import AbstractSet, Any, Callable, Literal, TypedDict, Union, cast
|
||||||
|
|
||||||
from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
|
from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
|
||||||
from ._parser import parse_marker as _parse_marker
|
from ._parser import parse_marker as _parse_marker
|
||||||
@@ -17,7 +17,6 @@ from .specifiers import InvalidSpecifier, Specifier
|
|||||||
from .utils import canonicalize_name
|
from .utils import canonicalize_name
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Environment",
|
|
||||||
"EvaluateContext",
|
"EvaluateContext",
|
||||||
"InvalidMarker",
|
"InvalidMarker",
|
||||||
"Marker",
|
"Marker",
|
||||||
@@ -26,58 +25,31 @@ __all__ = [
|
|||||||
"default_environment",
|
"default_environment",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
|
Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
|
||||||
EvaluateContext = Literal["metadata", "lock_file", "requirement"]
|
EvaluateContext = Literal["metadata", "lock_file", "requirement"]
|
||||||
"""A ``typing.Literal`` enumerating valid marker evaluation contexts.
|
|
||||||
|
|
||||||
Valid values for the ``context`` passed to :meth:`Marker.evaluate` are:
|
|
||||||
|
|
||||||
* ``"metadata"`` (for core metadata; default)
|
|
||||||
* ``"lock_file"`` (for lock files)
|
|
||||||
* ``"requirement"`` (i.e. all other situations)
|
|
||||||
"""
|
|
||||||
|
|
||||||
MARKERS_ALLOWING_SET = {"extras", "dependency_groups"}
|
MARKERS_ALLOWING_SET = {"extras", "dependency_groups"}
|
||||||
MARKERS_REQUIRING_VERSION = {
|
|
||||||
"implementation_version",
|
|
||||||
"platform_release",
|
|
||||||
"python_full_version",
|
|
||||||
"python_version",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidMarker(ValueError):
|
class InvalidMarker(ValueError):
|
||||||
"""Raised when attempting to create a :class:`Marker` from invalid input.
|
"""
|
||||||
|
An invalid marker was found, users should refer to PEP 508.
|
||||||
This error indicates that the given marker string does not conform to the
|
|
||||||
:ref:`specification of dependency specifiers <pypug:dependency-specifiers>`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class UndefinedComparison(ValueError):
|
class UndefinedComparison(ValueError):
|
||||||
"""Raised when evaluating an unsupported marker comparison.
|
"""
|
||||||
|
An invalid operation was attempted on a value that doesn't support it.
|
||||||
This can happen when marker values are compared as versions but do not
|
|
||||||
conform to the :ref:`specification of version specifiers
|
|
||||||
<pypug:version-specifiers>`.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class UndefinedEnvironmentName(ValueError):
|
class UndefinedEnvironmentName(ValueError):
|
||||||
"""Raised when evaluating a marker that references a missing environment key."""
|
"""
|
||||||
|
A name was attempted to be used that does not exist inside of the
|
||||||
|
environment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Environment(TypedDict):
|
class Environment(TypedDict):
|
||||||
"""
|
|
||||||
A dictionary that represents a Python environment as captured by
|
|
||||||
:func:`default_environment`. All fields are required.
|
|
||||||
"""
|
|
||||||
|
|
||||||
implementation_name: str
|
implementation_name: str
|
||||||
"""The implementation's identifier, e.g. ``'cpython'``."""
|
"""The implementation's identifier, e.g. ``'cpython'``."""
|
||||||
|
|
||||||
@@ -149,28 +121,20 @@ class Environment(TypedDict):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _normalize_extras(
|
def _normalize_extra_values(results: Any) -> Any:
|
||||||
result: MarkerList | MarkerAtom | str,
|
|
||||||
) -> MarkerList | MarkerAtom | str:
|
|
||||||
if not isinstance(result, tuple):
|
|
||||||
return result
|
|
||||||
|
|
||||||
lhs, op, rhs = result
|
|
||||||
if isinstance(lhs, Variable) and lhs.value == "extra":
|
|
||||||
normalized_extra = canonicalize_name(rhs.value)
|
|
||||||
rhs = Value(normalized_extra)
|
|
||||||
elif isinstance(rhs, Variable) and rhs.value == "extra":
|
|
||||||
normalized_extra = canonicalize_name(lhs.value)
|
|
||||||
lhs = Value(normalized_extra)
|
|
||||||
return lhs, op, rhs
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_extra_values(results: MarkerList) -> MarkerList:
|
|
||||||
"""
|
"""
|
||||||
Normalize extra values.
|
Normalize extra values.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(results[0], tuple):
|
||||||
return [_normalize_extras(r) for r in results]
|
lhs, op, rhs = results[0]
|
||||||
|
if isinstance(lhs, Variable) and lhs.value == "extra":
|
||||||
|
normalized_extra = canonicalize_name(rhs.value)
|
||||||
|
rhs = Value(normalized_extra)
|
||||||
|
elif isinstance(rhs, Variable) and rhs.value == "extra":
|
||||||
|
normalized_extra = canonicalize_name(lhs.value)
|
||||||
|
lhs = Value(normalized_extra)
|
||||||
|
results[0] = lhs, op, rhs
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
def _format_marker(
|
def _format_marker(
|
||||||
@@ -204,26 +168,25 @@ def _format_marker(
|
|||||||
_operators: dict[str, Operator] = {
|
_operators: dict[str, Operator] = {
|
||||||
"in": lambda lhs, rhs: lhs in rhs,
|
"in": lambda lhs, rhs: lhs in rhs,
|
||||||
"not in": lambda lhs, rhs: lhs not in rhs,
|
"not in": lambda lhs, rhs: lhs not in rhs,
|
||||||
"<": lambda _lhs, _rhs: False,
|
"<": operator.lt,
|
||||||
"<=": operator.eq,
|
"<=": operator.le,
|
||||||
"==": operator.eq,
|
"==": operator.eq,
|
||||||
"!=": operator.ne,
|
"!=": operator.ne,
|
||||||
">=": operator.eq,
|
">=": operator.ge,
|
||||||
">": lambda _lhs, _rhs: False,
|
">": operator.gt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool:
|
def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str]) -> bool:
|
||||||
op_str = op.serialize()
|
if isinstance(rhs, str):
|
||||||
if key in MARKERS_REQUIRING_VERSION:
|
|
||||||
try:
|
try:
|
||||||
spec = Specifier(f"{op_str}{rhs}")
|
spec = Specifier("".join([op.serialize(), rhs]))
|
||||||
except InvalidSpecifier:
|
except InvalidSpecifier:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return spec.contains(lhs, prereleases=True)
|
return spec.contains(lhs, prereleases=True)
|
||||||
|
|
||||||
oper: Operator | None = _operators.get(op_str)
|
oper: Operator | None = _operators.get(op.serialize())
|
||||||
if oper is None:
|
if oper is None:
|
||||||
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
|
raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
|
||||||
|
|
||||||
@@ -233,14 +196,13 @@ def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool
|
|||||||
def _normalize(
|
def _normalize(
|
||||||
lhs: str, rhs: str | AbstractSet[str], key: str
|
lhs: str, rhs: str | AbstractSet[str], key: str
|
||||||
) -> tuple[str, str | AbstractSet[str]]:
|
) -> tuple[str, str | AbstractSet[str]]:
|
||||||
# PEP 685 - Comparison of extra names for optional distribution dependencies
|
# PEP 685 – Comparison of extra names for optional distribution dependencies
|
||||||
# https://peps.python.org/pep-0685/
|
# https://peps.python.org/pep-0685/
|
||||||
# > When comparing extra names, tools MUST normalize the names being
|
# > When comparing extra names, tools MUST normalize the names being
|
||||||
# > compared using the semantics outlined in PEP 503 for names
|
# > compared using the semantics outlined in PEP 503 for names
|
||||||
if key == "extra":
|
if key == "extra":
|
||||||
assert isinstance(rhs, str), "extra value must be a string"
|
assert isinstance(rhs, str), "extra value must be a string"
|
||||||
# Both sides are normalized at this point already
|
return (canonicalize_name(lhs), canonicalize_name(rhs))
|
||||||
return (lhs, rhs)
|
|
||||||
if key in MARKERS_ALLOWING_SET:
|
if key in MARKERS_ALLOWING_SET:
|
||||||
if isinstance(rhs, str): # pragma: no cover
|
if isinstance(rhs, str): # pragma: no cover
|
||||||
return (canonicalize_name(lhs), canonicalize_name(rhs))
|
return (canonicalize_name(lhs), canonicalize_name(rhs))
|
||||||
@@ -257,6 +219,8 @@ def _evaluate_markers(
|
|||||||
groups: list[list[bool]] = [[]]
|
groups: list[list[bool]] = [[]]
|
||||||
|
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
|
assert isinstance(marker, (list, tuple, str))
|
||||||
|
|
||||||
if isinstance(marker, list):
|
if isinstance(marker, list):
|
||||||
groups[-1].append(_evaluate_markers(marker, environment))
|
groups[-1].append(_evaluate_markers(marker, environment))
|
||||||
elif isinstance(marker, tuple):
|
elif isinstance(marker, tuple):
|
||||||
@@ -270,21 +234,18 @@ def _evaluate_markers(
|
|||||||
lhs_value = lhs.value
|
lhs_value = lhs.value
|
||||||
environment_key = rhs.value
|
environment_key = rhs.value
|
||||||
rhs_value = environment[environment_key]
|
rhs_value = environment[environment_key]
|
||||||
|
|
||||||
assert isinstance(lhs_value, str), "lhs must be a string"
|
assert isinstance(lhs_value, str), "lhs must be a string"
|
||||||
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
|
lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
|
||||||
groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
|
groups[-1].append(_eval_op(lhs_value, op, rhs_value))
|
||||||
elif marker == "or":
|
else:
|
||||||
groups.append([])
|
assert marker in ["and", "or"]
|
||||||
elif marker == "and":
|
if marker == "or":
|
||||||
pass
|
groups.append([])
|
||||||
else: # pragma: nocover
|
|
||||||
raise TypeError(f"Unexpected marker {marker!r}")
|
|
||||||
|
|
||||||
return any(all(item) for item in groups)
|
return any(all(item) for item in groups)
|
||||||
|
|
||||||
|
|
||||||
def _format_full_version(info: sys._version_info) -> str:
|
def format_full_version(info: sys._version_info) -> str:
|
||||||
version = f"{info.major}.{info.minor}.{info.micro}"
|
version = f"{info.major}.{info.minor}.{info.micro}"
|
||||||
kind = info.releaselevel
|
kind = info.releaselevel
|
||||||
if kind != "final":
|
if kind != "final":
|
||||||
@@ -293,11 +254,7 @@ def _format_full_version(info: sys._version_info) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def default_environment() -> Environment:
|
def default_environment() -> Environment:
|
||||||
"""Return the default marker environment for the current Python process.
|
iver = format_full_version(sys.implementation.version)
|
||||||
|
|
||||||
This is the base environment used by :meth:`Marker.evaluate`.
|
|
||||||
"""
|
|
||||||
iver = _format_full_version(sys.implementation.version)
|
|
||||||
implementation_name = sys.implementation.name
|
implementation_name = sys.implementation.name
|
||||||
return {
|
return {
|
||||||
"implementation_name": implementation_name,
|
"implementation_name": implementation_name,
|
||||||
@@ -315,36 +272,10 @@ def default_environment() -> Environment:
|
|||||||
|
|
||||||
|
|
||||||
class Marker:
|
class Marker:
|
||||||
"""Represents a parsed dependency marker expression.
|
|
||||||
|
|
||||||
Marker expressions are parsed according to the
|
|
||||||
:ref:`specification of dependency specifiers <pypug:dependency-specifiers>`.
|
|
||||||
|
|
||||||
:param marker: The string representation of a marker expression.
|
|
||||||
:raises InvalidMarker: If ``marker`` cannot be parsed.
|
|
||||||
|
|
||||||
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
|
||||||
format so the same pickle can be loaded in future packaging releases.
|
|
||||||
|
|
||||||
.. versionchanged:: 26.2
|
|
||||||
|
|
||||||
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
|
||||||
be unpickled with future releases. Backward compatibility with pickles
|
|
||||||
from packaging < 26.2 is supported but may be removed in a future
|
|
||||||
release.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ("_markers",)
|
|
||||||
|
|
||||||
def __init__(self, marker: str) -> None:
|
def __init__(self, marker: str) -> None:
|
||||||
# Note: We create a Marker object without calling this constructor in
|
# Note: We create a Marker object without calling this constructor in
|
||||||
# packaging.requirements.Requirement. If any additional logic is
|
# packaging.requirements.Requirement. If any additional logic is
|
||||||
# added here, make sure to mirror/adapt Requirement.
|
# added here, make sure to mirror/adapt Requirement.
|
||||||
|
|
||||||
# If this fails and throws an error, the repr still expects _markers to
|
|
||||||
# be defined.
|
|
||||||
self._markers: MarkerList = []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._markers = _normalize_extra_values(_parse_marker(marker))
|
self._markers = _normalize_extra_values(_parse_marker(marker))
|
||||||
# The attribute `_markers` can be described in terms of a recursive type:
|
# The attribute `_markers` can be described in terms of a recursive type:
|
||||||
@@ -366,94 +297,36 @@ class Marker:
|
|||||||
except ParserSyntaxError as e:
|
except ParserSyntaxError as e:
|
||||||
raise InvalidMarker(str(e)) from e
|
raise InvalidMarker(str(e)) from e
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_markers(cls, markers: MarkerList) -> Marker:
|
|
||||||
"""Create a Marker instance from a pre-parsed marker tree.
|
|
||||||
|
|
||||||
This avoids re-parsing serialised marker strings when combining markers.
|
|
||||||
"""
|
|
||||||
new = cls.__new__(cls)
|
|
||||||
new._markers = markers
|
|
||||||
return new
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return _format_marker(self._markers)
|
return _format_marker(self._markers)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.__class__.__name__}({str(self)!r})>"
|
return f"<Marker('{self}')>"
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(str(self))
|
return hash((self.__class__.__name__, str(self)))
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: Any) -> bool:
|
||||||
if not isinstance(other, Marker):
|
if not isinstance(other, Marker):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
return str(self) == str(other)
|
return str(self) == str(other)
|
||||||
|
|
||||||
def __getstate__(self) -> str:
|
|
||||||
# Return the marker expression string for compactness and stability.
|
|
||||||
# Internal Node objects are excluded; the string is re-parsed on load.
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
def __setstate__(self, state: object) -> None:
|
|
||||||
if isinstance(state, str):
|
|
||||||
# New format (26.2+): just the marker expression string.
|
|
||||||
try:
|
|
||||||
self._markers = _normalize_extra_values(_parse_marker(state))
|
|
||||||
except ParserSyntaxError as exc:
|
|
||||||
raise TypeError(f"Cannot restore Marker from {state!r}") from exc
|
|
||||||
return
|
|
||||||
if isinstance(state, dict) and "_markers" in state:
|
|
||||||
# Old format (packaging <= 26.1, no __slots__): plain __dict__.
|
|
||||||
markers = state["_markers"]
|
|
||||||
if isinstance(markers, list):
|
|
||||||
self._markers = markers
|
|
||||||
return
|
|
||||||
if isinstance(state, tuple) and len(state) == 2:
|
|
||||||
# Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
|
|
||||||
_, slot_dict = state
|
|
||||||
if isinstance(slot_dict, dict) and "_markers" in slot_dict:
|
|
||||||
markers = slot_dict["_markers"]
|
|
||||||
if isinstance(markers, list):
|
|
||||||
self._markers = markers
|
|
||||||
return
|
|
||||||
raise TypeError(f"Cannot restore Marker from {state!r}")
|
|
||||||
|
|
||||||
def __and__(self, other: Marker) -> Marker:
|
|
||||||
if not isinstance(other, Marker):
|
|
||||||
return NotImplemented
|
|
||||||
return self._from_markers([self._markers, "and", other._markers])
|
|
||||||
|
|
||||||
def __or__(self, other: Marker) -> Marker:
|
|
||||||
if not isinstance(other, Marker):
|
|
||||||
return NotImplemented
|
|
||||||
return self._from_markers([self._markers, "or", other._markers])
|
|
||||||
|
|
||||||
def evaluate(
|
def evaluate(
|
||||||
self,
|
self,
|
||||||
environment: Mapping[str, str | AbstractSet[str]] | None = None,
|
environment: dict[str, str] | None = None,
|
||||||
context: EvaluateContext = "metadata",
|
context: EvaluateContext = "metadata",
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Evaluate a marker.
|
"""Evaluate a marker.
|
||||||
|
|
||||||
Return the boolean from evaluating this marker against the environment.
|
Return the boolean from evaluating the given marker against the
|
||||||
The environment is determined from the current Python process unless
|
environment. environment is an optional argument to override all or
|
||||||
passed in explicitly.
|
part of the determined environment. The *context* parameter specifies what
|
||||||
|
context the markers are being evaluated for, which influences what markers
|
||||||
:param environment: Mapping containing keys and values to override the
|
are considered valid. Acceptable values are "metadata" (for core metadata;
|
||||||
detected environment.
|
default), "lock_file", and "requirement" (i.e. all other situations).
|
||||||
:param EvaluateContext context: The context in which the marker is
|
|
||||||
evaluated, which influences what marker names are considered valid.
|
|
||||||
Accepted values are ``"metadata"`` (for core metadata; default),
|
|
||||||
``"lock_file"``, and ``"requirement"`` (i.e. all other situations).
|
|
||||||
:raises UndefinedComparison: If the marker uses a comparison on values
|
|
||||||
that are not valid versions per the :ref:`specification of version
|
|
||||||
specifiers <pypug:version-specifiers>`.
|
|
||||||
:raises UndefinedEnvironmentName: If the marker references a value that
|
|
||||||
is missing from the evaluation environment.
|
|
||||||
:returns: ``True`` if the marker matches, otherwise ``False``.
|
|
||||||
|
|
||||||
|
The environment is determined from the current Python process.
|
||||||
"""
|
"""
|
||||||
current_environment = cast(
|
current_environment = cast(
|
||||||
"dict[str, str | AbstractSet[str]]", default_environment()
|
"dict[str, str | AbstractSet[str]]", default_environment()
|
||||||
@@ -464,15 +337,12 @@ class Marker:
|
|||||||
)
|
)
|
||||||
elif context == "metadata":
|
elif context == "metadata":
|
||||||
current_environment["extra"] = ""
|
current_environment["extra"] = ""
|
||||||
|
|
||||||
if environment is not None:
|
if environment is not None:
|
||||||
current_environment.update(environment)
|
current_environment.update(environment)
|
||||||
if "extra" in current_environment:
|
# The API used to allow setting extra to None. We need to handle this
|
||||||
# The API used to allow setting extra to None. We need to handle
|
# case for backwards compatibility.
|
||||||
# this case for backwards compatibility. Also skip running
|
if "extra" in current_environment and current_environment["extra"] is None:
|
||||||
# normalize name if extra is empty.
|
current_environment["extra"] = ""
|
||||||
extra = cast("str | None", current_environment["extra"])
|
|
||||||
current_environment["extra"] = canonicalize_name(extra) if extra else ""
|
|
||||||
|
|
||||||
return _evaluate_markers(
|
return _evaluate_markers(
|
||||||
self._markers, _repair_python_full_version(current_environment)
|
self._markers, _repair_python_full_version(current_environment)
|
||||||
@@ -486,7 +356,7 @@ def _repair_python_full_version(
|
|||||||
Work around platform.python_version() returning something that is not PEP 440
|
Work around platform.python_version() returning something that is not PEP 440
|
||||||
compliant for non-tagged Python builds.
|
compliant for non-tagged Python builds.
|
||||||
"""
|
"""
|
||||||
python_full_version = cast("str", env["python_full_version"])
|
python_full_version = cast(str, env["python_full_version"])
|
||||||
if python_full_version.endswith("+"):
|
if python_full_version.endswith("+"):
|
||||||
env["python_full_version"] = f"{python_full_version}local"
|
env["python_full_version"] = f"{python_full_version}local"
|
||||||
return env
|
return env
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import email.feedparser
|
||||||
import email.header
|
import email.header
|
||||||
import email.message
|
import email.message
|
||||||
import email.parser
|
import email.parser
|
||||||
import email.policy
|
import email.policy
|
||||||
import keyword
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
import sys
|
||||||
import typing
|
import typing
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
@@ -18,27 +19,31 @@ from typing import (
|
|||||||
|
|
||||||
from . import licenses, requirements, specifiers, utils
|
from . import licenses, requirements, specifiers, utils
|
||||||
from . import version as version_module
|
from . import version as version_module
|
||||||
from .errors import ExceptionGroup, _ErrorCollector
|
from .licenses import NormalizedLicenseExpression
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
|
||||||
from .licenses import NormalizedLicenseExpression
|
|
||||||
|
|
||||||
T = typing.TypeVar("T")
|
T = typing.TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
if sys.version_info >= (3, 11): # pragma: no cover
|
||||||
"ExceptionGroup", # Keep this for a bit (makes mypy happy w/ 26.0 compat)
|
ExceptionGroup = ExceptionGroup
|
||||||
"InvalidMetadata",
|
else: # pragma: no cover
|
||||||
"Metadata",
|
|
||||||
"RFC822Message",
|
|
||||||
"RFC822Policy",
|
|
||||||
"RawMetadata",
|
|
||||||
"parse_email",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
class ExceptionGroup(Exception):
|
||||||
|
"""A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
If :external:exc:`ExceptionGroup` is already defined by Python itself,
|
||||||
return __all__
|
that version is used instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
exceptions: list[Exception]
|
||||||
|
|
||||||
|
def __init__(self, message: str, exceptions: list[Exception]) -> None:
|
||||||
|
self.message = message
|
||||||
|
self.exceptions = exceptions
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
|
||||||
|
|
||||||
|
|
||||||
class InvalidMetadata(ValueError):
|
class InvalidMetadata(ValueError):
|
||||||
@@ -68,8 +73,8 @@ class RawMetadata(TypedDict, total=False):
|
|||||||
|
|
||||||
Core metadata fields that can be specified multiple times are stored as a
|
Core metadata fields that can be specified multiple times are stored as a
|
||||||
list or dict depending on which is appropriate for the field. Any fields
|
list or dict depending on which is appropriate for the field. Any fields
|
||||||
which hold multiple values in a single field are stored as a list. All fields
|
which hold multiple values in a single field are stored as a list.
|
||||||
are considered optional.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Metadata 1.0 - PEP 241
|
# Metadata 1.0 - PEP 241
|
||||||
@@ -121,19 +126,13 @@ class RawMetadata(TypedDict, total=False):
|
|||||||
|
|
||||||
# Metadata 2.3 - PEP 685
|
# Metadata 2.3 - PEP 685
|
||||||
# No new fields were added in PEP 685, just some edge case were
|
# No new fields were added in PEP 685, just some edge case were
|
||||||
# tightened up to provide better interoperability.
|
# tightened up to provide better interoptability.
|
||||||
|
|
||||||
# Metadata 2.4 - PEP 639
|
# Metadata 2.4 - PEP 639
|
||||||
license_expression: str
|
license_expression: str
|
||||||
license_files: list[str]
|
license_files: list[str]
|
||||||
|
|
||||||
# Metadata 2.5 - PEP 794
|
|
||||||
import_names: list[str]
|
|
||||||
import_namespaces: list[str]
|
|
||||||
|
|
||||||
|
|
||||||
# 'keywords' is special as it's a string in the core metadata spec, but we
|
|
||||||
# represent it as a list.
|
|
||||||
_STRING_FIELDS = {
|
_STRING_FIELDS = {
|
||||||
"author",
|
"author",
|
||||||
"author_email",
|
"author_email",
|
||||||
@@ -166,8 +165,6 @@ _LIST_FIELDS = {
|
|||||||
"requires_dist",
|
"requires_dist",
|
||||||
"requires_external",
|
"requires_external",
|
||||||
"supported_platforms",
|
"supported_platforms",
|
||||||
"import_names",
|
|
||||||
"import_namespaces",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_DICT_FIELDS = {
|
_DICT_FIELDS = {
|
||||||
@@ -196,23 +193,24 @@ def _parse_project_urls(data: list[str]) -> dict[str, str]:
|
|||||||
# be the missing value, then they'd have multiple '' values that
|
# be the missing value, then they'd have multiple '' values that
|
||||||
# overwrite each other in a accumulating dict.
|
# overwrite each other in a accumulating dict.
|
||||||
#
|
#
|
||||||
# The other potential issue is that it's possible to have the
|
# The other potentional issue is that it's possible to have the
|
||||||
# same label multiple times in the metadata, with no solid "right"
|
# same label multiple times in the metadata, with no solid "right"
|
||||||
# answer with what to do in that case. As such, we'll do the only
|
# answer with what to do in that case. As such, we'll do the only
|
||||||
# thing we can, which is treat the field as unparsable and add it
|
# thing we can, which is treat the field as unparseable and add it
|
||||||
# to our list of unparsed fields.
|
# to our list of unparsed fields.
|
||||||
#
|
parts = [p.strip() for p in pair.split(",", 1)]
|
||||||
|
parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items
|
||||||
|
|
||||||
# TODO: The spec doesn't say anything about if the keys should be
|
# TODO: The spec doesn't say anything about if the keys should be
|
||||||
# considered case sensitive or not... logically they should
|
# considered case sensitive or not... logically they should
|
||||||
# be case-preserving and case-insensitive, but doing that
|
# be case-preserving and case-insensitive, but doing that
|
||||||
# would open up more cases where we might have duplicate
|
# would open up more cases where we might have duplicate
|
||||||
# entries.
|
# entries.
|
||||||
label, _, url = (s.strip() for s in pair.partition(","))
|
label, url = parts
|
||||||
|
|
||||||
if label in urls:
|
if label in urls:
|
||||||
# The label already exists in our set of urls, so this field
|
# The label already exists in our set of urls, so this field
|
||||||
# is unparsable, and we can just add the whole thing to our
|
# is unparseable, and we can just add the whole thing to our
|
||||||
# unparsable data and stop processing it.
|
# unparseable data and stop processing it.
|
||||||
raise KeyError("duplicate labels in project urls")
|
raise KeyError("duplicate labels in project urls")
|
||||||
urls[label] = url
|
urls[label] = url
|
||||||
|
|
||||||
@@ -259,8 +257,6 @@ _EMAIL_TO_RAW_MAPPING = {
|
|||||||
"download-url": "download_url",
|
"download-url": "download_url",
|
||||||
"dynamic": "dynamic",
|
"dynamic": "dynamic",
|
||||||
"home-page": "home_page",
|
"home-page": "home_page",
|
||||||
"import-name": "import_names",
|
|
||||||
"import-namespace": "import_namespaces",
|
|
||||||
"keywords": "keywords",
|
"keywords": "keywords",
|
||||||
"license": "license",
|
"license": "license",
|
||||||
"license-expression": "license_expression",
|
"license-expression": "license_expression",
|
||||||
@@ -287,45 +283,6 @@ _EMAIL_TO_RAW_MAPPING = {
|
|||||||
_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
|
_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
|
||||||
|
|
||||||
|
|
||||||
# This class is for writing RFC822 messages
|
|
||||||
class RFC822Policy(email.policy.EmailPolicy):
|
|
||||||
"""
|
|
||||||
This is :class:`email.policy.EmailPolicy`, but with a simple ``header_store_parse``
|
|
||||||
implementation that handles multi-line values, and some nice defaults.
|
|
||||||
"""
|
|
||||||
|
|
||||||
utf8 = True
|
|
||||||
mangle_from_ = False
|
|
||||||
max_line_length = 0
|
|
||||||
|
|
||||||
def header_store_parse(self, name: str, value: str) -> tuple[str, str]:
|
|
||||||
size = len(name) + 2
|
|
||||||
value = value.replace("\n", "\n" + " " * size)
|
|
||||||
return (name, value)
|
|
||||||
|
|
||||||
|
|
||||||
# This class is for writing RFC822 messages
|
|
||||||
class RFC822Message(email.message.EmailMessage):
|
|
||||||
"""
|
|
||||||
This is :class:`email.message.EmailMessage` with two small changes: it defaults to
|
|
||||||
our `RFC822Policy`, and it correctly writes unicode when being called
|
|
||||||
with `bytes()`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__(policy=RFC822Policy())
|
|
||||||
|
|
||||||
def as_bytes(
|
|
||||||
self, unixfrom: bool = False, policy: email.policy.Policy | None = None
|
|
||||||
) -> bytes:
|
|
||||||
"""
|
|
||||||
Return the bytes representation of the message.
|
|
||||||
|
|
||||||
This handles unicode encoding.
|
|
||||||
"""
|
|
||||||
return self.as_string(unixfrom, policy=policy).encode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
||||||
"""Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
|
"""Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
|
||||||
|
|
||||||
@@ -353,10 +310,10 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
|||||||
# We have to wrap parsed.keys() in a set, because in the case of multiple
|
# We have to wrap parsed.keys() in a set, because in the case of multiple
|
||||||
# values for a key (a list), the key will appear multiple times in the
|
# values for a key (a list), the key will appear multiple times in the
|
||||||
# list of keys, but we're avoiding that by using get_all().
|
# list of keys, but we're avoiding that by using get_all().
|
||||||
for name_with_case in frozenset(parsed.keys()):
|
for name in frozenset(parsed.keys()):
|
||||||
# Header names in RFC are case insensitive, so we'll normalize to all
|
# Header names in RFC are case insensitive, so we'll normalize to all
|
||||||
# lower case to make comparisons easier.
|
# lower case to make comparisons easier.
|
||||||
name = name_with_case.lower()
|
name = name.lower()
|
||||||
|
|
||||||
# We use get_all() here, even for fields that aren't multiple use,
|
# We use get_all() here, even for fields that aren't multiple use,
|
||||||
# because otherwise someone could have e.g. two Name fields, and we
|
# because otherwise someone could have e.g. two Name fields, and we
|
||||||
@@ -392,16 +349,16 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
|||||||
# can be independently encoded, so we'll need to check each
|
# can be independently encoded, so we'll need to check each
|
||||||
# of them.
|
# of them.
|
||||||
chunks: list[tuple[bytes, str | None]] = []
|
chunks: list[tuple[bytes, str | None]] = []
|
||||||
for binary, _encoding in email.header.decode_header(h):
|
for bin, encoding in email.header.decode_header(h):
|
||||||
try:
|
try:
|
||||||
binary.decode("utf8", "strict")
|
bin.decode("utf8", "strict")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
# Enable mojibake.
|
# Enable mojibake.
|
||||||
encoding = "latin1"
|
encoding = "latin1"
|
||||||
valid_encoding = False
|
valid_encoding = False
|
||||||
else:
|
else:
|
||||||
encoding = "utf8"
|
encoding = "utf8"
|
||||||
chunks.append((binary, encoding))
|
chunks.append((bin, encoding))
|
||||||
|
|
||||||
# Turn our chunks back into a Header object, then let that
|
# Turn our chunks back into a Header object, then let that
|
||||||
# Header object do the right thing to turn them into a
|
# Header object do the right thing to turn them into a
|
||||||
@@ -440,11 +397,6 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
|||||||
# of unparsed stuff.
|
# of unparsed stuff.
|
||||||
if raw_name in _STRING_FIELDS and len(value) == 1:
|
if raw_name in _STRING_FIELDS and len(value) == 1:
|
||||||
raw[raw_name] = value[0]
|
raw[raw_name] = value[0]
|
||||||
# If this is import_names, we need to special case the empty field
|
|
||||||
# case, which converts to an empty list instead of None. We can't let
|
|
||||||
# the empty case slip through, as it will fail validation.
|
|
||||||
elif raw_name == "import_names" and value == [""]:
|
|
||||||
raw[raw_name] = []
|
|
||||||
# If this is one of our list of string fields, then we can just assign
|
# If this is one of our list of string fields, then we can just assign
|
||||||
# the value, since email *only* has strings, and our get_all() call
|
# the value, since email *only* has strings, and our get_all() call
|
||||||
# above ensures that this is a list.
|
# above ensures that this is a list.
|
||||||
@@ -472,7 +424,7 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
unparsed[name] = value
|
unparsed[name] = value
|
||||||
# Nothing that we've done has managed to parse this, so it'll just
|
# Nothing that we've done has managed to parse this, so it'll just
|
||||||
# throw it in our unparsable data and move on.
|
# throw it in our unparseable data and move on.
|
||||||
else:
|
else:
|
||||||
unparsed[name] = value
|
unparsed[name] = value
|
||||||
|
|
||||||
@@ -489,9 +441,9 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
|||||||
else:
|
else:
|
||||||
if payload:
|
if payload:
|
||||||
# Check to see if we've already got a description, if so then both
|
# Check to see if we've already got a description, if so then both
|
||||||
# it, and this body move to unparsable.
|
# it, and this body move to unparseable.
|
||||||
if "description" in raw:
|
if "description" in raw:
|
||||||
description_header = cast("str", raw.pop("description"))
|
description_header = cast(str, raw.pop("description"))
|
||||||
unparsed.setdefault("description", []).extend(
|
unparsed.setdefault("description", []).extend(
|
||||||
[description_header, payload]
|
[description_header, payload]
|
||||||
)
|
)
|
||||||
@@ -504,15 +456,15 @@ def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
|
|||||||
# literal key names, but we're computing our key names on purpose, but the
|
# literal key names, but we're computing our key names on purpose, but the
|
||||||
# way this function is implemented, our `TypedDict` can only have valid key
|
# way this function is implemented, our `TypedDict` can only have valid key
|
||||||
# names.
|
# names.
|
||||||
return cast("RawMetadata", raw), unparsed
|
return cast(RawMetadata, raw), unparsed
|
||||||
|
|
||||||
|
|
||||||
_NOT_FOUND = object()
|
_NOT_FOUND = object()
|
||||||
|
|
||||||
|
|
||||||
# Keep the two values in sync.
|
# Keep the two values in sync.
|
||||||
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
|
_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"]
|
||||||
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
|
_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"]
|
||||||
|
|
||||||
_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
|
_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
|
||||||
|
|
||||||
@@ -567,7 +519,7 @@ class _Validator(Generic[T]):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return cast("T", value)
|
return cast(T, value)
|
||||||
|
|
||||||
def _invalid_metadata(
|
def _invalid_metadata(
|
||||||
self, msg: str, cause: Exception | None = None
|
self, msg: str, cause: Exception | None = None
|
||||||
@@ -582,7 +534,7 @@ class _Validator(Generic[T]):
|
|||||||
# Implicitly makes Metadata-Version required.
|
# Implicitly makes Metadata-Version required.
|
||||||
if value not in _VALID_METADATA_VERSIONS:
|
if value not in _VALID_METADATA_VERSIONS:
|
||||||
raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
|
raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
|
||||||
return cast("_MetadataVersion", value)
|
return cast(_MetadataVersion, value)
|
||||||
|
|
||||||
def _process_name(self, value: str) -> str:
|
def _process_name(self, value: str) -> str:
|
||||||
if not value:
|
if not value:
|
||||||
@@ -633,7 +585,7 @@ class _Validator(Generic[T]):
|
|||||||
charset = parameters.get("charset", "UTF-8")
|
charset = parameters.get("charset", "UTF-8")
|
||||||
if charset != "UTF-8":
|
if charset != "UTF-8":
|
||||||
raise self._invalid_metadata(
|
raise self._invalid_metadata(
|
||||||
f"{{field}} can only specify the UTF-8 charset, not {charset!r}"
|
f"{{field}} can only specify the UTF-8 charset, not {list(charset)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
markdown_variants = {"GFM", "CommonMark"}
|
markdown_variants = {"GFM", "CommonMark"}
|
||||||
@@ -695,7 +647,9 @@ class _Validator(Generic[T]):
|
|||||||
else:
|
else:
|
||||||
return reqs
|
return reqs
|
||||||
|
|
||||||
def _process_license_expression(self, value: str) -> NormalizedLicenseExpression:
|
def _process_license_expression(
|
||||||
|
self, value: str
|
||||||
|
) -> NormalizedLicenseExpression | None:
|
||||||
try:
|
try:
|
||||||
return licenses.canonicalize_license_expression(value)
|
return licenses.canonicalize_license_expression(value)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
@@ -729,30 +683,6 @@ class _Validator(Generic[T]):
|
|||||||
paths.append(path)
|
paths.append(path)
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def _process_import_names(self, value: list[str]) -> list[str]:
|
|
||||||
for import_name in value:
|
|
||||||
name, semicolon, private = import_name.partition(";")
|
|
||||||
name = name.rstrip()
|
|
||||||
for identifier in name.split("."):
|
|
||||||
if not identifier.isidentifier():
|
|
||||||
raise self._invalid_metadata(
|
|
||||||
f"{name!r} is invalid for {{field}}; "
|
|
||||||
f"{identifier!r} is not a valid identifier"
|
|
||||||
)
|
|
||||||
elif keyword.iskeyword(identifier):
|
|
||||||
raise self._invalid_metadata(
|
|
||||||
f"{name!r} is invalid for {{field}}; "
|
|
||||||
f"{identifier!r} is a keyword"
|
|
||||||
)
|
|
||||||
if semicolon and private.lstrip() != "private":
|
|
||||||
raise self._invalid_metadata(
|
|
||||||
f"{import_name!r} is invalid for {{field}}; "
|
|
||||||
"the only valid option is 'private'"
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
_process_import_namespaces = _process_import_names
|
|
||||||
|
|
||||||
|
|
||||||
class Metadata:
|
class Metadata:
|
||||||
"""Representation of distribution metadata.
|
"""Representation of distribution metadata.
|
||||||
@@ -776,11 +706,13 @@ class Metadata:
|
|||||||
ins._raw = data.copy() # Mutations occur due to caching enriched values.
|
ins._raw = data.copy() # Mutations occur due to caching enriched values.
|
||||||
|
|
||||||
if validate:
|
if validate:
|
||||||
collector = _ErrorCollector()
|
exceptions: list[Exception] = []
|
||||||
metadata_version = None
|
try:
|
||||||
with collector.collect(InvalidMetadata):
|
|
||||||
metadata_version = ins.metadata_version
|
metadata_version = ins.metadata_version
|
||||||
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
|
metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
|
||||||
|
except InvalidMetadata as metadata_version_exc:
|
||||||
|
exceptions.append(metadata_version_exc)
|
||||||
|
metadata_version = None
|
||||||
|
|
||||||
# Make sure to check for the fields that are present, the required
|
# Make sure to check for the fields that are present, the required
|
||||||
# fields (so their absence can be reported).
|
# fields (so their absence can be reported).
|
||||||
@@ -797,7 +729,7 @@ class Metadata:
|
|||||||
field_metadata_version = cls.__dict__[key].added
|
field_metadata_version = cls.__dict__[key].added
|
||||||
except KeyError:
|
except KeyError:
|
||||||
exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
|
exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
|
||||||
collector.error(exc)
|
exceptions.append(exc)
|
||||||
continue
|
continue
|
||||||
field_age = _VALID_METADATA_VERSIONS.index(
|
field_age = _VALID_METADATA_VERSIONS.index(
|
||||||
field_metadata_version
|
field_metadata_version
|
||||||
@@ -809,13 +741,14 @@ class Metadata:
|
|||||||
f"{field} introduced in metadata version "
|
f"{field} introduced in metadata version "
|
||||||
f"{field_metadata_version}, not {metadata_version}",
|
f"{field_metadata_version}, not {metadata_version}",
|
||||||
)
|
)
|
||||||
collector.error(exc)
|
exceptions.append(exc)
|
||||||
continue
|
continue
|
||||||
getattr(ins, key)
|
getattr(ins, key)
|
||||||
except InvalidMetadata as exc:
|
except InvalidMetadata as exc:
|
||||||
collector.error(exc)
|
exceptions.append(exc)
|
||||||
|
|
||||||
collector.finalize("invalid metadata")
|
if exceptions:
|
||||||
|
raise ExceptionGroup("invalid metadata", exceptions)
|
||||||
|
|
||||||
return ins
|
return ins
|
||||||
|
|
||||||
@@ -829,13 +762,16 @@ class Metadata:
|
|||||||
raw, unparsed = parse_email(data)
|
raw, unparsed = parse_email(data)
|
||||||
|
|
||||||
if validate:
|
if validate:
|
||||||
with _ErrorCollector().on_exit("unparsed") as collector:
|
exceptions: list[Exception] = []
|
||||||
for unparsed_key in unparsed:
|
for unparsed_key in unparsed:
|
||||||
if unparsed_key in _EMAIL_TO_RAW_MAPPING:
|
if unparsed_key in _EMAIL_TO_RAW_MAPPING:
|
||||||
message = f"{unparsed_key!r} has invalid data"
|
message = f"{unparsed_key!r} has invalid data"
|
||||||
else:
|
else:
|
||||||
message = f"unrecognized field: {unparsed_key!r}"
|
message = f"unrecognized field: {unparsed_key!r}"
|
||||||
collector.error(InvalidMetadata(unparsed_key, message))
|
exceptions.append(InvalidMetadata(unparsed_key, message))
|
||||||
|
|
||||||
|
if exceptions:
|
||||||
|
raise ExceptionGroup("unparsed", exceptions)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return cls.from_raw(raw, validate=validate)
|
return cls.from_raw(raw, validate=validate)
|
||||||
@@ -918,47 +854,9 @@ class Metadata:
|
|||||||
""":external:ref:`core-metadata-provides-dist`"""
|
""":external:ref:`core-metadata-provides-dist`"""
|
||||||
obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
|
obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
|
||||||
""":external:ref:`core-metadata-obsoletes-dist`"""
|
""":external:ref:`core-metadata-obsoletes-dist`"""
|
||||||
import_names: _Validator[list[str] | None] = _Validator(added="2.5")
|
|
||||||
""":external:ref:`core-metadata-import-name`"""
|
|
||||||
import_namespaces: _Validator[list[str] | None] = _Validator(added="2.5")
|
|
||||||
""":external:ref:`core-metadata-import-namespace`"""
|
|
||||||
requires: _Validator[list[str] | None] = _Validator(added="1.1")
|
requires: _Validator[list[str] | None] = _Validator(added="1.1")
|
||||||
"""``Requires`` (deprecated)"""
|
"""``Requires`` (deprecated)"""
|
||||||
provides: _Validator[list[str] | None] = _Validator(added="1.1")
|
provides: _Validator[list[str] | None] = _Validator(added="1.1")
|
||||||
"""``Provides`` (deprecated)"""
|
"""``Provides`` (deprecated)"""
|
||||||
obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
|
obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
|
||||||
"""``Obsoletes`` (deprecated)"""
|
"""``Obsoletes`` (deprecated)"""
|
||||||
|
|
||||||
def as_rfc822(self) -> RFC822Message:
|
|
||||||
"""
|
|
||||||
Return an RFC822 message with the metadata.
|
|
||||||
"""
|
|
||||||
message = RFC822Message()
|
|
||||||
self._write_metadata(message)
|
|
||||||
return message
|
|
||||||
|
|
||||||
def _write_metadata(self, message: RFC822Message) -> None:
|
|
||||||
"""
|
|
||||||
Return an RFC822 message with the metadata.
|
|
||||||
"""
|
|
||||||
for name, validator in self.__class__.__dict__.items():
|
|
||||||
if isinstance(validator, _Validator) and name != "description":
|
|
||||||
value = getattr(self, name)
|
|
||||||
email_name = _RAW_TO_EMAIL_MAPPING[name]
|
|
||||||
if value is not None:
|
|
||||||
if email_name == "project-url":
|
|
||||||
for label, url in value.items():
|
|
||||||
message[email_name] = f"{label}, {url}"
|
|
||||||
elif email_name == "keywords":
|
|
||||||
message[email_name] = ",".join(value)
|
|
||||||
elif email_name == "import-name" and value == []:
|
|
||||||
message[email_name] = ""
|
|
||||||
elif isinstance(value, list):
|
|
||||||
for item in value:
|
|
||||||
message[email_name] = str(item)
|
|
||||||
else:
|
|
||||||
message[email_name] = str(value)
|
|
||||||
|
|
||||||
# The description is a special case because it is in the body of the message.
|
|
||||||
if self.description is not None:
|
|
||||||
message.set_payload(self.description)
|
|
||||||
|
|||||||
@@ -1,905 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import dataclasses
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
from collections.abc import Mapping, Sequence
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import (
|
|
||||||
TYPE_CHECKING,
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Protocol,
|
|
||||||
TypeVar,
|
|
||||||
cast,
|
|
||||||
)
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from .markers import Environment, Marker, default_environment
|
|
||||||
from .specifiers import SpecifierSet
|
|
||||||
from .tags import create_compatible_tags_selector, sys_tags
|
|
||||||
from .utils import (
|
|
||||||
NormalizedName,
|
|
||||||
is_normalized_name,
|
|
||||||
parse_sdist_filename,
|
|
||||||
parse_wheel_filename,
|
|
||||||
)
|
|
||||||
from .version import Version
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
|
||||||
from collections.abc import Collection, Iterator
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from typing_extensions import Self
|
|
||||||
|
|
||||||
from .tags import Tag
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Package",
|
|
||||||
"PackageArchive",
|
|
||||||
"PackageDirectory",
|
|
||||||
"PackageSdist",
|
|
||||||
"PackageVcs",
|
|
||||||
"PackageWheel",
|
|
||||||
"Pylock",
|
|
||||||
"PylockUnsupportedVersionError",
|
|
||||||
"PylockValidationError",
|
|
||||||
"is_valid_pylock_path",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
_T = TypeVar("_T")
|
|
||||||
_T2 = TypeVar("_T2")
|
|
||||||
|
|
||||||
|
|
||||||
class _FromMappingProtocol(Protocol): # pragma: no cover
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
|
|
||||||
|
|
||||||
|
|
||||||
_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
|
|
||||||
|
|
||||||
|
|
||||||
_PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid_pylock_path(path: Path) -> bool:
|
|
||||||
"""Check if the given path is a valid pylock file path."""
|
|
||||||
return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))
|
|
||||||
|
|
||||||
|
|
||||||
def _toml_key(key: str) -> str:
|
|
||||||
return key.replace("_", "-")
|
|
||||||
|
|
||||||
|
|
||||||
def _toml_value(key: str, value: Any) -> Any: # noqa: ANN401
|
|
||||||
if isinstance(value, (Version, Marker, SpecifierSet)):
|
|
||||||
return str(value)
|
|
||||||
if isinstance(value, Sequence) and key == "environments":
|
|
||||||
return [str(v) for v in value]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
||||||
return {
|
|
||||||
_toml_key(key): _toml_value(key, value)
|
|
||||||
for key, value in data
|
|
||||||
if value is not None
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
|
|
||||||
"""Get a value from the dictionary and verify it's the expected type."""
|
|
||||||
if (value := d.get(key)) is None:
|
|
||||||
return None
|
|
||||||
if not isinstance(value, expected_type):
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Unexpected type {type(value).__name__} "
|
|
||||||
f"(expected {expected_type.__name__})",
|
|
||||||
context=key,
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
|
|
||||||
"""Get a required value from the dictionary and verify it's the expected type."""
|
|
||||||
if (value := _get(d, expected_type, key)) is None:
|
|
||||||
raise _PylockRequiredKeyError(key)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_sequence(
|
|
||||||
d: Mapping[str, Any], expected_item_type: type[_T], key: str
|
|
||||||
) -> Sequence[_T] | None:
|
|
||||||
"""Get a list value from the dictionary and verify it's the expected items type."""
|
|
||||||
if (value := _get(d, Sequence, key)) is None: # type: ignore[type-abstract]
|
|
||||||
return None
|
|
||||||
if isinstance(value, (str, bytes)):
|
|
||||||
# special case: str and bytes are Sequences, but we want to reject it
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Unexpected type {type(value).__name__} (expected Sequence)",
|
|
||||||
context=key,
|
|
||||||
)
|
|
||||||
for i, item in enumerate(value):
|
|
||||||
if not isinstance(item, expected_item_type):
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Unexpected type {type(item).__name__} "
|
|
||||||
f"(expected {expected_item_type.__name__})",
|
|
||||||
context=f"{key}[{i}]",
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_as(
|
|
||||||
d: Mapping[str, Any],
|
|
||||||
expected_type: type[_T],
|
|
||||||
target_type: Callable[[_T], _T2],
|
|
||||||
key: str,
|
|
||||||
) -> _T2 | None:
|
|
||||||
"""Get a value from the dictionary, verify it's the expected type,
|
|
||||||
and convert to the target type.
|
|
||||||
|
|
||||||
This assumes the target_type constructor accepts the value.
|
|
||||||
"""
|
|
||||||
if (value := _get(d, expected_type, key)) is None:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return target_type(value)
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(e, context=key) from e
|
|
||||||
|
|
||||||
|
|
||||||
def _get_required_as(
|
|
||||||
d: Mapping[str, Any],
|
|
||||||
expected_type: type[_T],
|
|
||||||
target_type: Callable[[_T], _T2],
|
|
||||||
key: str,
|
|
||||||
) -> _T2:
|
|
||||||
"""Get a required value from the dict, verify it's the expected type,
|
|
||||||
and convert to the target type."""
|
|
||||||
if (value := _get_as(d, expected_type, target_type, key)) is None:
|
|
||||||
raise _PylockRequiredKeyError(key)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def _get_sequence_as(
|
|
||||||
d: Mapping[str, Any],
|
|
||||||
expected_item_type: type[_T],
|
|
||||||
target_item_type: Callable[[_T], _T2],
|
|
||||||
key: str,
|
|
||||||
) -> list[_T2] | None:
|
|
||||||
"""Get list value from dictionary and verify expected items type."""
|
|
||||||
if (value := _get_sequence(d, expected_item_type, key)) is None:
|
|
||||||
return None
|
|
||||||
result = []
|
|
||||||
try:
|
|
||||||
for item in value:
|
|
||||||
typed_item = target_item_type(item)
|
|
||||||
result.append(typed_item)
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _get_object(
|
|
||||||
d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
|
|
||||||
) -> _FromMappingProtocolT | None:
|
|
||||||
"""Get a dictionary value from the dictionary and convert it to a dataclass."""
|
|
||||||
if (value := _get(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
return target_type._from_dict(value)
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(e, context=key) from e
|
|
||||||
|
|
||||||
|
|
||||||
def _get_sequence_of_objects(
|
|
||||||
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
|
||||||
) -> list[_FromMappingProtocolT] | None:
|
|
||||||
"""Get a list value from the dictionary and convert its items to a dataclass."""
|
|
||||||
if (value := _get_sequence(d, Mapping, key)) is None: # type: ignore[type-abstract]
|
|
||||||
return None
|
|
||||||
result: list[_FromMappingProtocolT] = []
|
|
||||||
try:
|
|
||||||
for item in value:
|
|
||||||
typed_item = target_item_type._from_dict(item)
|
|
||||||
result.append(typed_item)
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _get_required_sequence_of_objects(
|
|
||||||
d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
|
|
||||||
) -> Sequence[_FromMappingProtocolT]:
|
|
||||||
"""Get a required list value from the dictionary and convert its items to a
|
|
||||||
dataclass."""
|
|
||||||
if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:
|
|
||||||
raise _PylockRequiredKeyError(key)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_normalized_name(name: str) -> NormalizedName:
|
|
||||||
"""Validate that a string is a NormalizedName."""
|
|
||||||
if not is_normalized_name(name):
|
|
||||||
raise PylockValidationError(f"Name {name!r} is not normalized")
|
|
||||||
return NormalizedName(name)
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_path_url(path: str | None, url: str | None) -> None:
|
|
||||||
if not path and not url:
|
|
||||||
raise PylockValidationError("path or url must be provided")
|
|
||||||
|
|
||||||
|
|
||||||
def _path_name(path: str | None) -> str | None:
|
|
||||||
if not path:
|
|
||||||
return None
|
|
||||||
# If the path is relative it MAY use POSIX-style path separators explicitly
|
|
||||||
# for portability
|
|
||||||
if "/" in path:
|
|
||||||
return path.rsplit("/", 1)[-1]
|
|
||||||
elif "\\" in path:
|
|
||||||
return path.rsplit("\\", 1)[-1]
|
|
||||||
else:
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def _url_name(url: str | None) -> str | None:
|
|
||||||
if not url:
|
|
||||||
return None
|
|
||||||
url_path = urlparse(url).path
|
|
||||||
return url_path.rsplit("/", 1)[-1]
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
||||||
if not hashes:
|
|
||||||
raise PylockValidationError("At least one hash must be provided")
|
|
||||||
if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
|
|
||||||
raise PylockValidationError("Hash values must be strings")
|
|
||||||
return hashes
|
|
||||||
|
|
||||||
|
|
||||||
class PylockValidationError(Exception):
|
|
||||||
"""Raised when when input data is not spec-compliant."""
|
|
||||||
|
|
||||||
context: str | None = None
|
|
||||||
message: str
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
cause: str | Exception,
|
|
||||||
*,
|
|
||||||
context: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
if isinstance(cause, PylockValidationError):
|
|
||||||
if cause.context:
|
|
||||||
self.context = (
|
|
||||||
f"{context}.{cause.context}" if context else cause.context
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.context = context
|
|
||||||
self.message = cause.message
|
|
||||||
else:
|
|
||||||
self.context = context
|
|
||||||
self.message = str(cause)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
if self.context:
|
|
||||||
return f"{self.message} in {self.context!r}"
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class _PylockRequiredKeyError(PylockValidationError):
|
|
||||||
def __init__(self, key: str) -> None:
|
|
||||||
super().__init__("Missing required value", context=key)
|
|
||||||
|
|
||||||
|
|
||||||
class PylockUnsupportedVersionError(PylockValidationError):
|
|
||||||
"""Raised when encountering an unsupported `lock_version`."""
|
|
||||||
|
|
||||||
|
|
||||||
class PylockSelectError(Exception):
|
|
||||||
"""Base exception for errors raised by :meth:`Pylock.select`."""
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class PackageVcs:
|
|
||||||
type: str
|
|
||||||
url: str | None = None
|
|
||||||
path: str | None = None
|
|
||||||
requested_revision: str | None = None
|
|
||||||
commit_id: str # type: ignore[misc]
|
|
||||||
subdirectory: str | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
type: str,
|
|
||||||
url: str | None = None,
|
|
||||||
path: str | None = None,
|
|
||||||
requested_revision: str | None = None,
|
|
||||||
commit_id: str,
|
|
||||||
subdirectory: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "type", type)
|
|
||||||
object.__setattr__(self, "url", url)
|
|
||||||
object.__setattr__(self, "path", path)
|
|
||||||
object.__setattr__(self, "requested_revision", requested_revision)
|
|
||||||
object.__setattr__(self, "commit_id", commit_id)
|
|
||||||
object.__setattr__(self, "subdirectory", subdirectory)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
package_vcs = cls(
|
|
||||||
type=_get_required(d, str, "type"),
|
|
||||||
url=_get(d, str, "url"),
|
|
||||||
path=_get(d, str, "path"),
|
|
||||||
requested_revision=_get(d, str, "requested-revision"),
|
|
||||||
commit_id=_get_required(d, str, "commit-id"),
|
|
||||||
subdirectory=_get(d, str, "subdirectory"),
|
|
||||||
)
|
|
||||||
_validate_path_url(package_vcs.path, package_vcs.url)
|
|
||||||
return package_vcs
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class PackageDirectory:
|
|
||||||
path: str
|
|
||||||
editable: bool | None = None
|
|
||||||
subdirectory: str | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
path: str,
|
|
||||||
editable: bool | None = None,
|
|
||||||
subdirectory: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "path", path)
|
|
||||||
object.__setattr__(self, "editable", editable)
|
|
||||||
object.__setattr__(self, "subdirectory", subdirectory)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
return cls(
|
|
||||||
path=_get_required(d, str, "path"),
|
|
||||||
editable=_get(d, bool, "editable"),
|
|
||||||
subdirectory=_get(d, str, "subdirectory"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class PackageArchive:
|
|
||||||
url: str | None = None
|
|
||||||
path: str | None = None
|
|
||||||
size: int | None = None
|
|
||||||
upload_time: datetime | None = None
|
|
||||||
hashes: Mapping[str, str] # type: ignore[misc]
|
|
||||||
subdirectory: str | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
url: str | None = None,
|
|
||||||
path: str | None = None,
|
|
||||||
size: int | None = None,
|
|
||||||
upload_time: datetime | None = None,
|
|
||||||
hashes: Mapping[str, str],
|
|
||||||
subdirectory: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "url", url)
|
|
||||||
object.__setattr__(self, "path", path)
|
|
||||||
object.__setattr__(self, "size", size)
|
|
||||||
object.__setattr__(self, "upload_time", upload_time)
|
|
||||||
object.__setattr__(self, "hashes", hashes)
|
|
||||||
object.__setattr__(self, "subdirectory", subdirectory)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
package_archive = cls(
|
|
||||||
url=_get(d, str, "url"),
|
|
||||||
path=_get(d, str, "path"),
|
|
||||||
size=_get(d, int, "size"),
|
|
||||||
upload_time=_get(d, datetime, "upload-time"),
|
|
||||||
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
|
||||||
subdirectory=_get(d, str, "subdirectory"),
|
|
||||||
)
|
|
||||||
_validate_path_url(package_archive.path, package_archive.url)
|
|
||||||
return package_archive
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class PackageSdist:
|
|
||||||
name: str | None = None
|
|
||||||
upload_time: datetime | None = None
|
|
||||||
url: str | None = None
|
|
||||||
path: str | None = None
|
|
||||||
size: int | None = None
|
|
||||||
hashes: Mapping[str, str] # type: ignore[misc]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
name: str | None = None,
|
|
||||||
upload_time: datetime | None = None,
|
|
||||||
url: str | None = None,
|
|
||||||
path: str | None = None,
|
|
||||||
size: int | None = None,
|
|
||||||
hashes: Mapping[str, str],
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "name", name)
|
|
||||||
object.__setattr__(self, "upload_time", upload_time)
|
|
||||||
object.__setattr__(self, "url", url)
|
|
||||||
object.__setattr__(self, "path", path)
|
|
||||||
object.__setattr__(self, "size", size)
|
|
||||||
object.__setattr__(self, "hashes", hashes)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
package_sdist = cls(
|
|
||||||
name=_get(d, str, "name"),
|
|
||||||
upload_time=_get(d, datetime, "upload-time"),
|
|
||||||
url=_get(d, str, "url"),
|
|
||||||
path=_get(d, str, "path"),
|
|
||||||
size=_get(d, int, "size"),
|
|
||||||
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
|
||||||
)
|
|
||||||
_validate_path_url(package_sdist.path, package_sdist.url)
|
|
||||||
return package_sdist
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self) -> str:
|
|
||||||
"""Get the filename of the sdist."""
|
|
||||||
filename = self.name or _path_name(self.path) or _url_name(self.url)
|
|
||||||
if not filename:
|
|
||||||
raise PylockValidationError("Cannot determine sdist filename")
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class PackageWheel:
|
|
||||||
name: str | None = None
|
|
||||||
upload_time: datetime | None = None
|
|
||||||
url: str | None = None
|
|
||||||
path: str | None = None
|
|
||||||
size: int | None = None
|
|
||||||
hashes: Mapping[str, str] # type: ignore[misc]
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
name: str | None = None,
|
|
||||||
upload_time: datetime | None = None,
|
|
||||||
url: str | None = None,
|
|
||||||
path: str | None = None,
|
|
||||||
size: int | None = None,
|
|
||||||
hashes: Mapping[str, str],
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "name", name)
|
|
||||||
object.__setattr__(self, "upload_time", upload_time)
|
|
||||||
object.__setattr__(self, "url", url)
|
|
||||||
object.__setattr__(self, "path", path)
|
|
||||||
object.__setattr__(self, "size", size)
|
|
||||||
object.__setattr__(self, "hashes", hashes)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
package_wheel = cls(
|
|
||||||
name=_get(d, str, "name"),
|
|
||||||
upload_time=_get(d, datetime, "upload-time"),
|
|
||||||
url=_get(d, str, "url"),
|
|
||||||
path=_get(d, str, "path"),
|
|
||||||
size=_get(d, int, "size"),
|
|
||||||
hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"), # type: ignore[type-abstract]
|
|
||||||
)
|
|
||||||
_validate_path_url(package_wheel.path, package_wheel.url)
|
|
||||||
return package_wheel
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self) -> str:
|
|
||||||
"""Get the filename of the wheel."""
|
|
||||||
filename = self.name or _path_name(self.path) or _url_name(self.url)
|
|
||||||
if not filename:
|
|
||||||
raise PylockValidationError("Cannot determine wheel filename")
|
|
||||||
return filename
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class Package:
|
|
||||||
name: NormalizedName
|
|
||||||
version: Version | None = None
|
|
||||||
marker: Marker | None = None
|
|
||||||
requires_python: SpecifierSet | None = None
|
|
||||||
dependencies: Sequence[Mapping[str, Any]] | None = None
|
|
||||||
vcs: PackageVcs | None = None
|
|
||||||
directory: PackageDirectory | None = None
|
|
||||||
archive: PackageArchive | None = None
|
|
||||||
index: str | None = None
|
|
||||||
sdist: PackageSdist | None = None
|
|
||||||
wheels: Sequence[PackageWheel] | None = None
|
|
||||||
attestation_identities: Sequence[Mapping[str, Any]] | None = None
|
|
||||||
tool: Mapping[str, Any] | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
name: NormalizedName,
|
|
||||||
version: Version | None = None,
|
|
||||||
marker: Marker | None = None,
|
|
||||||
requires_python: SpecifierSet | None = None,
|
|
||||||
dependencies: Sequence[Mapping[str, Any]] | None = None,
|
|
||||||
vcs: PackageVcs | None = None,
|
|
||||||
directory: PackageDirectory | None = None,
|
|
||||||
archive: PackageArchive | None = None,
|
|
||||||
index: str | None = None,
|
|
||||||
sdist: PackageSdist | None = None,
|
|
||||||
wheels: Sequence[PackageWheel] | None = None,
|
|
||||||
attestation_identities: Sequence[Mapping[str, Any]] | None = None,
|
|
||||||
tool: Mapping[str, Any] | None = None,
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "name", name)
|
|
||||||
object.__setattr__(self, "version", version)
|
|
||||||
object.__setattr__(self, "marker", marker)
|
|
||||||
object.__setattr__(self, "requires_python", requires_python)
|
|
||||||
object.__setattr__(self, "dependencies", dependencies)
|
|
||||||
object.__setattr__(self, "vcs", vcs)
|
|
||||||
object.__setattr__(self, "directory", directory)
|
|
||||||
object.__setattr__(self, "archive", archive)
|
|
||||||
object.__setattr__(self, "index", index)
|
|
||||||
object.__setattr__(self, "sdist", sdist)
|
|
||||||
object.__setattr__(self, "wheels", wheels)
|
|
||||||
object.__setattr__(self, "attestation_identities", attestation_identities)
|
|
||||||
object.__setattr__(self, "tool", tool)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
package = cls(
|
|
||||||
name=_get_required_as(d, str, _validate_normalized_name, "name"),
|
|
||||||
version=_get_as(d, str, Version, "version"),
|
|
||||||
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
|
||||||
dependencies=_get_sequence(d, Mapping, "dependencies"), # type: ignore[type-abstract]
|
|
||||||
marker=_get_as(d, str, Marker, "marker"),
|
|
||||||
vcs=_get_object(d, PackageVcs, "vcs"),
|
|
||||||
directory=_get_object(d, PackageDirectory, "directory"),
|
|
||||||
archive=_get_object(d, PackageArchive, "archive"),
|
|
||||||
index=_get(d, str, "index"),
|
|
||||||
sdist=_get_object(d, PackageSdist, "sdist"),
|
|
||||||
wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
|
|
||||||
attestation_identities=_get_sequence(d, Mapping, "attestation-identities"), # type: ignore[type-abstract]
|
|
||||||
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
|
||||||
)
|
|
||||||
distributions = bool(package.sdist) + len(package.wheels or [])
|
|
||||||
direct_urls = (
|
|
||||||
bool(package.vcs) + bool(package.directory) + bool(package.archive)
|
|
||||||
)
|
|
||||||
if distributions > 0 and direct_urls > 0:
|
|
||||||
raise PylockValidationError(
|
|
||||||
"None of vcs, directory, archive must be set if sdist or wheels are set"
|
|
||||||
)
|
|
||||||
if distributions == 0 and direct_urls != 1:
|
|
||||||
raise PylockValidationError(
|
|
||||||
"Exactly one of vcs, directory, archive must be set "
|
|
||||||
"if sdist and wheels are not set"
|
|
||||||
)
|
|
||||||
for i, wheel in enumerate(package.wheels or []):
|
|
||||||
try:
|
|
||||||
(name, version, _, _) = parse_wheel_filename(wheel.filename)
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Invalid wheel filename {wheel.filename!r}",
|
|
||||||
context=f"wheels[{i}]",
|
|
||||||
) from e
|
|
||||||
if name != package.name:
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Name in {wheel.filename!r} is not consistent with "
|
|
||||||
f"package name {package.name!r}",
|
|
||||||
context=f"wheels[{i}]",
|
|
||||||
)
|
|
||||||
if package.version and version != package.version:
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Version in {wheel.filename!r} is not consistent with "
|
|
||||||
f"package version {str(package.version)!r}",
|
|
||||||
context=f"wheels[{i}]",
|
|
||||||
)
|
|
||||||
if package.sdist:
|
|
||||||
try:
|
|
||||||
name, version = parse_sdist_filename(package.sdist.filename)
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Invalid sdist filename {package.sdist.filename!r}",
|
|
||||||
context="sdist",
|
|
||||||
) from e
|
|
||||||
if name != package.name:
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Name in {package.sdist.filename!r} is not consistent with "
|
|
||||||
f"package name {package.name!r}",
|
|
||||||
context="sdist",
|
|
||||||
)
|
|
||||||
if package.version and version != package.version:
|
|
||||||
raise PylockValidationError(
|
|
||||||
f"Version in {package.sdist.filename!r} is not consistent with "
|
|
||||||
f"package version {str(package.version)!r}",
|
|
||||||
context="sdist",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
for i, attestation_identity in enumerate( # noqa: B007
|
|
||||||
package.attestation_identities or []
|
|
||||||
):
|
|
||||||
_get_required(attestation_identity, str, "kind")
|
|
||||||
except Exception as e:
|
|
||||||
raise PylockValidationError(
|
|
||||||
e, context=f"attestation-identities[{i}]"
|
|
||||||
) from e
|
|
||||||
return package
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_direct(self) -> bool:
|
|
||||||
return not (self.sdist or self.wheels)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, init=False)
|
|
||||||
class Pylock:
|
|
||||||
"""A class representing a pylock file."""
|
|
||||||
|
|
||||||
lock_version: Version
|
|
||||||
environments: Sequence[Marker] | None = None
|
|
||||||
requires_python: SpecifierSet | None = None
|
|
||||||
extras: Sequence[NormalizedName] | None = None
|
|
||||||
dependency_groups: Sequence[str] | None = None
|
|
||||||
default_groups: Sequence[str] | None = None
|
|
||||||
created_by: str # type: ignore[misc]
|
|
||||||
packages: Sequence[Package] # type: ignore[misc]
|
|
||||||
tool: Mapping[str, Any] | None = None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
lock_version: Version,
|
|
||||||
environments: Sequence[Marker] | None = None,
|
|
||||||
requires_python: SpecifierSet | None = None,
|
|
||||||
extras: Sequence[NormalizedName] | None = None,
|
|
||||||
dependency_groups: Sequence[str] | None = None,
|
|
||||||
default_groups: Sequence[str] | None = None,
|
|
||||||
created_by: str,
|
|
||||||
packages: Sequence[Package],
|
|
||||||
tool: Mapping[str, Any] | None = None,
|
|
||||||
) -> None:
|
|
||||||
# In Python 3.10+ make dataclass kw_only=True and remove __init__
|
|
||||||
object.__setattr__(self, "lock_version", lock_version)
|
|
||||||
object.__setattr__(self, "environments", environments)
|
|
||||||
object.__setattr__(self, "requires_python", requires_python)
|
|
||||||
object.__setattr__(self, "extras", extras)
|
|
||||||
object.__setattr__(self, "dependency_groups", dependency_groups)
|
|
||||||
object.__setattr__(self, "default_groups", default_groups)
|
|
||||||
object.__setattr__(self, "created_by", created_by)
|
|
||||||
object.__setattr__(self, "packages", packages)
|
|
||||||
object.__setattr__(self, "tool", tool)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _from_dict(cls, d: Mapping[str, Any]) -> Self:
|
|
||||||
pylock = cls(
|
|
||||||
lock_version=_get_required_as(d, str, Version, "lock-version"),
|
|
||||||
environments=_get_sequence_as(d, str, Marker, "environments"),
|
|
||||||
extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),
|
|
||||||
dependency_groups=_get_sequence(d, str, "dependency-groups"),
|
|
||||||
default_groups=_get_sequence(d, str, "default-groups"),
|
|
||||||
created_by=_get_required(d, str, "created-by"),
|
|
||||||
requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
|
|
||||||
packages=_get_required_sequence_of_objects(d, Package, "packages"),
|
|
||||||
tool=_get(d, Mapping, "tool"), # type: ignore[type-abstract]
|
|
||||||
)
|
|
||||||
if not Version("1") <= pylock.lock_version < Version("2"):
|
|
||||||
raise PylockUnsupportedVersionError(
|
|
||||||
f"pylock version {pylock.lock_version} is not supported"
|
|
||||||
)
|
|
||||||
if pylock.lock_version > Version("1.0"):
|
|
||||||
_logger.warning(
|
|
||||||
"pylock minor version %s is not supported", pylock.lock_version
|
|
||||||
)
|
|
||||||
return pylock
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, d: Mapping[str, Any], /) -> Self:
|
|
||||||
"""Create and validate a Pylock instance from a TOML dictionary.
|
|
||||||
|
|
||||||
Raises :class:`PylockValidationError` if the input data is not
|
|
||||||
spec-compliant.
|
|
||||||
"""
|
|
||||||
return cls._from_dict(d)
|
|
||||||
|
|
||||||
def to_dict(self) -> Mapping[str, Any]:
|
|
||||||
"""Convert the Pylock instance to a TOML dictionary."""
|
|
||||||
return dataclasses.asdict(self, dict_factory=_toml_dict_factory)
|
|
||||||
|
|
||||||
def validate(self) -> None:
|
|
||||||
"""Validate the Pylock instance against the specification.
|
|
||||||
|
|
||||||
Raises :class:`PylockValidationError` otherwise."""
|
|
||||||
self.from_dict(self.to_dict())
|
|
||||||
|
|
||||||
def select(
|
|
||||||
self,
|
|
||||||
*,
|
|
||||||
environment: Environment | None = None,
|
|
||||||
tags: Sequence[Tag] | None = None,
|
|
||||||
extras: Collection[str] | None = None,
|
|
||||||
dependency_groups: Collection[str] | None = None,
|
|
||||||
) -> Iterator[
|
|
||||||
tuple[
|
|
||||||
Package,
|
|
||||||
PackageVcs
|
|
||||||
| PackageDirectory
|
|
||||||
| PackageArchive
|
|
||||||
| PackageWheel
|
|
||||||
| PackageSdist,
|
|
||||||
]
|
|
||||||
]:
|
|
||||||
"""Select what to install from the lock file.
|
|
||||||
|
|
||||||
The *environment* and *tags* parameters represent the environment being
|
|
||||||
selected for. If unspecified, ``packaging.markers.default_environment()`` and
|
|
||||||
``packaging.tags.sys_tags()`` are used.
|
|
||||||
|
|
||||||
The *extras* parameter represents the extras to install.
|
|
||||||
|
|
||||||
The *dependency_groups* parameter represents the groups to install. If
|
|
||||||
unspecified, the default groups are used.
|
|
||||||
|
|
||||||
This method must be used on valid Pylock instances (i.e. one obtained
|
|
||||||
from :meth:`Pylock.from_dict` or if constructed manually, after calling
|
|
||||||
:meth:`Pylock.validate`).
|
|
||||||
"""
|
|
||||||
compatible_tags_selector = create_compatible_tags_selector(tags or sys_tags())
|
|
||||||
|
|
||||||
# #. Gather the extras and dependency groups to install and set ``extras`` and
|
|
||||||
# ``dependency_groups`` for marker evaluation, respectively.
|
|
||||||
#
|
|
||||||
# #. ``extras`` SHOULD be set to the empty set by default.
|
|
||||||
# #. ``dependency_groups`` SHOULD be the set created from
|
|
||||||
# :ref:`pylock-default-groups` by default.
|
|
||||||
env = cast(
|
|
||||||
"dict[str, str | frozenset[str]]",
|
|
||||||
dict(
|
|
||||||
environment or {}, # Marker.evaluate will fill-up
|
|
||||||
extras=frozenset(extras or []),
|
|
||||||
dependency_groups=frozenset(
|
|
||||||
(self.default_groups or [])
|
|
||||||
if dependency_groups is None # to allow selecting no group
|
|
||||||
else dependency_groups
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
env_python_full_version = (
|
|
||||||
environment["python_full_version"]
|
|
||||||
if environment
|
|
||||||
else default_environment()["python_full_version"]
|
|
||||||
)
|
|
||||||
|
|
||||||
# #. Check if the metadata version specified by :ref:`pylock-lock-version` is
|
|
||||||
# supported; an error or warning MUST be raised as appropriate.
|
|
||||||
# Covered by lock.validate() which is a precondition for this method.
|
|
||||||
|
|
||||||
# #. If :ref:`pylock-requires-python` is specified, check that the environment
|
|
||||||
# being installed for meets the requirement; an error MUST be raised if it is
|
|
||||||
# not met.
|
|
||||||
if self.requires_python and not self.requires_python.contains(
|
|
||||||
env_python_full_version,
|
|
||||||
):
|
|
||||||
raise PylockSelectError(
|
|
||||||
f"python_full_version {env_python_full_version!r} "
|
|
||||||
f"in provided environment does not satisfy the Python version "
|
|
||||||
f"requirement {str(self.requires_python)!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# #. If :ref:`pylock-environments` is specified, check that at least one of the
|
|
||||||
# environment marker expressions is satisfied; an error MUST be raised if no
|
|
||||||
# expression is satisfied.
|
|
||||||
if self.environments:
|
|
||||||
for env_marker in self.environments:
|
|
||||||
if env_marker.evaluate(
|
|
||||||
cast("dict[str, str]", environment or {}), context="requirement"
|
|
||||||
):
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise PylockSelectError(
|
|
||||||
"Provided environment does not satisfy any of the "
|
|
||||||
"environments specified in the lock file"
|
|
||||||
)
|
|
||||||
|
|
||||||
# #. For each package listed in :ref:`pylock-packages`:
|
|
||||||
selected_packages_by_name: dict[str, tuple[int, Package]] = {}
|
|
||||||
for package_index, package in enumerate(self.packages):
|
|
||||||
# #. If :ref:`pylock-packages-marker` is specified, check if it is
|
|
||||||
# satisfied;if it isn't, skip to the next package.
|
|
||||||
if package.marker and not package.marker.evaluate(env, context="lock_file"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# #. If :ref:`pylock-packages-requires-python` is specified, check if it is
|
|
||||||
# satisfied; an error MUST be raised if it isn't.
|
|
||||||
if package.requires_python and not package.requires_python.contains(
|
|
||||||
env_python_full_version,
|
|
||||||
):
|
|
||||||
raise PylockSelectError(
|
|
||||||
f"python_full_version {env_python_full_version!r} "
|
|
||||||
f"in provided environment does not satisfy the Python version "
|
|
||||||
f"requirement {str(package.requires_python)!r} for package "
|
|
||||||
f"{package.name!r} at packages[{package_index}]"
|
|
||||||
)
|
|
||||||
|
|
||||||
# #. Check that no other conflicting instance of the package has been slated
|
|
||||||
# to be installed; an error about the ambiguity MUST be raised otherwise.
|
|
||||||
if package.name in selected_packages_by_name:
|
|
||||||
raise PylockSelectError(
|
|
||||||
f"Multiple packages with the name {package.name!r} are "
|
|
||||||
f"selected at packages[{package_index}] and "
|
|
||||||
f"packages[{selected_packages_by_name[package.name][0]}]"
|
|
||||||
)
|
|
||||||
|
|
||||||
# #. Check that the source of the package is specified appropriately (i.e.
|
|
||||||
# there are no conflicting sources in the package entry);
|
|
||||||
# an error MUST be raised if any issues are found.
|
|
||||||
# Covered by lock.validate() which is a precondition for this method.
|
|
||||||
|
|
||||||
# #. Add the package to the set of packages to install.
|
|
||||||
selected_packages_by_name[package.name] = (package_index, package)
|
|
||||||
|
|
||||||
# #. For each package to be installed:
|
|
||||||
for package_index, package in selected_packages_by_name.values():
|
|
||||||
# - If :ref:`pylock-packages-vcs` is set:
|
|
||||||
if package.vcs is not None:
|
|
||||||
yield package, package.vcs
|
|
||||||
|
|
||||||
# - Else if :ref:`pylock-packages-directory` is set:
|
|
||||||
elif package.directory is not None:
|
|
||||||
yield package, package.directory
|
|
||||||
|
|
||||||
# - Else if :ref:`pylock-packages-archive` is set:
|
|
||||||
elif package.archive is not None:
|
|
||||||
yield package, package.archive
|
|
||||||
|
|
||||||
# - Else if there are entries for :ref:`pylock-packages-wheels`:
|
|
||||||
elif package.wheels:
|
|
||||||
# #. Look for the appropriate wheel file based on
|
|
||||||
# :ref:`pylock-packages-wheels-name`; if one is not found then move
|
|
||||||
# on to :ref:`pylock-packages-sdist` or an error MUST be raised about
|
|
||||||
# a lack of source for the project.
|
|
||||||
best_wheel = next(
|
|
||||||
compatible_tags_selector(
|
|
||||||
(wheel, parse_wheel_filename(wheel.filename)[-1])
|
|
||||||
for wheel in package.wheels
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if best_wheel:
|
|
||||||
yield package, best_wheel
|
|
||||||
elif package.sdist is not None:
|
|
||||||
yield package, package.sdist
|
|
||||||
else:
|
|
||||||
raise PylockSelectError(
|
|
||||||
f"No wheel found matching the provided tags "
|
|
||||||
f"for package {package.name!r} "
|
|
||||||
f"at packages[{package_index}], "
|
|
||||||
f"and no sdist available as a fallback"
|
|
||||||
)
|
|
||||||
|
|
||||||
# - Else if no :ref:`pylock-packages-wheels` file is found or
|
|
||||||
# :ref:`pylock-packages-sdist` is solely set:
|
|
||||||
elif package.sdist is not None:
|
|
||||||
yield package, package.sdist
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Covered by lock.validate() which is a precondition for this method.
|
|
||||||
raise NotImplementedError # pragma: no cover
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
# for complete details.
|
# for complete details.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterator
|
from typing import Any, Iterator
|
||||||
|
|
||||||
from ._parser import parse_requirement as _parse_requirement
|
from ._parser import parse_requirement as _parse_requirement
|
||||||
from ._tokenizer import ParserSyntaxError
|
from ._tokenizer import ParserSyntaxError
|
||||||
@@ -11,15 +11,6 @@ from .markers import Marker, _normalize_extra_values
|
|||||||
from .specifiers import SpecifierSet
|
from .specifiers import SpecifierSet
|
||||||
from .utils import canonicalize_name
|
from .utils import canonicalize_name
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"InvalidRequirement",
|
|
||||||
"Requirement",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidRequirement(ValueError):
|
class InvalidRequirement(ValueError):
|
||||||
"""
|
"""
|
||||||
@@ -33,16 +24,6 @@ class Requirement:
|
|||||||
Parse a given requirement string into its parts, such as name, specifier,
|
Parse a given requirement string into its parts, such as name, specifier,
|
||||||
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
|
URL, and extras. Raises InvalidRequirement on a badly-formed requirement
|
||||||
string.
|
string.
|
||||||
|
|
||||||
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
|
||||||
format so the same pickle can be loaded in future packaging releases.
|
|
||||||
|
|
||||||
.. versionchanged:: 26.2
|
|
||||||
|
|
||||||
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
|
||||||
be unpickled with future releases. Backward compatibility with pickles
|
|
||||||
from packaging < 26.2 is supported but may be removed in a future
|
|
||||||
release.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: Can we test whether something is contained within a requirement?
|
# TODO: Can we test whether something is contained within a requirement?
|
||||||
@@ -76,47 +57,28 @@ class Requirement:
|
|||||||
yield str(self.specifier)
|
yield str(self.specifier)
|
||||||
|
|
||||||
if self.url:
|
if self.url:
|
||||||
yield f" @ {self.url}"
|
yield f"@ {self.url}"
|
||||||
if self.marker:
|
if self.marker:
|
||||||
yield " "
|
yield " "
|
||||||
|
|
||||||
if self.marker:
|
if self.marker:
|
||||||
yield f"; {self.marker}"
|
yield f"; {self.marker}"
|
||||||
|
|
||||||
def __getstate__(self) -> str:
|
|
||||||
# Return the requirement string for compactness and stability.
|
|
||||||
# Re-parsed on load to reconstruct all fields.
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
def __setstate__(self, state: object) -> None:
|
|
||||||
if isinstance(state, str):
|
|
||||||
# New format (26.2+): just the requirement string.
|
|
||||||
try:
|
|
||||||
tmp = Requirement(state)
|
|
||||||
except InvalidRequirement as exc:
|
|
||||||
raise TypeError(f"Cannot restore Requirement from {state!r}") from exc
|
|
||||||
self.name = tmp.name
|
|
||||||
self.url = tmp.url
|
|
||||||
self.extras = tmp.extras
|
|
||||||
self.specifier = tmp.specifier
|
|
||||||
self.marker = tmp.marker
|
|
||||||
return
|
|
||||||
if isinstance(state, dict):
|
|
||||||
# Old format (packaging <= 26.1, no __slots__): plain __dict__.
|
|
||||||
self.__dict__.update(state)
|
|
||||||
return
|
|
||||||
raise TypeError(f"Cannot restore Requirement from {state!r}")
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "".join(self._iter_parts(self.name))
|
return "".join(self._iter_parts(self.name))
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self.__class__.__name__}({str(self)!r})>"
|
return f"<Requirement('{self}')>"
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(tuple(self._iter_parts(canonicalize_name(self.name))))
|
return hash(
|
||||||
|
(
|
||||||
|
self.__class__.__name__,
|
||||||
|
*self._iter_parts(canonicalize_name(self.name)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: Any) -> bool:
|
||||||
if not isinstance(other, Requirement):
|
if not isinstance(other, Requirement):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|||||||
+243
-1167
File diff suppressed because it is too large
Load Diff
+59
-335
@@ -5,7 +5,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import operator
|
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
@@ -14,52 +13,19 @@ import sys
|
|||||||
import sysconfig
|
import sysconfig
|
||||||
from importlib.machinery import EXTENSION_SUFFIXES
|
from importlib.machinery import EXTENSION_SUFFIXES
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
|
||||||
Iterable,
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
TypeVar,
|
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import _manylinux, _musllinux
|
from . import _manylinux, _musllinux
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from collections.abc import Callable, Iterable
|
|
||||||
from typing import AbstractSet
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"INTERPRETER_SHORT_NAMES",
|
|
||||||
"AppleVersion",
|
|
||||||
"PythonVersion",
|
|
||||||
"Tag",
|
|
||||||
"UnsortedTagsError",
|
|
||||||
"android_platforms",
|
|
||||||
"compatible_tags",
|
|
||||||
"cpython_tags",
|
|
||||||
"create_compatible_tags_selector",
|
|
||||||
"generic_tags",
|
|
||||||
"interpreter_name",
|
|
||||||
"interpreter_version",
|
|
||||||
"ios_platforms",
|
|
||||||
"mac_platforms",
|
|
||||||
"parse_tag",
|
|
||||||
"platform_tags",
|
|
||||||
"sys_tags",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
PythonVersion = Sequence[int]
|
PythonVersion = Sequence[int]
|
||||||
AppleVersion = Tuple[int, int]
|
AppleVersion = Tuple[int, int]
|
||||||
_T = TypeVar("_T")
|
|
||||||
|
|
||||||
INTERPRETER_SHORT_NAMES: dict[str, str] = {
|
INTERPRETER_SHORT_NAMES: dict[str, str] = {
|
||||||
"python": "py", # Generic.
|
"python": "py", # Generic.
|
||||||
@@ -70,19 +36,7 @@ INTERPRETER_SHORT_NAMES: dict[str, str] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# This function can be unit tested without reloading the module
|
_32_BIT_INTERPRETER = struct.calcsize("P") == 4
|
||||||
# (Unlike _32_BIT_INTERPRETER)
|
|
||||||
def _compute_32_bit_interpreter() -> bool:
|
|
||||||
return struct.calcsize("P") == 4
|
|
||||||
|
|
||||||
|
|
||||||
_32_BIT_INTERPRETER = _compute_32_bit_interpreter()
|
|
||||||
|
|
||||||
|
|
||||||
class UnsortedTagsError(ValueError):
|
|
||||||
"""
|
|
||||||
Raised when a tag component is not in sorted order per PEP 425.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Tag:
|
class Tag:
|
||||||
@@ -91,29 +45,11 @@ class Tag:
|
|||||||
|
|
||||||
Instances are considered immutable and thus are hashable. Equality checking
|
Instances are considered immutable and thus are hashable. Equality checking
|
||||||
is also supported.
|
is also supported.
|
||||||
|
|
||||||
Instances are safe to serialize with :mod:`pickle`. They use a stable
|
|
||||||
format so the same pickle can be loaded in future packaging releases.
|
|
||||||
|
|
||||||
.. versionchanged:: 26.2
|
|
||||||
|
|
||||||
Added a stable pickle format. Pickles created with packaging 26.2+ can
|
|
||||||
be unpickled with future releases. Backward compatibility with pickles
|
|
||||||
from packaging < 26.2 is supported but may be removed in a future
|
|
||||||
release.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
|
__slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
|
||||||
|
|
||||||
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
|
def __init__(self, interpreter: str, abi: str, platform: str) -> None:
|
||||||
"""
|
|
||||||
:param str interpreter: The interpreter name, e.g. ``"py"``
|
|
||||||
(see :attr:`INTERPRETER_SHORT_NAMES` for mapping
|
|
||||||
well-known interpreter names to their short names).
|
|
||||||
:param str abi: The ABI that a wheel supports, e.g. ``"cp37m"``.
|
|
||||||
:param str platform: The OS/platform the wheel supports,
|
|
||||||
e.g. ``"win_amd64"``.
|
|
||||||
"""
|
|
||||||
self._interpreter = interpreter.lower()
|
self._interpreter = interpreter.lower()
|
||||||
self._abi = abi.lower()
|
self._abi = abi.lower()
|
||||||
self._platform = platform.lower()
|
self._platform = platform.lower()
|
||||||
@@ -126,25 +62,14 @@ class Tag:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def interpreter(self) -> str:
|
def interpreter(self) -> str:
|
||||||
"""
|
|
||||||
The interpreter name, e.g. ``"py"`` (see
|
|
||||||
:attr:`INTERPRETER_SHORT_NAMES` for mapping well-known interpreter
|
|
||||||
names to their short names).
|
|
||||||
"""
|
|
||||||
return self._interpreter
|
return self._interpreter
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def abi(self) -> str:
|
def abi(self) -> str:
|
||||||
"""
|
|
||||||
The supported ABI.
|
|
||||||
"""
|
|
||||||
return self._abi
|
return self._abi
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platform(self) -> str:
|
def platform(self) -> str:
|
||||||
"""
|
|
||||||
The OS/platform.
|
|
||||||
"""
|
|
||||||
return self._platform
|
return self._platform
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
@@ -167,69 +92,16 @@ class Tag:
|
|||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<{self} @ {id(self)}>"
|
return f"<{self} @ {id(self)}>"
|
||||||
|
|
||||||
def __getstate__(self) -> tuple[str, str, str]:
|
|
||||||
# Return state as a 3-item tuple: (interpreter, abi, platform).
|
|
||||||
# Cache member _hash is excluded and will be recomputed.
|
|
||||||
return (self._interpreter, self._abi, self._platform)
|
|
||||||
|
|
||||||
def __setstate__(self, state: object) -> None:
|
def parse_tag(tag: str) -> frozenset[Tag]:
|
||||||
if isinstance(state, tuple):
|
|
||||||
if len(state) == 3 and all(isinstance(s, str) for s in state):
|
|
||||||
# New format (26.2+): (interpreter, abi, platform)
|
|
||||||
self._interpreter, self._abi, self._platform = state
|
|
||||||
self._hash = hash((self._interpreter, self._abi, self._platform))
|
|
||||||
return
|
|
||||||
if len(state) == 2 and isinstance(state[1], dict):
|
|
||||||
# Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
|
|
||||||
_, slots = state
|
|
||||||
try:
|
|
||||||
interpreter = slots["_interpreter"]
|
|
||||||
abi = slots["_abi"]
|
|
||||||
platform = slots["_platform"]
|
|
||||||
except KeyError:
|
|
||||||
raise TypeError(f"Cannot restore Tag from {state!r}") from None
|
|
||||||
if not all(
|
|
||||||
isinstance(value, str) for value in (interpreter, abi, platform)
|
|
||||||
):
|
|
||||||
raise TypeError(f"Cannot restore Tag from {state!r}")
|
|
||||||
self._interpreter = interpreter.lower()
|
|
||||||
self._abi = abi.lower()
|
|
||||||
self._platform = platform.lower()
|
|
||||||
self._hash = hash((self._interpreter, self._abi, self._platform))
|
|
||||||
return
|
|
||||||
raise TypeError(f"Cannot restore Tag from {state!r}")
|
|
||||||
|
|
||||||
|
|
||||||
def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]:
|
|
||||||
"""
|
"""
|
||||||
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of
|
Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances.
|
||||||
:class:`Tag` instances.
|
|
||||||
|
|
||||||
Returning a set is required due to the possibility that the tag is a
|
Returning a set is required due to the possibility that the tag is a
|
||||||
`compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both
|
compressed tag set.
|
||||||
Python 2 and Python 3.
|
|
||||||
|
|
||||||
If **validate_order** is true, compressed tag set components are checked
|
|
||||||
to be in sorted order as required by PEP 425.
|
|
||||||
|
|
||||||
:param str tag: The tag to parse, e.g. ``"py3-none-any"``.
|
|
||||||
:param bool validate_order: Check whether compressed tag set components
|
|
||||||
are in sorted order.
|
|
||||||
:raises UnsortedTagsError: If **validate_order** is true and any compressed tag
|
|
||||||
set component is not in sorted order.
|
|
||||||
|
|
||||||
.. versionadded:: 26.1
|
|
||||||
The *validate_order* parameter.
|
|
||||||
"""
|
"""
|
||||||
tags = set()
|
tags = set()
|
||||||
interpreters, abis, platforms = tag.split("-")
|
interpreters, abis, platforms = tag.split("-")
|
||||||
if validate_order:
|
|
||||||
for component in (interpreters, abis, platforms):
|
|
||||||
parts = component.split(".")
|
|
||||||
if parts != sorted(parts):
|
|
||||||
raise UnsortedTagsError(
|
|
||||||
f"Tag component {component!r} is not in sorted order per PEP 425"
|
|
||||||
)
|
|
||||||
for interpreter in interpreters.split("."):
|
for interpreter in interpreters.split("."):
|
||||||
for abi in abis.split("."):
|
for abi in abis.split("."):
|
||||||
for platform_ in platforms.split("."):
|
for platform_ in platforms.split("."):
|
||||||
@@ -270,25 +142,12 @@ def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
|
|||||||
"""
|
"""
|
||||||
Determine if the Python version supports abi3.
|
Determine if the Python version supports abi3.
|
||||||
|
|
||||||
PEP 384 was first implemented in Python 3.2. The free-threaded
|
PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`)
|
||||||
builds do not support abi3.
|
builds do not support abi3.
|
||||||
"""
|
"""
|
||||||
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
|
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
|
||||||
|
|
||||||
|
|
||||||
def _abi3t_applies(python_version: PythonVersion, threading: bool) -> bool:
|
|
||||||
"""
|
|
||||||
Determine if the Python version supports abi3t.
|
|
||||||
|
|
||||||
PEP 803 was first implemented in Python 3.15 but, per PEP 803, this
|
|
||||||
returns tags going back to Python 3.2 to mirror the abi3
|
|
||||||
implementation and leave open the possibility of abi3t wheels
|
|
||||||
supporting older Python versions.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return len(python_version) > 1 and tuple(python_version) >= (3, 2) and threading
|
|
||||||
|
|
||||||
|
|
||||||
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
|
def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
|
||||||
py_version = tuple(py_version) # To allow for version comparison.
|
py_version = tuple(py_version) # To allow for version comparison.
|
||||||
abis = []
|
abis = []
|
||||||
@@ -330,31 +189,19 @@ def cpython_tags(
|
|||||||
warn: bool = False,
|
warn: bool = False,
|
||||||
) -> Iterator[Tag]:
|
) -> Iterator[Tag]:
|
||||||
"""
|
"""
|
||||||
Yields the tags for the CPython interpreter.
|
Yields the tags for a CPython interpreter.
|
||||||
|
|
||||||
The specific tags generated are:
|
The tags consist of:
|
||||||
|
- cp<python_version>-<abi>-<platform>
|
||||||
|
- cp<python_version>-abi3-<platform>
|
||||||
|
- cp<python_version>-none-<platform>
|
||||||
|
- cp<less than python_version>-abi3-<platform> # Older Python versions down to 3.2.
|
||||||
|
|
||||||
- ``cp<python_version>-<abi>-<platform>``
|
If python_version only specifies a major version then user-provided ABIs and
|
||||||
- ``cp<python_version>-<stable_abi>-<platform>``
|
the 'none' ABItag will be used.
|
||||||
- ``cp<python_version>-none-<platform>``
|
|
||||||
- ``cp<older version>-<stable_abi>-<platform>`` where "older version" is all older
|
|
||||||
minor versions down to Python 3.2 (when ``abi3`` was introduced)
|
|
||||||
|
|
||||||
If ``python_version`` only provides a major-only version then only
|
If 'abi3' or 'none' are specified in 'abis' then they will be yielded at
|
||||||
user-provided ABIs via ``abis`` and the ``none`` ABI will be used.
|
their normal position and not at the beginning.
|
||||||
|
|
||||||
The ``stable_abi`` will be either ``abi3`` or ``abi3t`` if `abi` is a
|
|
||||||
GIL-enabled ABI like `"cp315"` or a free-threaded ABI like `"cp315t"`,
|
|
||||||
respectively.
|
|
||||||
|
|
||||||
:param Sequence python_version: A one- or two-item sequence representing the
|
|
||||||
targeted Python version. Defaults to
|
|
||||||
``sys.version_info[:2]``.
|
|
||||||
:param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
|
|
||||||
compatible with the current system.
|
|
||||||
:param Iterable platforms: Iterable of compatible platforms. Defaults to the
|
|
||||||
platforms compatible with the current system.
|
|
||||||
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
|
||||||
"""
|
"""
|
||||||
if not python_version:
|
if not python_version:
|
||||||
python_version = sys.version_info[:2]
|
python_version = sys.version_info[:2]
|
||||||
@@ -362,13 +209,16 @@ def cpython_tags(
|
|||||||
interpreter = f"cp{_version_nodot(python_version[:2])}"
|
interpreter = f"cp{_version_nodot(python_version[:2])}"
|
||||||
|
|
||||||
if abis is None:
|
if abis is None:
|
||||||
abis = _cpython_abis(python_version, warn) if len(python_version) > 1 else []
|
if len(python_version) > 1:
|
||||||
|
abis = _cpython_abis(python_version, warn)
|
||||||
|
else:
|
||||||
|
abis = []
|
||||||
abis = list(abis)
|
abis = list(abis)
|
||||||
# 'abi3' and 'none' are explicitly handled later.
|
# 'abi3' and 'none' are explicitly handled later.
|
||||||
for explicit_abi in ("abi3", "none"):
|
for explicit_abi in ("abi3", "none"):
|
||||||
try:
|
try:
|
||||||
abis.remove(explicit_abi)
|
abis.remove(explicit_abi)
|
||||||
except ValueError: # noqa: PERF203
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
platforms = list(platforms or platform_tags())
|
platforms = list(platforms or platform_tags())
|
||||||
@@ -378,27 +228,16 @@ def cpython_tags(
|
|||||||
|
|
||||||
threading = _is_threaded_cpython(abis)
|
threading = _is_threaded_cpython(abis)
|
||||||
use_abi3 = _abi3_applies(python_version, threading)
|
use_abi3 = _abi3_applies(python_version, threading)
|
||||||
use_abi3t = _abi3t_applies(python_version, threading)
|
|
||||||
|
|
||||||
if use_abi3:
|
if use_abi3:
|
||||||
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
|
||||||
if use_abi3t:
|
|
||||||
yield from (Tag(interpreter, "abi3t", platform_) for platform_ in platforms)
|
|
||||||
|
|
||||||
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
|
||||||
|
|
||||||
if use_abi3 or use_abi3t:
|
if use_abi3:
|
||||||
for minor_version in range(python_version[1] - 1, 1, -1):
|
for minor_version in range(python_version[1] - 1, 1, -1):
|
||||||
for platform_ in platforms:
|
for platform_ in platforms:
|
||||||
version = _version_nodot((python_version[0], minor_version))
|
version = _version_nodot((python_version[0], minor_version))
|
||||||
interpreter = f"cp{version}"
|
interpreter = f"cp{version}"
|
||||||
if use_abi3:
|
yield Tag(interpreter, "abi3", platform_)
|
||||||
yield Tag(interpreter, "abi3", platform_)
|
|
||||||
if use_abi3t:
|
|
||||||
# Support for abi3t was introduced in Python 3.15, but in
|
|
||||||
# principle abi3t wheels are possible for older limited API
|
|
||||||
# versions, so allow things like ("cp37", "abi3t", "platform")
|
|
||||||
yield Tag(interpreter, "abi3t", platform_)
|
|
||||||
|
|
||||||
|
|
||||||
def _generic_abi() -> list[str]:
|
def _generic_abi() -> list[str]:
|
||||||
@@ -450,31 +289,21 @@ def generic_tags(
|
|||||||
warn: bool = False,
|
warn: bool = False,
|
||||||
) -> Iterator[Tag]:
|
) -> Iterator[Tag]:
|
||||||
"""
|
"""
|
||||||
Yields the tags for an interpreter which requires no specialization.
|
Yields the tags for a generic interpreter.
|
||||||
|
|
||||||
This function should be used if one of the other interpreter-specific
|
The tags consist of:
|
||||||
functions provided by this module is not appropriate (i.e. not calculating
|
- <interpreter>-<abi>-<platform>
|
||||||
tags for a CPython interpreter).
|
|
||||||
|
|
||||||
The specific tags generated are:
|
The "none" ABI will be added if it was not explicitly provided.
|
||||||
|
|
||||||
- ``<interpreter>-<abi>-<platform>``
|
|
||||||
|
|
||||||
The ``"none"`` ABI will be added if it was not explicitly provided.
|
|
||||||
|
|
||||||
:param str interpreter: The name of the interpreter. Defaults to being
|
|
||||||
calculated.
|
|
||||||
:param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
|
|
||||||
compatible with the current system.
|
|
||||||
:param Iterable platforms: Iterable of compatible platforms. Defaults to the
|
|
||||||
platforms compatible with the current system.
|
|
||||||
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
|
||||||
"""
|
"""
|
||||||
if not interpreter:
|
if not interpreter:
|
||||||
interp_name = interpreter_name()
|
interp_name = interpreter_name()
|
||||||
interp_version = interpreter_version(warn=warn)
|
interp_version = interpreter_version(warn=warn)
|
||||||
interpreter = f"{interp_name}{interp_version}"
|
interpreter = "".join([interp_name, interp_version])
|
||||||
abis = _generic_abi() if abis is None else list(abis)
|
if abis is None:
|
||||||
|
abis = _generic_abi()
|
||||||
|
else:
|
||||||
|
abis = list(abis)
|
||||||
platforms = list(platforms or platform_tags())
|
platforms = list(platforms or platform_tags())
|
||||||
if "none" not in abis:
|
if "none" not in abis:
|
||||||
abis.append("none")
|
abis.append("none")
|
||||||
@@ -504,22 +333,12 @@ def compatible_tags(
|
|||||||
platforms: Iterable[str] | None = None,
|
platforms: Iterable[str] | None = None,
|
||||||
) -> Iterator[Tag]:
|
) -> Iterator[Tag]:
|
||||||
"""
|
"""
|
||||||
Yields the tags for an interpreter compatible with the Python version
|
Yields the sequence of tags that are compatible with a specific version of Python.
|
||||||
specified by ``python_version``.
|
|
||||||
|
|
||||||
The specific tags generated are:
|
The tags consist of:
|
||||||
|
- py*-none-<platform>
|
||||||
- ``py*-none-<platform>``
|
- <interpreter>-none-any # ... if `interpreter` is provided.
|
||||||
- ``<interpreter>-none-any`` if ``interpreter`` is provided
|
- py*-none-any
|
||||||
- ``py*-none-any``
|
|
||||||
|
|
||||||
:param Sequence python_version: A one- or two-item sequence representing the
|
|
||||||
compatible version of Python. Defaults to
|
|
||||||
``sys.version_info[:2]``.
|
|
||||||
:param str interpreter: The name of the interpreter (if known), e.g.
|
|
||||||
``"cp38"``. Defaults to the current interpreter.
|
|
||||||
:param Iterable platforms: Iterable of compatible platforms. Defaults to the
|
|
||||||
platforms compatible with the current system.
|
|
||||||
"""
|
"""
|
||||||
if not python_version:
|
if not python_version:
|
||||||
python_version = sys.version_info[:2]
|
python_version = sys.version_info[:2]
|
||||||
@@ -579,25 +398,12 @@ def mac_platforms(
|
|||||||
version: AppleVersion | None = None, arch: str | None = None
|
version: AppleVersion | None = None, arch: str | None = None
|
||||||
) -> Iterator[str]:
|
) -> Iterator[str]:
|
||||||
"""
|
"""
|
||||||
Yields the :attr:`~Tag.platform` tags for macOS.
|
Yields the platform tags for a macOS system.
|
||||||
|
|
||||||
The `version` parameter is a two-item tuple specifying the macOS version to
|
The `version` parameter is a two-item tuple specifying the macOS version to
|
||||||
generate platform tags for. The `arch` parameter is the CPU architecture to
|
generate platform tags for. The `arch` parameter is the CPU architecture to
|
||||||
generate platform tags for. Both parameters default to the appropriate value
|
generate platform tags for. Both parameters default to the appropriate value
|
||||||
for the current system.
|
for the current system.
|
||||||
|
|
||||||
:param tuple version: A two-item tuple representing the version of macOS.
|
|
||||||
Defaults to the current system's version.
|
|
||||||
:param str arch: The CPU architecture. Defaults to the architecture of the
|
|
||||||
current system, e.g. ``"x86_64"``.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Equivalent support for the other major platforms is purposefully not
|
|
||||||
provided:
|
|
||||||
|
|
||||||
- On Windows, platform compatibility is statically specified
|
|
||||||
- On Linux, code must be run on the system itself to determine
|
|
||||||
compatibility
|
|
||||||
"""
|
"""
|
||||||
version_str, _, cpu_arch = platform.mac_ver()
|
version_str, _, cpu_arch = platform.mac_ver()
|
||||||
if version is None:
|
if version is None:
|
||||||
@@ -618,11 +424,14 @@ def mac_platforms(
|
|||||||
text=True,
|
text=True,
|
||||||
).stdout
|
).stdout
|
||||||
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
|
||||||
|
else:
|
||||||
|
version = version
|
||||||
if arch is None:
|
if arch is None:
|
||||||
arch = _mac_arch(cpu_arch)
|
arch = _mac_arch(cpu_arch)
|
||||||
|
else:
|
||||||
|
arch = arch
|
||||||
|
|
||||||
if (10, 0) <= version < (11, 0):
|
if (10, 0) <= version and version < (11, 0):
|
||||||
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
|
# Prior to Mac OS 11, each yearly release of Mac OS bumped the
|
||||||
# "minor" version number. The major version was always 10.
|
# "minor" version number. The major version was always 10.
|
||||||
major_version = 10
|
major_version = 10
|
||||||
@@ -668,19 +477,14 @@ def ios_platforms(
|
|||||||
version: AppleVersion | None = None, multiarch: str | None = None
|
version: AppleVersion | None = None, multiarch: str | None = None
|
||||||
) -> Iterator[str]:
|
) -> Iterator[str]:
|
||||||
"""
|
"""
|
||||||
|
Yields the platform tags for an iOS system.
|
||||||
|
|
||||||
Yields the :attr:`~Tag.platform` tags for iOS.
|
:param version: A two-item tuple specifying the iOS version to generate
|
||||||
|
platform tags for. Defaults to the current iOS version.
|
||||||
:param tuple version: A two-item tuple representing the version of iOS.
|
:param multiarch: The CPU architecture+ABI to generate platform tags for -
|
||||||
Defaults to the current system's version.
|
(the value used by `sys.implementation._multiarch` e.g.,
|
||||||
:param str multiarch: The CPU architecture+ABI to be used. This should be in
|
`arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current
|
||||||
the format by ``sys.implementation._multiarch`` (e.g.,
|
multiarch value.
|
||||||
``arm64_iphoneos`` or ``x86_64_iphonesimulator``).
|
|
||||||
Defaults to the current system's multiarch value.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Behavior of this method is undefined if invoked on non-iOS platforms
|
|
||||||
without providing explicit version and multiarch arguments.
|
|
||||||
"""
|
"""
|
||||||
if version is None:
|
if version is None:
|
||||||
# if iOS is the current platform, ios_ver *must* be defined. However,
|
# if iOS is the current platform, ios_ver *must* be defined. However,
|
||||||
@@ -782,22 +586,13 @@ def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
|
|||||||
yield f"linux_{arch}"
|
yield f"linux_{arch}"
|
||||||
|
|
||||||
|
|
||||||
def _emscripten_platforms() -> Iterator[str]:
|
|
||||||
pyemscripten_platform_version = sysconfig.get_config_var(
|
|
||||||
"PYEMSCRIPTEN_PLATFORM_VERSION"
|
|
||||||
)
|
|
||||||
if pyemscripten_platform_version:
|
|
||||||
yield f"pyemscripten_{pyemscripten_platform_version}_wasm32"
|
|
||||||
yield from _generic_platforms()
|
|
||||||
|
|
||||||
|
|
||||||
def _generic_platforms() -> Iterator[str]:
|
def _generic_platforms() -> Iterator[str]:
|
||||||
yield _normalize_string(sysconfig.get_platform())
|
yield _normalize_string(sysconfig.get_platform())
|
||||||
|
|
||||||
|
|
||||||
def platform_tags() -> Iterator[str]:
|
def platform_tags() -> Iterator[str]:
|
||||||
"""
|
"""
|
||||||
Yields the :attr:`~Tag.platform` tags for the running interpreter.
|
Provides the platform tags for this installation.
|
||||||
"""
|
"""
|
||||||
if platform.system() == "Darwin":
|
if platform.system() == "Darwin":
|
||||||
return mac_platforms()
|
return mac_platforms()
|
||||||
@@ -807,8 +602,6 @@ def platform_tags() -> Iterator[str]:
|
|||||||
return android_platforms()
|
return android_platforms()
|
||||||
elif platform.system() == "Linux":
|
elif platform.system() == "Linux":
|
||||||
return _linux_platforms()
|
return _linux_platforms()
|
||||||
elif platform.system() == "Emscripten":
|
|
||||||
return _emscripten_platforms()
|
|
||||||
else:
|
else:
|
||||||
return _generic_platforms()
|
return _generic_platforms()
|
||||||
|
|
||||||
@@ -819,8 +612,6 @@ def interpreter_name() -> str:
|
|||||||
|
|
||||||
Some implementations have a reserved, two-letter abbreviation which will
|
Some implementations have a reserved, two-letter abbreviation which will
|
||||||
be returned when appropriate.
|
be returned when appropriate.
|
||||||
|
|
||||||
This typically acts as the prefix to the :attr:`~Tag.interpreter` tag.
|
|
||||||
"""
|
"""
|
||||||
name = sys.implementation.name
|
name = sys.implementation.name
|
||||||
return INTERPRETER_SHORT_NAMES.get(name) or name
|
return INTERPRETER_SHORT_NAMES.get(name) or name
|
||||||
@@ -828,14 +619,14 @@ def interpreter_name() -> str:
|
|||||||
|
|
||||||
def interpreter_version(*, warn: bool = False) -> str:
|
def interpreter_version(*, warn: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the running interpreter's version.
|
Returns the version of the running interpreter.
|
||||||
|
|
||||||
This typically acts as the suffix to the :attr:`~Tag.interpreter` tag.
|
|
||||||
|
|
||||||
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
|
||||||
"""
|
"""
|
||||||
version = _get_config_var("py_version_nodot", warn=warn)
|
version = _get_config_var("py_version_nodot", warn=warn)
|
||||||
return str(version) if version else _version_nodot(sys.version_info[:2])
|
if version:
|
||||||
|
version = str(version)
|
||||||
|
else:
|
||||||
|
version = _version_nodot(sys.version_info[:2])
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
def _version_nodot(version: PythonVersion) -> str:
|
def _version_nodot(version: PythonVersion) -> str:
|
||||||
@@ -844,31 +635,10 @@ def _version_nodot(version: PythonVersion) -> str:
|
|||||||
|
|
||||||
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
||||||
"""
|
"""
|
||||||
Yields the sequence of tag triples that the running interpreter supports.
|
Returns the sequence of tag triples for the running interpreter.
|
||||||
|
|
||||||
The iterable is ordered so that the best-matching tag is first in the
|
The order of the sequence corresponds to priority order for the
|
||||||
sequence. The exact preferential order to tags is interpreter-specific, but
|
interpreter, from most to least important.
|
||||||
in general the tag importance is in the order of:
|
|
||||||
|
|
||||||
1. Interpreter
|
|
||||||
2. Platform
|
|
||||||
3. ABI
|
|
||||||
|
|
||||||
This order is due to the fact that an ABI is inherently tied to the
|
|
||||||
platform, but platform-specific code is not necessarily tied to the ABI. The
|
|
||||||
interpreter is the most important tag as it dictates basic support for any
|
|
||||||
wheel.
|
|
||||||
|
|
||||||
The function returns an iterable in order to allow for the possible
|
|
||||||
short-circuiting of tag generation if the entire sequence is not necessary
|
|
||||||
and tag calculation happens to be expensive.
|
|
||||||
|
|
||||||
:param bool warn: Whether warnings should be logged. Defaults to ``False``.
|
|
||||||
|
|
||||||
.. versionchanged:: 21.3
|
|
||||||
Added the `pp3-none-any` tag (:issue:`311`).
|
|
||||||
.. versionchanged:: 27.0
|
|
||||||
Added the `abi3t` tag (:issue:`1099`).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
interp_name = interpreter_name()
|
interp_name = interpreter_name()
|
||||||
@@ -884,49 +654,3 @@ def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
|
|||||||
else:
|
else:
|
||||||
interp = None
|
interp = None
|
||||||
yield from compatible_tags(interpreter=interp)
|
yield from compatible_tags(interpreter=interp)
|
||||||
|
|
||||||
|
|
||||||
def create_compatible_tags_selector(
|
|
||||||
tags: Iterable[Tag],
|
|
||||||
) -> Callable[[Iterable[tuple[_T, AbstractSet[Tag]]]], Iterator[_T]]:
|
|
||||||
"""Create a callable to select things compatible with supported tags.
|
|
||||||
|
|
||||||
This function accepts an ordered sequence of tags, with the preferred
|
|
||||||
tags first.
|
|
||||||
|
|
||||||
The returned callable accepts an iterable of tuples (thing, set[Tag]),
|
|
||||||
and returns an iterator of things, with the things with the best
|
|
||||||
matching tags first.
|
|
||||||
|
|
||||||
Example to select compatible wheel filenames:
|
|
||||||
|
|
||||||
>>> from packaging import tags
|
|
||||||
>>> from packaging.utils import parse_wheel_filename
|
|
||||||
>>> selector = tags.create_compatible_tags_selector(tags.sys_tags())
|
|
||||||
>>> filenames = ["foo-1.0-py3-none-any.whl", "foo-1.0-py2-none-any.whl"]
|
|
||||||
>>> list(selector([
|
|
||||||
... (filename, parse_wheel_filename(filename)[-1]) for filename in filenames
|
|
||||||
... ]))
|
|
||||||
['foo-1.0-py3-none-any.whl']
|
|
||||||
|
|
||||||
.. versionadded:: 26.1
|
|
||||||
"""
|
|
||||||
tag_ranks: dict[Tag, int] = {}
|
|
||||||
for rank, tag in enumerate(tags):
|
|
||||||
tag_ranks.setdefault(tag, rank) # ignore duplicate tags, keep first
|
|
||||||
supported_tags = tag_ranks.keys()
|
|
||||||
|
|
||||||
def selector(
|
|
||||||
tagged_things: Iterable[tuple[_T, AbstractSet[Tag]]],
|
|
||||||
) -> Iterator[_T]:
|
|
||||||
ranked_things: list[tuple[_T, int]] = []
|
|
||||||
for thing, thing_tags in tagged_things:
|
|
||||||
supported_thing_tags = thing_tags & supported_tags
|
|
||||||
if supported_thing_tags:
|
|
||||||
thing_rank = min(tag_ranks[t] for t in supported_thing_tags)
|
|
||||||
ranked_things.append((thing, thing_rank))
|
|
||||||
return iter(
|
|
||||||
thing for thing, _ in sorted(ranked_things, key=operator.itemgetter(1))
|
|
||||||
)
|
|
||||||
|
|
||||||
return selector
|
|
||||||
|
|||||||
+27
-160
@@ -4,36 +4,15 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import functools
|
||||||
import re
|
import re
|
||||||
from typing import NewType, Tuple, Union, cast
|
from typing import NewType, Tuple, Union, cast
|
||||||
|
|
||||||
from .tags import Tag, UnsortedTagsError, parse_tag
|
from .tags import Tag, parse_tag
|
||||||
from .version import InvalidVersion, Version, _TrimmedRelease
|
from .version import InvalidVersion, Version, _TrimmedRelease
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"BuildTag",
|
|
||||||
"InvalidName",
|
|
||||||
"InvalidSdistFilename",
|
|
||||||
"InvalidWheelFilename",
|
|
||||||
"NormalizedName",
|
|
||||||
"canonicalize_name",
|
|
||||||
"canonicalize_version",
|
|
||||||
"is_normalized_name",
|
|
||||||
"parse_sdist_filename",
|
|
||||||
"parse_wheel_filename",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def __dir__() -> list[str]:
|
|
||||||
return __all__
|
|
||||||
|
|
||||||
|
|
||||||
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
BuildTag = Union[Tuple[()], Tuple[int, str]]
|
||||||
|
|
||||||
NormalizedName = NewType("NormalizedName", str)
|
NormalizedName = NewType("NormalizedName", str)
|
||||||
"""
|
|
||||||
A :class:`typing.NewType` of :class:`str`, representing a normalized name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidName(ValueError):
|
class InvalidName(ValueError):
|
||||||
@@ -56,77 +35,33 @@ class InvalidSdistFilename(ValueError):
|
|||||||
|
|
||||||
# Core metadata spec for `Name`
|
# Core metadata spec for `Name`
|
||||||
_validate_regex = re.compile(
|
_validate_regex = re.compile(
|
||||||
r"[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]", re.IGNORECASE | re.ASCII
|
r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
|
||||||
)
|
)
|
||||||
_normalized_regex = re.compile(r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]", re.ASCII)
|
_canonicalize_regex = re.compile(r"[-_.]+")
|
||||||
|
_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$")
|
||||||
# PEP 427: The build number must start with a digit.
|
# PEP 427: The build number must start with a digit.
|
||||||
_build_tag_regex = re.compile(r"(\d+)(.*)", re.ASCII)
|
_build_tag_regex = re.compile(r"(\d+)(.*)")
|
||||||
|
|
||||||
|
|
||||||
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
|
||||||
"""
|
if validate and not _validate_regex.match(name):
|
||||||
This function takes a valid Python package or extra name, and returns the
|
|
||||||
normalized form of it.
|
|
||||||
|
|
||||||
The return type is typed as :class:`NormalizedName`. This allows type
|
|
||||||
checkers to help require that a string has passed through this function
|
|
||||||
before use.
|
|
||||||
|
|
||||||
If **validate** is true, then the function will check if **name** is a valid
|
|
||||||
distribution name before normalizing.
|
|
||||||
|
|
||||||
:param str name: The name to normalize.
|
|
||||||
:param bool validate: Check whether the name is a valid distribution name.
|
|
||||||
:raises InvalidName: If **validate** is true and the name is not an
|
|
||||||
acceptable distribution name.
|
|
||||||
|
|
||||||
>>> from packaging.utils import canonicalize_name
|
|
||||||
>>> canonicalize_name("Django")
|
|
||||||
'django'
|
|
||||||
>>> canonicalize_name("oslo.concurrency")
|
|
||||||
'oslo-concurrency'
|
|
||||||
>>> canonicalize_name("requests")
|
|
||||||
'requests'
|
|
||||||
"""
|
|
||||||
if validate and not _validate_regex.fullmatch(name):
|
|
||||||
raise InvalidName(f"name is invalid: {name!r}")
|
raise InvalidName(f"name is invalid: {name!r}")
|
||||||
# Ensure all ``.`` and ``_`` are ``-``
|
# This is taken from PEP 503.
|
||||||
# Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
|
value = _canonicalize_regex.sub("-", name).lower()
|
||||||
# Much faster than re, and even faster than str.translate
|
return cast(NormalizedName, value)
|
||||||
value = name.lower().replace("_", "-").replace(".", "-")
|
|
||||||
# Condense repeats (faster than regex)
|
|
||||||
while "--" in value:
|
|
||||||
value = value.replace("--", "-")
|
|
||||||
return cast("NormalizedName", value)
|
|
||||||
|
|
||||||
|
|
||||||
def is_normalized_name(name: str) -> bool:
|
def is_normalized_name(name: str) -> bool:
|
||||||
"""
|
return _normalized_regex.match(name) is not None
|
||||||
Check if a name is already normalized (i.e. :func:`canonicalize_name` would
|
|
||||||
roundtrip to the same value).
|
|
||||||
|
|
||||||
:param str name: The name to check.
|
|
||||||
|
|
||||||
>>> from packaging.utils import is_normalized_name
|
|
||||||
>>> is_normalized_name("requests")
|
|
||||||
True
|
|
||||||
>>> is_normalized_name("Django")
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
return _normalized_regex.fullmatch(name) is not None
|
|
||||||
|
|
||||||
|
|
||||||
|
@functools.singledispatch
|
||||||
def canonicalize_version(
|
def canonicalize_version(
|
||||||
version: Version | str, *, strip_trailing_zero: bool = True
|
version: Version | str, *, strip_trailing_zero: bool = True
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Return a canonical form of a version as a string.
|
"""
|
||||||
|
Return a canonical form of a version as a string.
|
||||||
|
|
||||||
This function takes a string representing a package version (or a
|
|
||||||
:class:`~packaging.version.Version` instance), and returns the
|
|
||||||
normalized form of it. By default, it strips trailing zeros from
|
|
||||||
the release segment.
|
|
||||||
|
|
||||||
>>> from packaging.utils import canonicalize_version
|
|
||||||
>>> canonicalize_version('1.0.1')
|
>>> canonicalize_version('1.0.1')
|
||||||
'1.0.1'
|
'1.0.1'
|
||||||
|
|
||||||
@@ -142,63 +77,23 @@ def canonicalize_version(
|
|||||||
|
|
||||||
>>> canonicalize_version('foo bar baz')
|
>>> canonicalize_version('foo bar baz')
|
||||||
'foo bar baz'
|
'foo bar baz'
|
||||||
|
|
||||||
>>> canonicalize_version('1.4.0.0.0')
|
|
||||||
'1.4'
|
|
||||||
"""
|
"""
|
||||||
if isinstance(version, str):
|
return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version)
|
||||||
try:
|
|
||||||
version = Version(version)
|
|
||||||
except InvalidVersion:
|
@canonicalize_version.register
|
||||||
return str(version)
|
def _(version: str, *, strip_trailing_zero: bool = True) -> str:
|
||||||
return str(_TrimmedRelease(version) if strip_trailing_zero else version)
|
try:
|
||||||
|
parsed = Version(version)
|
||||||
|
except InvalidVersion:
|
||||||
|
# Legacy versions cannot be normalized
|
||||||
|
return version
|
||||||
|
return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero)
|
||||||
|
|
||||||
|
|
||||||
def parse_wheel_filename(
|
def parse_wheel_filename(
|
||||||
filename: str,
|
filename: str,
|
||||||
*,
|
|
||||||
validate_order: bool = False,
|
|
||||||
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
|
) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
|
||||||
"""
|
|
||||||
This function takes the filename of a wheel file, and parses it,
|
|
||||||
returning a tuple of name, version, build number, and tags.
|
|
||||||
|
|
||||||
The name part of the tuple is normalized and typed as
|
|
||||||
:class:`NormalizedName`. The version portion is an instance of
|
|
||||||
:class:`~packaging.version.Version`. The build number is ``()`` if
|
|
||||||
there is no build number in the wheel filename, otherwise a
|
|
||||||
two-item tuple of an integer for the leading digits and
|
|
||||||
a string for the rest of the build number. The tags portion is a
|
|
||||||
frozen set of :class:`~packaging.tags.Tag` instances (as the tag
|
|
||||||
string format allows multiple tags to be combined into a single
|
|
||||||
string).
|
|
||||||
|
|
||||||
If **validate_order** is true, compressed tag set components are
|
|
||||||
checked to be in sorted order as required by PEP 425.
|
|
||||||
|
|
||||||
:param str filename: The name of the wheel file.
|
|
||||||
:param bool validate_order: Check whether compressed tag set components
|
|
||||||
are in sorted order.
|
|
||||||
:raises InvalidWheelFilename: If the filename in question
|
|
||||||
does not follow the :ref:`wheel specification
|
|
||||||
<pypug:binary-distribution-format>`.
|
|
||||||
|
|
||||||
>>> from packaging.utils import parse_wheel_filename
|
|
||||||
>>> from packaging.tags import Tag
|
|
||||||
>>> from packaging.version import Version
|
|
||||||
>>> name, ver, build, tags = parse_wheel_filename("foo-1.0-py3-none-any.whl")
|
|
||||||
>>> name
|
|
||||||
'foo'
|
|
||||||
>>> ver == Version('1.0')
|
|
||||||
True
|
|
||||||
>>> tags == {Tag("py3", "none", "any")}
|
|
||||||
True
|
|
||||||
>>> not build
|
|
||||||
True
|
|
||||||
|
|
||||||
.. versionadded:: 26.1
|
|
||||||
The *validate_order* parameter.
|
|
||||||
"""
|
|
||||||
if not filename.endswith(".whl"):
|
if not filename.endswith(".whl"):
|
||||||
raise InvalidWheelFilename(
|
raise InvalidWheelFilename(
|
||||||
f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
|
f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
|
||||||
@@ -232,42 +127,14 @@ def parse_wheel_filename(
|
|||||||
raise InvalidWheelFilename(
|
raise InvalidWheelFilename(
|
||||||
f"Invalid build number: {build_part} in {filename!r}"
|
f"Invalid build number: {build_part} in {filename!r}"
|
||||||
)
|
)
|
||||||
build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
|
build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2)))
|
||||||
else:
|
else:
|
||||||
build = ()
|
build = ()
|
||||||
tag_str = parts[-1]
|
tags = parse_tag(parts[-1])
|
||||||
try:
|
|
||||||
tags = parse_tag(tag_str, validate_order=validate_order)
|
|
||||||
except UnsortedTagsError:
|
|
||||||
raise InvalidWheelFilename(
|
|
||||||
f"Invalid wheel filename (compressed tag set components must be in "
|
|
||||||
f"sorted order per PEP 425): {filename!r}"
|
|
||||||
) from None
|
|
||||||
return (name, version, build, tags)
|
return (name, version, build, tags)
|
||||||
|
|
||||||
|
|
||||||
def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
|
def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
|
||||||
"""
|
|
||||||
This function takes the filename of a sdist file (as specified
|
|
||||||
in the `Source distribution format`_ documentation), and parses
|
|
||||||
it, returning a tuple of the normalized name and version as
|
|
||||||
represented by an instance of :class:`~packaging.version.Version`.
|
|
||||||
|
|
||||||
:param str filename: The name of the sdist file.
|
|
||||||
:raises InvalidSdistFilename: If the filename does not end
|
|
||||||
with an sdist extension (``.zip`` or ``.tar.gz``), or if it does not
|
|
||||||
contain a dash separating the name and the version of the distribution.
|
|
||||||
|
|
||||||
>>> from packaging.utils import parse_sdist_filename
|
|
||||||
>>> from packaging.version import Version
|
|
||||||
>>> name, ver = parse_sdist_filename("foo-1.0.tar.gz")
|
|
||||||
>>> name
|
|
||||||
'foo'
|
|
||||||
>>> ver == Version('1.0')
|
|
||||||
True
|
|
||||||
|
|
||||||
.. _Source distribution format: https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-name
|
|
||||||
"""
|
|
||||||
if filename.endswith(".tar.gz"):
|
if filename.endswith(".tar.gz"):
|
||||||
file_stem = filename[: -len(".tar.gz")]
|
file_stem = filename[: -len(".tar.gz")]
|
||||||
elif filename.endswith(".zip"):
|
elif filename.endswith(".zip"):
|
||||||
|
|||||||
+154
-803
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
|||||||
uv
|
pip
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
pathspec-0.11.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
pathspec-0.11.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
pathspec-0.11.2.dist-info/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
|
pathspec-0.11.2.dist-info/LICENSE,sha256=-rPda9qyJvHAhjCx3ZF-Efy07F4eAg4sFvg6ChOGPoU,16726
|
||||||
pathspec-0.11.2.dist-info/METADATA,sha256=SxnZo-5WRH5npmxwSmYWT1DThQTSIfanQM8_-j8ye1g,19563
|
pathspec-0.11.2.dist-info/METADATA,sha256=SxnZo-5WRH5npmxwSmYWT1DThQTSIfanQM8_-j8ye1g,19563
|
||||||
pathspec-0.11.2.dist-info/RECORD,,
|
pathspec-0.11.2.dist-info/RECORD,,
|
||||||
pathspec-0.11.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
pathspec-0.11.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
pathspec-0.11.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
pathspec-0.11.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
||||||
pathspec/__init__.py,sha256=7SXysmS-FbGnfonqXtaSm6aUKdepQCXdvd4ArWAMJak,1630
|
pathspec/__init__.py,sha256=7SXysmS-FbGnfonqXtaSm6aUKdepQCXdvd4ArWAMJak,1630
|
||||||
|
pathspec/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
pathspec/__pycache__/_meta.cpython-311.pyc,,
|
||||||
|
pathspec/__pycache__/gitignore.cpython-311.pyc,,
|
||||||
|
pathspec/__pycache__/pathspec.cpython-311.pyc,,
|
||||||
|
pathspec/__pycache__/pattern.cpython-311.pyc,,
|
||||||
|
pathspec/__pycache__/util.cpython-311.pyc,,
|
||||||
pathspec/_meta.py,sha256=KkXyQhYw9KfMMlZeEuw6TV5Ar7qn_Y9yF4jTBeiJ-pQ,2223
|
pathspec/_meta.py,sha256=KkXyQhYw9KfMMlZeEuw6TV5Ar7qn_Y9yF4jTBeiJ-pQ,2223
|
||||||
pathspec/gitignore.py,sha256=nHZA92AltTIfCLf1i4uwvvXeEdfvbNBjetogn0ueGJM,3895
|
pathspec/gitignore.py,sha256=nHZA92AltTIfCLf1i4uwvvXeEdfvbNBjetogn0ueGJM,3895
|
||||||
pathspec/pathspec.py,sha256=O8oFAbo71uvwFWZZm7c2iFy0nzClyEcxOFJHg_EL8WQ,9530
|
pathspec/pathspec.py,sha256=O8oFAbo71uvwFWZZm7c2iFy0nzClyEcxOFJHg_EL8WQ,9530
|
||||||
pathspec/pattern.py,sha256=HVpwUuGMAW7WOtgPOkZUmJY-lg84BtWVvkVXcc_ha28,5784
|
pathspec/pattern.py,sha256=HVpwUuGMAW7WOtgPOkZUmJY-lg84BtWVvkVXcc_ha28,5784
|
||||||
pathspec/patterns/__init__.py,sha256=vAzIEqBc2KsvWsiszsLCeYQwQVWXIHzbHNgq5TNrPdk,302
|
pathspec/patterns/__init__.py,sha256=vAzIEqBc2KsvWsiszsLCeYQwQVWXIHzbHNgq5TNrPdk,302
|
||||||
|
pathspec/patterns/__pycache__/__init__.cpython-311.pyc,,
|
||||||
|
pathspec/patterns/__pycache__/gitwildmatch.cpython-311.pyc,,
|
||||||
pathspec/patterns/gitwildmatch.py,sha256=7f8zEBMvySzznrm7oo_geFV0NnmxUucZaYQxtsnMBQ8,12438
|
pathspec/patterns/gitwildmatch.py,sha256=7f8zEBMvySzznrm7oo_geFV0NnmxUucZaYQxtsnMBQ8,12438
|
||||||
pathspec/py.typed,sha256=wq7wwDeyBungK6DsiV4O-IujgKzARwHz94uQshdpdEU,68
|
pathspec/py.typed,sha256=wq7wwDeyBungK6DsiV4O-IujgKzARwHz94uQshdpdEU,68
|
||||||
pathspec/util.py,sha256=8w65a_vDtw3eCyIY5LVQ8EgUGekLwdIBne5yORdIoOQ,20273
|
pathspec/util.py,sha256=8w65a_vDtw3eCyIY5LVQ8EgUGekLwdIBne5yORdIoOQ,20273
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
uv
|
pip
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user