Compare commits
157
Commits
0.4.5
...
33cf282b0a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33cf282b0a | ||
|
|
35a4357551 | ||
|
|
f5bd79f067 | ||
|
|
a39aee1b9d | ||
|
|
4ca1b0cce9 | ||
|
|
6051260a8d | ||
|
|
dfe15dd3db | ||
|
|
5d1c402d2a | ||
|
|
88f834ae79 | ||
|
|
3b233389c8 | ||
|
|
67bd9de18a | ||
|
|
495b7b7a07 | ||
|
|
8b39c4d250 | ||
|
|
49e8cdd7df | ||
|
|
f60c4563e4 | ||
|
|
3ad3847045 | ||
|
|
41f331d4c4 | ||
|
|
91ce762570 | ||
|
|
d60d3ec942 | ||
|
|
e52722e353 | ||
|
|
a8bf55882e | ||
|
|
377bddf579 | ||
|
|
cdc1cb4805 | ||
|
|
34430097d8 | ||
|
|
dcf447ed4f | ||
|
|
783a9aa7d7 | ||
|
|
63638d6b3b | ||
|
|
af8cd58fda | ||
|
|
df0f3d7a61 | ||
|
|
72906a1930 | ||
|
|
068cf2d1c3 | ||
|
|
ad18a3dcf3 | ||
|
|
bd4b17825c | ||
|
|
5d6182e91f | ||
|
|
63e9c429bd | ||
|
|
7fdafe484e | ||
|
|
5cba6763f1 | ||
|
|
e20bb85ac6 | ||
|
|
98a0b2197d | ||
|
|
579847b56d | ||
|
|
618d6b478b | ||
|
|
480978d6bb | ||
|
|
04798328bd | ||
|
|
ae835089b6 | ||
|
|
0ed1e887fc | ||
|
|
12b7b62aa5 | ||
|
|
7263b6f530 | ||
|
|
c0635e48ea | ||
|
|
8fb98d2f40 | ||
|
|
f24b280edf | ||
|
|
b61cff3b3b | ||
|
|
cf62044d3e | ||
|
|
feba88212f | ||
|
|
aeebb5c964 | ||
|
|
b141818251 | ||
|
|
72f894b05d | ||
|
|
0a26cb5c0f | ||
|
|
b3cac5c292 | ||
|
|
ef558c3aa3 | ||
|
|
9d5ef23162 | ||
|
|
f659db1147 | ||
|
|
c25bf79427 | ||
|
|
b059a75205 | ||
|
|
a90e15514b | ||
|
|
2c2579319d | ||
|
|
b38fab1428 | ||
|
|
bc745622b9 | ||
|
|
0bb4ef731e | ||
|
|
27e2b3bf6e | ||
|
|
f55c3ba904 | ||
|
|
cb430a5553 | ||
|
|
637a28ad82 | ||
|
|
499df9b51d | ||
|
|
31ccae9a40 | ||
|
|
9b373e85b7 | ||
|
|
0c5f4ea6fb | ||
|
|
793b35f39f | ||
|
|
3780fc27dc | ||
|
|
6541830ef6 | ||
|
|
509b13e99f | ||
|
|
396cfdc4ec | ||
|
|
ccd22e4343 | ||
|
|
7d83ae1012 | ||
|
|
029eaebd57 | ||
|
|
2a2db8f217 | ||
|
|
8be9aae05d | ||
|
|
6bded783a1 | ||
|
|
f5c61267e3 | ||
|
|
b680c01989 | ||
|
|
1abf89fe56 | ||
|
|
6a051a6dee | ||
|
|
d22d897d23 | ||
|
|
7c834176a3 | ||
|
|
72be83c282 | ||
|
|
fa5ea00384 | ||
|
|
a8b4fd9d1e | ||
|
|
35d0f20fba | ||
|
|
440ebc4df5 | ||
|
|
1b48cc1983 | ||
|
|
6cde05b786 | ||
|
|
cde58db3d7 | ||
|
|
3613391c23 | ||
|
|
894a5b8b52 | ||
|
|
c644ac9bd3 | ||
|
|
fd5b1f2e71 | ||
|
|
57c65221d3 | ||
|
|
528c7a6b9e | ||
|
|
2d034838b4 | ||
|
|
f0ed8ac708 | ||
|
|
6a30c1ab53 | ||
|
|
6d404f26f2 | ||
|
|
afc675f47f | ||
|
|
d2936ec5f6 | ||
|
|
8b44b41d68 | ||
|
|
5124c12bc6 | ||
|
|
32061809a7 | ||
|
|
f762c42e00 | ||
|
|
4ccdf9e9f3 | ||
|
|
b86d4f7b58 | ||
|
|
0318532308 | ||
|
|
5e2e85830b | ||
|
|
98e2d104a8 | ||
|
|
44fd772151 | ||
|
|
d90f9b5425 | ||
|
|
fed8b84b46 | ||
|
|
ed045aa459 | ||
|
|
8da61abfce | ||
|
|
0ea2cbec87 | ||
|
|
d99d7c6bf6 | ||
|
|
3a7c8ea774 | ||
|
|
9a56aca6ef | ||
|
|
4d79f268b8 | ||
|
|
c6f0758b97 | ||
|
|
c34dac847a | ||
|
|
94181497bd | ||
|
|
d391b5dd79 | ||
|
|
666c537f07 | ||
|
|
e842d9d74e | ||
|
|
c9a07ae1b9 | ||
|
|
64fa802cd6 | ||
|
|
7984b1648c | ||
|
|
490994dbab | ||
|
|
16608cb994 | ||
|
|
250ec0c227 | ||
|
|
f78b4836f9 | ||
|
|
3fbe4f9243 | ||
|
|
5a020f7d07 | ||
|
|
cf6a39b51d | ||
|
|
b264f20baf | ||
|
|
4ef697190a | ||
|
|
896e9f6c1f | ||
|
|
52a2b2f774 | ||
|
|
ee5240c861 | ||
|
|
61fb0bc5d2 | ||
|
|
95ddae6d13 | ||
|
|
a6f616a89d | ||
|
|
632fc32c3e |
@@ -1,13 +1,15 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
build_and_publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Checkout branch from release
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.release.target_commitish }}
|
||||
fetch-depth: 0
|
||||
- name: GitHub Tag Name example
|
||||
run: |
|
||||
echo "Tag name from GITHUB_REF_NAME: $GITHUB_REF_NAME"
|
||||
@@ -33,9 +35,41 @@ jobs:
|
||||
git config user.name "${{ vars.USERNAME_GIT }}"
|
||||
git config user.email "${{ vars.EMAIL_GIT }}"
|
||||
git add package.json
|
||||
git commit -m "Update version to ${{ github.ref_name }}"
|
||||
git push origin HEAD:main
|
||||
- name: Publish to Visual Studio Marketplace
|
||||
git commit -m "Update version to ${{ github.ref_name }}" || echo "No changes to commit."
|
||||
|
||||
# Get current branch; if detached, resolve to a remote branch that contains this commit
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||
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
|
||||
CANDIDATE='${{ github.event.release.target_commitish }}'
|
||||
if [[ "$CANDIDATE" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
BRANCH="$(git branch -r --contains "$GITHUB_SHA" | sed -n 's|.*origin/||p' | head -n1)"
|
||||
else
|
||||
BRANCH="$CANDIDATE"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$BRANCH" ]]; then
|
||||
echo "Could not determine branch for push."; git branch -a; exit 1
|
||||
fi
|
||||
|
||||
git push origin HEAD:refs/heads/$BRANCH
|
||||
- name: Publish to Visual Studio Marketplace (Pre-Release)
|
||||
if: ${{ github.event.release.prerelease }}
|
||||
run: vsce publish --pre-release
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCODE_MARKETPALCE }}
|
||||
- name: Publish to Visual Studio Marketplace (Stable)
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
run: vsce publish
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCODE_MARKETPALCE }}
|
||||
- name: Pack Extension for Release
|
||||
run: vsce pack
|
||||
- name: Upload VSIX to Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: "nx-post-support-*.vsix"
|
||||
# generate_release_notes: true # Automatische Release Notes
|
||||
# draft: false # N
|
||||
# prerelease: false
|
||||
|
||||
@@ -9,3 +9,4 @@ __pycache__
|
||||
.nox
|
||||
*.g4
|
||||
.antlr
|
||||
.claude
|
||||
Vendored
+35
-12
@@ -10,13 +10,21 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/client/**/*.js"],
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"${env:TEMP}/nx-post-support-vscode-debug/test/test.tcl"
|
||||
],
|
||||
"cwd": "${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"outFiles": ["${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js"],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": [
|
||||
"${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"autoAttachChildProcesses": true,
|
||||
"preLaunchTask": {
|
||||
"type": "npm",
|
||||
"script": "watch"
|
||||
}
|
||||
"preLaunchTask": "NX Post Support: Compile Debug"
|
||||
},
|
||||
{
|
||||
"name": "Python Attach",
|
||||
@@ -34,10 +42,24 @@
|
||||
"name": "Debug Extension (hidden)",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||
"outFiles": ["${workspaceFolder}/client/**/*.js"],
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"${env:TEMP}/nx-post-support-vscode-debug/test/test.tcl"
|
||||
],
|
||||
"cwd": "${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"outFiles": ["${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js"],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": [
|
||||
"${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"env": {
|
||||
"USE_DEBUGPY": "True"
|
||||
"USE_DEBUGPY": "True",
|
||||
"NXPS_DEBUG_HOST": "127.0.0.1",
|
||||
"NXPS_DEBUG_PORT": "5678"
|
||||
},
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
@@ -49,8 +71,9 @@
|
||||
"name": "Python debug server (hidden)",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"listen": { "host": "localhost", "port": 5678 },
|
||||
"justMyCode": true,
|
||||
"listen": { "host": "127.0.0.1", "port": 5678 },
|
||||
"justMyCode": false,
|
||||
"logToFile": true,
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
"group": "",
|
||||
@@ -63,7 +86,7 @@
|
||||
"name": "Debug Extension and Python",
|
||||
"configurations": ["Python debug server (hidden)", "Debug Extension (hidden)"],
|
||||
"stopAll": true,
|
||||
"preLaunchTask": "npm: watch",
|
||||
"preLaunchTask": "NX Post Support: Compile Debug",
|
||||
"presentation": {
|
||||
"hidden": false,
|
||||
"group": "",
|
||||
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WorkspaceRoot,
|
||||
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DebugRoot
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function ConvertFrom-ExtendedWindowsPath {
|
||||
param([string]$Path)
|
||||
|
||||
if ($Path.StartsWith("\\?\UNC\", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return "\\" + $Path.Substring(8)
|
||||
}
|
||||
if ($Path.StartsWith("\\?\", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return $Path.Substring(4)
|
||||
}
|
||||
return $Path
|
||||
}
|
||||
|
||||
$workspacePath = ConvertFrom-ExtendedWindowsPath $WorkspaceRoot
|
||||
$workspaceItem = Get-Item -LiteralPath $workspacePath
|
||||
if (-not $workspaceItem.PSIsContainer) {
|
||||
throw "Workspace root is not a directory: $workspacePath"
|
||||
}
|
||||
$workspacePath = $workspaceItem.FullName
|
||||
|
||||
$debugPath = ConvertFrom-ExtendedWindowsPath $DebugRoot
|
||||
$tempPath = [System.IO.Path]::GetFullPath([System.IO.Path]::GetTempPath()).TrimEnd("\")
|
||||
$debugParent = [System.IO.Path]::GetFullPath((Split-Path -Parent $debugPath)).TrimEnd("\")
|
||||
if (-not $debugParent.Equals($tempPath, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "Debug alias must be located directly below the user temp directory: $debugPath"
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $debugPath) {
|
||||
$debugItem = Get-Item -LiteralPath $debugPath -Force
|
||||
if ($debugItem.LinkType -ne "Junction") {
|
||||
throw "Debug alias exists but is not a junction: $debugPath"
|
||||
}
|
||||
|
||||
$currentTarget = (Get-Item -LiteralPath $debugItem.Target).FullName
|
||||
if (-not $currentTarget.Equals($workspacePath, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
# Removing a junction removes only the link, never the target directory.
|
||||
Remove-Item -LiteralPath $debugPath -Force
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $debugPath)) {
|
||||
New-Item -ItemType Junction -Path $debugPath -Target $workspacePath | Out-Null
|
||||
}
|
||||
|
||||
Write-Output "Debug extension path: $debugPath -> $workspacePath"
|
||||
& npm.cmd --prefix $workspacePath run compile:debug
|
||||
exit $LASTEXITCODE
|
||||
Vendored
+2
-1
@@ -2,6 +2,7 @@
|
||||
"ruff.configuration": {
|
||||
"lint": {
|
||||
"extend-ignore": ["F821", "E402"]
|
||||
}
|
||||
},
|
||||
"line-length": 200
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "NX Post Support: Compile Debug",
|
||||
"type": "process",
|
||||
"command": "powershell.exe",
|
||||
"args": [
|
||||
"-NoLogo",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy",
|
||||
"Bypass",
|
||||
"-Command",
|
||||
"& { param([string]$WorkspaceRoot, [string]$DebugRoot); $scriptRoot = $WorkspaceRoot; if ($scriptRoot.StartsWith('\\\\?\\')) { $scriptRoot = $scriptRoot.Substring(4) }; & (Join-Path $scriptRoot '.vscode\\prepare-debug.ps1') -WorkspaceRoot $WorkspaceRoot -DebugRoot $DebugRoot; exit $LASTEXITCODE }",
|
||||
"${workspaceFolder}",
|
||||
"${env:TEMP}\\nx-post-support-vscode-debug"
|
||||
],
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,7 +11,11 @@ esbuild.js
|
||||
.gitea
|
||||
.venv/**
|
||||
.nox/**
|
||||
server/.venv/**
|
||||
server/.nox/**
|
||||
**/__pycache__/**
|
||||
**/requirements.txt
|
||||
**/requirements.in
|
||||
**/server/src/_debug_server.py
|
||||
server/noxfile.py
|
||||
client/**
|
||||
@@ -1,3 +1,9 @@
|
||||
## Unreleased
|
||||
|
||||
- Add signature help for custom TCL procedures and built-in NX/MOM procedures
|
||||
- Clean stale TCL indexes on close, delete, and rename operations
|
||||
- Make background parsing and index updates thread-safe
|
||||
|
||||
## [0.0.1]
|
||||
|
||||
- Initial release
|
||||
@@ -13,3 +19,11 @@
|
||||
## [0.2.0]
|
||||
|
||||
- Add DEF File Support
|
||||
|
||||
## [2026.6.100]
|
||||
|
||||
- Fix several bugs
|
||||
|
||||
## [2026.6.200]
|
||||
|
||||
- Fix foramtting bug
|
||||
|
||||
@@ -1,21 +1,661 @@
|
||||
MIT License
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (c) 2025 Christoph
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Preamble
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
nx-post-support
|
||||
Copyright (C) 2025 Christoph
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# NX Postprocessor Support
|
||||
|
||||
A comprehensive VS Code extension providing language support for NX CAM postprocessor development, including CDL, TCL, and DEF files.
|
||||
|
||||
## Features
|
||||
|
||||
- **Syntax Highlighting** - Full syntax highlighting for CDL, TCL, and DEF files
|
||||
- **Code Formatting** - Automatic code formatting for all supported file types
|
||||
- **Multi-language Support** - Supports NX CDL, TCL, and DEF file formats
|
||||
- **Intelligent Code Analysis** - Linting and error detection for postprocessor code
|
||||
- **Auto-completion** - Context-aware code completion for faster development
|
||||
- **Signature Help** - Shows parameters and documentation for custom and NX procedures
|
||||
|
||||
## Supported File Types
|
||||
|
||||
- `.cdl` - NX User Defined Event File
|
||||
- `.tcl` - NX TCL postprocessor files
|
||||
- `.def` - NX Definition files
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install from the VS Code Marketplace
|
||||
2. Install Python 3.11 or higher
|
||||
3. Open any `.cdl`, `.tcl`, or `.def` file
|
||||
4. The extension will automatically activate and provide language support
|
||||
|
||||
## Configuration
|
||||
|
||||
The extension can be configured through VS Code settings:
|
||||
|
||||
- `nx-post-support.interpreter` - Specify custom Python interpreter path for the language server
|
||||
- `nx-post-support.formatter` - Enable/disable the TCL formatter (default: false)
|
||||
- `nx-post-support.inlayHint` - Enable/disable inlay Hints (default: true)
|
||||
|
||||
## Usage
|
||||
|
||||
Simply open any supported file type and enjoy:
|
||||
|
||||
- Syntax highlighting
|
||||
- Error detection and linting
|
||||
- Code completion
|
||||
- Code formatting (Format Document command)
|
||||
- Hover information
|
||||
- Signature help while entering procedure arguments
|
||||
|
||||
## Development and debugging
|
||||
|
||||
Install the root and client dependencies before the first debug session:
|
||||
|
||||
```powershell
|
||||
npm install
|
||||
npm install --prefix client
|
||||
```
|
||||
|
||||
Use one of the checked-in VS Code launch configurations:
|
||||
|
||||
- **Run Extension** debugs the TypeScript extension host.
|
||||
- **Debug Extension and Python** debugs both the TypeScript extension and the
|
||||
Python language server. This is the recommended configuration for LSP work.
|
||||
- **Python Attach** attaches manually to an already running Python process.
|
||||
|
||||
The launch configuration creates a fresh non-minified bundle with embedded
|
||||
source maps and opens `test/test.tcl` so the extension activates immediately.
|
||||
For combined debugging, the Python adapter listens on `127.0.0.1:5678`; the
|
||||
language server waits for that adapter before initialization. The NX
|
||||
Postprocessor Support output channel reports `Python debug mode: enabled` and
|
||||
shows `_debug_server.py` in the server command when the debug path is active.
|
||||
|
||||
## Contributing
|
||||
|
||||
This extension is actively maintained. For issues or feature requests, please visit our [repository](https://git.cbsk-tech.de/Christoph/nx_post_support.git).
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0 License - see LICENSE file for details.
|
||||
|
||||
Generated
+22
@@ -14,6 +14,7 @@
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/vscode": "^1.96.0"
|
||||
},
|
||||
@@ -21,6 +22,27 @@
|
||||
"vscode": "^1.96.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/fs-extra": {
|
||||
"version": "11.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
|
||||
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/jsonfile": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsonfile": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
|
||||
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/vscode": "^1.96.0"
|
||||
}
|
||||
|
||||
@@ -137,6 +137,102 @@ export function hoverCdlHandler(document: vscode.TextDocument, position: vscode.
|
||||
return undefined
|
||||
}
|
||||
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
||||
}
|
||||
|
||||
export function cdlEventAtPosition(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position
|
||||
): string | undefined {
|
||||
const line = document.lineAt(position.line).text
|
||||
const match = /^\s*EVENT\s+([^\s{]+)/.exec(line)
|
||||
if (!match) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const declarationStart = match.index
|
||||
const eventEnd = line.indexOf(match[1], match.index) + match[1].length
|
||||
if (position.character < declarationStart || position.character > eventEnd) {
|
||||
return undefined
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
export async function definitionCdlEventHandler(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.Location[] | undefined> {
|
||||
const eventName = cdlEventAtPosition(document, position)
|
||||
if (!eventName) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const handlerName = `MOM_${eventName}`
|
||||
try {
|
||||
const symbols = await vscode.commands.executeCommand<vscode.SymbolInformation[]>(
|
||||
"vscode.executeWorkspaceSymbolProvider",
|
||||
handlerName
|
||||
)
|
||||
const indexedLocations = (symbols || [])
|
||||
.filter(
|
||||
(symbol) =>
|
||||
symbol.kind === vscode.SymbolKind.Function &&
|
||||
(symbol.name === handlerName ||
|
||||
symbol.name.endsWith(`::${handlerName}`))
|
||||
)
|
||||
.map((symbol) => symbol.location)
|
||||
if (indexedLocations.length > 0) {
|
||||
return indexedLocations
|
||||
}
|
||||
} catch {
|
||||
// The Tcl language server may still be starting; use the file fallback below.
|
||||
}
|
||||
|
||||
const declaration = new RegExp(
|
||||
`^\\s*proc\\s+(?:::)?${escapeRegExp(handlerName)}(?=\\s|\\{)`
|
||||
)
|
||||
const tclFiles = await vscode.workspace.findFiles(
|
||||
"**/*.tcl",
|
||||
"**/{.git,.nox,.venv,dist,node_modules,out}/**"
|
||||
)
|
||||
const locations: vscode.Location[] = []
|
||||
|
||||
for (const uri of tclFiles) {
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined
|
||||
}
|
||||
let tclDocument: vscode.TextDocument
|
||||
try {
|
||||
tclDocument = await vscode.workspace.openTextDocument(uri)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
for (let lineNumber = 0; lineNumber < tclDocument.lineCount; lineNumber++) {
|
||||
const line = tclDocument.lineAt(lineNumber).text
|
||||
const match = declaration.exec(line)
|
||||
if (!match) {
|
||||
continue
|
||||
}
|
||||
const start = line.indexOf(handlerName, match.index)
|
||||
locations.push(
|
||||
new vscode.Location(
|
||||
uri,
|
||||
new vscode.Range(
|
||||
lineNumber,
|
||||
start,
|
||||
lineNumber,
|
||||
start + handlerName.length
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return locations.length > 0 ? locations : undefined
|
||||
}
|
||||
|
||||
export function tclDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] {
|
||||
const symbols: vscode.DocumentSymbol[] = []
|
||||
const lines = document.getText().split("\n")
|
||||
@@ -271,3 +367,180 @@ export function tclDocumentSymbolProvider(document: vscode.TextDocument): vscode
|
||||
|
||||
return collectSymbols(rootScope)
|
||||
}
|
||||
|
||||
export function cdlDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] {
|
||||
const lines = document.getText().split("\n")
|
||||
|
||||
interface Scope {
|
||||
symbol: vscode.DocumentSymbol
|
||||
startLine: number
|
||||
}
|
||||
|
||||
const root = new vscode.DocumentSymbol(
|
||||
"root",
|
||||
"",
|
||||
vscode.SymbolKind.Namespace,
|
||||
new vscode.Range(0, 0, lines.length, 0),
|
||||
new vscode.Range(0, 0, 0, 0)
|
||||
)
|
||||
root.children = []
|
||||
const stack: Scope[] = [{ symbol: root, startLine: 0 }]
|
||||
|
||||
let pending: { name: string; kind: vscode.SymbolKind; startLine: number } | null = null
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const trimmed = line.trim()
|
||||
|
||||
const createSymbol = () => {
|
||||
if (!pending) {
|
||||
return
|
||||
}
|
||||
const startRange = new vscode.Range(
|
||||
pending.startLine,
|
||||
0,
|
||||
pending.startLine,
|
||||
lines[pending.startLine].length
|
||||
)
|
||||
const symbol = new vscode.DocumentSymbol(
|
||||
pending.name,
|
||||
"",
|
||||
pending.kind,
|
||||
startRange,
|
||||
startRange
|
||||
)
|
||||
symbol.children = []
|
||||
stack[stack.length - 1].symbol.children?.push(symbol)
|
||||
stack.push({ symbol, startLine: pending.startLine })
|
||||
pending = null
|
||||
}
|
||||
|
||||
if (pending && trimmed.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("}")) {
|
||||
const scope = stack.pop()
|
||||
if (scope) {
|
||||
scope.symbol.range = new vscode.Range(scope.startLine, 0, i, line.length)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
let match
|
||||
if ((match = /^\s*EVENT\s+(\w+)/.exec(line))) {
|
||||
pending = {
|
||||
name: match[1],
|
||||
kind: vscode.SymbolKind.Event,
|
||||
startLine: i
|
||||
}
|
||||
if (line.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
} else if ((match = /^\s*PARAM\s+(\w+)/.exec(line))) {
|
||||
pending = {
|
||||
name: match[1],
|
||||
kind: vscode.SymbolKind.Field,
|
||||
startLine: i
|
||||
}
|
||||
if (line.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root.children || []
|
||||
}
|
||||
|
||||
export function defDocumentSymbolProvider(document: vscode.TextDocument): vscode.DocumentSymbol[] {
|
||||
const lines = document.getText().split("\n")
|
||||
|
||||
interface Scope {
|
||||
symbol: vscode.DocumentSymbol
|
||||
startLine: number
|
||||
}
|
||||
|
||||
const root = new vscode.DocumentSymbol(
|
||||
"root",
|
||||
"",
|
||||
vscode.SymbolKind.Namespace,
|
||||
new vscode.Range(0, 0, lines.length, 0),
|
||||
new vscode.Range(0, 0, 0, 0)
|
||||
)
|
||||
root.children = []
|
||||
const stack: Scope[] = [{ symbol: root, startLine: 0 }]
|
||||
|
||||
let pending: { name: string; kind: vscode.SymbolKind; startLine: number } | null = null
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const trimmed = line.trim()
|
||||
|
||||
const createSymbol = () => {
|
||||
if (!pending) {
|
||||
return
|
||||
}
|
||||
const startRange = new vscode.Range(
|
||||
pending.startLine,
|
||||
0,
|
||||
pending.startLine,
|
||||
lines[pending.startLine].length
|
||||
)
|
||||
const symbol = new vscode.DocumentSymbol(
|
||||
pending.name,
|
||||
"",
|
||||
pending.kind,
|
||||
startRange,
|
||||
startRange
|
||||
)
|
||||
symbol.children = []
|
||||
stack[stack.length - 1].symbol.children?.push(symbol)
|
||||
stack.push({ symbol, startLine: pending.startLine })
|
||||
pending = null
|
||||
}
|
||||
|
||||
if (pending && trimmed.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("}")) {
|
||||
const scope = stack.pop()
|
||||
if (scope) {
|
||||
scope.symbol.range = new vscode.Range(scope.startLine, 0, i, line.length)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
let match
|
||||
if ((match = /^\s*FORMATTING\b/.exec(line))) {
|
||||
pending = {
|
||||
name: "FORMATTING",
|
||||
kind: vscode.SymbolKind.Module,
|
||||
startLine: i
|
||||
}
|
||||
if (line.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
} else if ((match = /^\s*ADDRESS\s+(\w+)/.exec(line))) {
|
||||
pending = {
|
||||
name: match[1],
|
||||
kind: vscode.SymbolKind.Field,
|
||||
startLine: i
|
||||
}
|
||||
if (line.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
} else if ((match = /^\s*BLOCK_TEMPLATE\s+(\w+)/.exec(line))) {
|
||||
pending = {
|
||||
name: match[1],
|
||||
kind: vscode.SymbolKind.Function,
|
||||
startLine: i
|
||||
}
|
||||
if (line.includes("{")) {
|
||||
createSymbol()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root.children || []
|
||||
}
|
||||
|
||||
+50
-18
@@ -2,7 +2,7 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import * as fsapi from "fs-extra"
|
||||
import { Disposable, env, LogOutputChannel } from "vscode"
|
||||
import { Disposable, env, LogOutputChannel, workspace } from "vscode"
|
||||
import { State } from "vscode-languageclient"
|
||||
import {
|
||||
LanguageClient,
|
||||
@@ -24,6 +24,13 @@ import { isVirtualWorkspace } from "./vscodeapi"
|
||||
|
||||
export type IInitOptions = { settings: ISettings[]; globalSettings: ISettings }
|
||||
|
||||
let _disposables: Disposable[] = []
|
||||
|
||||
export function disposeServerResources(): void {
|
||||
_disposables.forEach((disposable) => disposable.dispose())
|
||||
_disposables = []
|
||||
}
|
||||
|
||||
async function createServer(
|
||||
settings: ISettings,
|
||||
serverId: string,
|
||||
@@ -32,13 +39,26 @@ async function createServer(
|
||||
initializationOptions: IInitOptions
|
||||
): Promise<LanguageClient> {
|
||||
const command = settings.interpreter[0]
|
||||
if (!command) {
|
||||
throw new Error("No Python interpreter is configured for the language server.")
|
||||
}
|
||||
const cwd = settings.cwd
|
||||
|
||||
// Set debugger path needed for debugging python code.
|
||||
const newEnv = { ...process.env }
|
||||
const debuggerPath = await getDebuggerPath()
|
||||
const isDebugScript = await fsapi.pathExists(DEBUG_SERVER_SCRIPT_PATH)
|
||||
if (newEnv.USE_DEBUGPY && debuggerPath) {
|
||||
const debugRequested = newEnv.USE_DEBUGPY?.toLowerCase() === "true"
|
||||
if (debugRequested && !isDebugScript) {
|
||||
throw new Error(`Python debug bootstrap not found: ${DEBUG_SERVER_SCRIPT_PATH}`)
|
||||
}
|
||||
|
||||
const debuggerPath = debugRequested ? await getDebuggerPath() : undefined
|
||||
if (debugRequested && !debuggerPath) {
|
||||
throw new Error(
|
||||
"Python debugging was requested, but the Python Debugger extension did not provide debugpy."
|
||||
)
|
||||
}
|
||||
if (debugRequested && debuggerPath) {
|
||||
newEnv.DEBUGPY_PATH = debuggerPath
|
||||
} else {
|
||||
newEnv.USE_DEBUGPY = "False"
|
||||
@@ -50,10 +70,13 @@ async function createServer(
|
||||
// Set notification type
|
||||
newEnv.LS_SHOW_NOTIFICATION = settings.showNotifications
|
||||
|
||||
const args =
|
||||
newEnv.USE_DEBUGPY === "False" || !isDebugScript
|
||||
? settings.interpreter.slice(1).concat([SERVER_SCRIPT_PATH])
|
||||
: settings.interpreter.slice(1).concat([DEBUG_SERVER_SCRIPT_PATH])
|
||||
const serverScript = debugRequested ? DEBUG_SERVER_SCRIPT_PATH : SERVER_SCRIPT_PATH
|
||||
const interpreterArgs = settings.interpreter.slice(1)
|
||||
if (debugRequested && !interpreterArgs.includes("-Xfrozen_modules=off")) {
|
||||
interpreterArgs.push("-Xfrozen_modules=off")
|
||||
}
|
||||
const args = interpreterArgs.concat([serverScript])
|
||||
traceInfo(`Python debug mode: ${debugRequested ? "enabled" : "disabled"}`)
|
||||
traceInfo(`Server run command: ${[command, ...args].join(" ")}`)
|
||||
|
||||
const serverOptions: ServerOptions = {
|
||||
@@ -63,6 +86,7 @@ async function createServer(
|
||||
}
|
||||
|
||||
// Options to control the language client
|
||||
const tclFileWatcher = workspace.createFileSystemWatcher("**/*.tcl")
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
// Register the server for python documents
|
||||
documentSelector: isVirtualWorkspace()
|
||||
@@ -76,13 +100,16 @@ async function createServer(
|
||||
outputChannel: outputChannel,
|
||||
traceOutputChannel: outputChannel,
|
||||
revealOutputChannelOn: RevealOutputChannelOn.Never,
|
||||
synchronize: {
|
||||
fileEvents: tclFileWatcher
|
||||
},
|
||||
initializationOptions
|
||||
}
|
||||
|
||||
_disposables.push(tclFileWatcher)
|
||||
return new LanguageClient(serverId, serverName, serverOptions, clientOptions)
|
||||
}
|
||||
|
||||
let _disposables: Disposable[] = []
|
||||
export async function restartServer(
|
||||
serverId: string,
|
||||
serverName: string,
|
||||
@@ -92,16 +119,22 @@ export async function restartServer(
|
||||
if (lsClient) {
|
||||
traceInfo(`Server: Stop requested`)
|
||||
await lsClient.stop()
|
||||
_disposables.forEach((d) => d.dispose())
|
||||
_disposables = []
|
||||
disposeServerResources()
|
||||
}
|
||||
const projectRoot = await getProjectRoot()
|
||||
const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true)
|
||||
|
||||
const newLSClient = await createServer(workspaceSetting, serverId, serverName, outputChannel, {
|
||||
try {
|
||||
const newLSClient = await createServer(
|
||||
workspaceSetting,
|
||||
serverId,
|
||||
serverName,
|
||||
outputChannel,
|
||||
{
|
||||
settings: await getExtensionSettings(serverId, true),
|
||||
globalSettings: await getGlobalSettings(serverId, false)
|
||||
})
|
||||
}
|
||||
)
|
||||
traceInfo(`Server: Start requested.`)
|
||||
_disposables.push(
|
||||
newLSClient.onDidChangeState((e) => {
|
||||
@@ -118,14 +151,13 @@ export async function restartServer(
|
||||
}
|
||||
})
|
||||
)
|
||||
try {
|
||||
await newLSClient.start()
|
||||
} catch (ex) {
|
||||
traceError(`Server: Start failed: ${ex}`)
|
||||
return undefined
|
||||
}
|
||||
|
||||
const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel)
|
||||
await newLSClient.setTrace(level)
|
||||
return newLSClient
|
||||
} catch (ex) {
|
||||
traceError(`Server: Start failed: ${ex}`)
|
||||
disposeServerResources()
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,9 @@ export async function getWorkspaceSettings(
|
||||
path: resolveVariables(config.get<string[]>(`path`) ?? [], workspace),
|
||||
interpreter: resolveVariables(interpreter, workspace),
|
||||
importStrategy: config.get<string>(`importStrategy`) ?? "useBundled",
|
||||
showNotifications: config.get<string>(`showNotifications`) ?? "off"
|
||||
showNotifications: config.get<string>(`showNotifications`) ?? "off",
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true
|
||||
}
|
||||
return workspaceSetting
|
||||
}
|
||||
@@ -109,7 +111,9 @@ export async function getGlobalSettings(
|
||||
path: getGlobalValue<string[]>(config, "path", []),
|
||||
interpreter: interpreter,
|
||||
importStrategy: getGlobalValue<string>(config, "importStrategy", "useBundled"),
|
||||
showNotifications: getGlobalValue<string>(config, "showNotifications", "off")
|
||||
showNotifications: getGlobalValue<string>(config, "showNotifications", "off"),
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true
|
||||
}
|
||||
return setting
|
||||
}
|
||||
@@ -123,7 +127,9 @@ export function checkIfConfigurationChanged(
|
||||
`${namespace}.path`,
|
||||
`${namespace}.interpreter`,
|
||||
`${namespace}.importStrategy`,
|
||||
`${namespace}.showNotifications`
|
||||
`${namespace}.showNotifications`,
|
||||
`${namespace}.formatter`,
|
||||
`${namespace}.inlayHint`
|
||||
]
|
||||
const changed = settings.map((s) => e.affectsConfiguration(s))
|
||||
return changed.includes(true)
|
||||
|
||||
+70
-12
@@ -12,7 +12,9 @@ import {
|
||||
formatDefFile,
|
||||
isFirstLineMachine,
|
||||
diagnosticHandler,
|
||||
tclDocumentSymbolProvider
|
||||
cdlDocumentSymbolProvider,
|
||||
defDocumentSymbolProvider,
|
||||
definitionCdlEventHandler
|
||||
} from "./common/handlers"
|
||||
import { registerLogger, traceError, traceLog, traceVerbose } from "./common/log/logging"
|
||||
import {
|
||||
@@ -22,13 +24,13 @@ import {
|
||||
onDidChangePythonInterpreter,
|
||||
resolveInterpreter
|
||||
} from "./common/python"
|
||||
import { restartServer } from "./common/server"
|
||||
import { disposeServerResources, restartServer } from "./common/server"
|
||||
import { checkIfConfigurationChanged, getInterpreterFromSetting } from "./common/settings"
|
||||
import { loadServerDefaults } from "./common/setup"
|
||||
import { getLSClientTraceLevel } from "./common/utilities"
|
||||
import { createOutputChannel, onDidChangeConfiguration, registerCommand } from "./common/vscodeapi"
|
||||
|
||||
let client: LanguageClient
|
||||
let client: LanguageClient | undefined
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
// This is required to get server name and module. This should be
|
||||
@@ -36,6 +38,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const serverInfo = loadServerDefaults()
|
||||
const serverName = serverInfo.name
|
||||
const serverId = serverInfo.module
|
||||
const pythonDebugMode = process.env.USE_DEBUGPY?.toLowerCase() === "true"
|
||||
|
||||
// Setup logging
|
||||
const outputChannel = createOutputChannel(serverName)
|
||||
@@ -60,7 +63,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
traceLog(`Module: ${serverInfo.module}`)
|
||||
traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`)
|
||||
|
||||
const runServer = async () => {
|
||||
const runServerImpl = async () => {
|
||||
const interpreter = getInterpreterFromSetting(serverId)
|
||||
if (interpreter && interpreter.length > 0) {
|
||||
if (checkVersion(await resolveInterpreter(interpreter))) {
|
||||
@@ -89,10 +92,25 @@ 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
|
||||
}
|
||||
|
||||
if (!pythonDebugMode) {
|
||||
context.subscriptions.push(
|
||||
onDidChangePythonInterpreter(async () => {
|
||||
await runServer()
|
||||
}),
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
|
||||
if (checkIfConfigurationChanged(e, serverId)) {
|
||||
await runServer()
|
||||
@@ -104,6 +122,15 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
)
|
||||
|
||||
setImmediate(async () => {
|
||||
if (pythonDebugMode) {
|
||||
// A debugpy listen session is attached to exactly one process. Do not
|
||||
// subscribe to interpreter changes during startup, as the Python
|
||||
// extension can emit a duplicate event and restart that process.
|
||||
traceLog("Python debug mode: starting one stable server session")
|
||||
await runServer()
|
||||
return
|
||||
}
|
||||
|
||||
const interpreter = getInterpreterFromSetting(serverId)
|
||||
if (interpreter === undefined || interpreter.length === 0) {
|
||||
traceLog(`Python extension loading`)
|
||||
@@ -114,6 +141,10 @@ 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(
|
||||
{ scheme: "file", language: "cdl" },
|
||||
@@ -162,6 +193,16 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
)
|
||||
context.subscriptions.push(hoverCdlProvider)
|
||||
|
||||
const definitionCdlEventProvider = vscode.languages.registerDefinitionProvider(
|
||||
{ scheme: "file", language: "cdl" },
|
||||
{
|
||||
provideDefinition(document, position, token) {
|
||||
return definitionCdlEventHandler(document, position, token)
|
||||
}
|
||||
}
|
||||
)
|
||||
context.subscriptions.push(definitionCdlEventProvider)
|
||||
|
||||
const formatDefProvider = vscode.languages.registerDocumentFormattingEditProvider(
|
||||
{ scheme: "file", language: "def" },
|
||||
{
|
||||
@@ -179,11 +220,26 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(formatDefProvider)
|
||||
|
||||
const tclOutlineProvider = vscode.languages.registerDocumentSymbolProvider(
|
||||
{ scheme: "file", language: "tcl" },
|
||||
{ provideDocumentSymbols: tclDocumentSymbolProvider }
|
||||
const cdlSymbolProvider = vscode.languages.registerDocumentSymbolProvider(
|
||||
{ scheme: "file", language: "cdl" },
|
||||
{
|
||||
provideDocumentSymbols(document: vscode.TextDocument) {
|
||||
return cdlDocumentSymbolProvider(document)
|
||||
}
|
||||
}
|
||||
)
|
||||
context.subscriptions.push(tclOutlineProvider)
|
||||
context.subscriptions.push(cdlSymbolProvider)
|
||||
|
||||
// Outline now provided by the language server (Document Symbols). Removing TS provider.
|
||||
const defSymbolProvider = vscode.languages.registerDocumentSymbolProvider(
|
||||
{ scheme: "file", language: "def" },
|
||||
{
|
||||
provideDocumentSymbols(document: vscode.TextDocument) {
|
||||
return defDocumentSymbolProvider(document)
|
||||
}
|
||||
}
|
||||
)
|
||||
context.subscriptions.push(defSymbolProvider)
|
||||
|
||||
// Diagnostics collection
|
||||
const diagnosticCollectionCdl = vscode.languages.createDiagnosticCollection("cdl")
|
||||
@@ -191,6 +247,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(diagnosticCollectionCdl, diagnosticCollectionDef)
|
||||
|
||||
// Check if the first line of the CDL file contains "MACHINE"
|
||||
context.subscriptions.push(
|
||||
vscode.workspace.onDidOpenTextDocument((document) => {
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
if (document.languageId === "cdl") {
|
||||
@@ -199,8 +256,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}),
|
||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||
const document = event.document
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
@@ -211,11 +267,13 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> | undefined {
|
||||
if (!client) {
|
||||
disposeServerResources()
|
||||
return undefined
|
||||
}
|
||||
return client.stop()
|
||||
return client.stop().finally(disposeServerResources)
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,19 +1,21 @@
|
||||
const esbuild = require("esbuild")
|
||||
const path = require("path")
|
||||
|
||||
const production = process.argv.includes("--production")
|
||||
const watch = process.argv.includes("--watch")
|
||||
|
||||
async function main() {
|
||||
const ctx = await esbuild.context({
|
||||
absWorkingDir: __dirname,
|
||||
entryPoints: ["client/src/extension.ts"],
|
||||
bundle: true,
|
||||
format: "cjs",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
sourcesContent: !production,
|
||||
platform: "node",
|
||||
// outdir: "out",
|
||||
outfile: "./dist/extension.js",
|
||||
outfile: path.join(__dirname, "dist", "extension.js"),
|
||||
external: ["vscode"],
|
||||
logLevel: "silent",
|
||||
plugins: [
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -3,6 +3,13 @@
|
||||
// symbol used for single line comment. Remove this entry if your language does not support line comments
|
||||
"lineComment": "#"
|
||||
},
|
||||
"indentationRules": {
|
||||
// Ignore pure comment lines so they don't terminate folds inside Tcl brace blocks.
|
||||
"unIndentedLinePattern": "^\\s*#.*$",
|
||||
// Opening braces on their own line are common in NX Tcl.
|
||||
"increaseIndentPattern": "^((?!#).)*(\\{[^}\"']*)$",
|
||||
"decreaseIndentPattern": "^\\s*[\\}\\]\\)].*$"
|
||||
},
|
||||
// symbols used as brackets
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "nx-post-support",
|
||||
"version": "0.3.0",
|
||||
"version": "2025.9.200",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nx-post-support",
|
||||
"version": "0.3.0",
|
||||
"version": "2025.9.200",
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.96.0",
|
||||
"@vscode/vsce": "^3.2.1",
|
||||
|
||||
+21
-3
@@ -1,9 +1,13 @@
|
||||
{
|
||||
"name": "nx-post-support",
|
||||
"displayName": "NX Postprocessor Support",
|
||||
"description": "",
|
||||
"version": "0.4.4",
|
||||
"description": "VS Code extension for NX CAM postprocessor development with syntax highlighting, formatting, linting, and auto-completion for CDL, TCL, and DEF files",
|
||||
"version": "2026.6.201",
|
||||
"publisher": "Christoph",
|
||||
"icon": "images/nx-1.png",
|
||||
"extensionDependencies": [
|
||||
"ms-python.python"
|
||||
],
|
||||
"serverInfo": {
|
||||
"name": "NX Postprocessor Support",
|
||||
"module": "nx-post-support"
|
||||
@@ -16,7 +20,10 @@
|
||||
"keywords": [
|
||||
"cdl",
|
||||
"NX CDL",
|
||||
"UDE"
|
||||
"UDE",
|
||||
"Postprocessor",
|
||||
"NX CAM",
|
||||
"Siemens NX"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.96.0"
|
||||
@@ -79,6 +86,16 @@
|
||||
],
|
||||
"configuration": {
|
||||
"properties": {
|
||||
"nx-post-support.formatter": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Use the TCL formatter from `NX Postprocessor Support`"
|
||||
},
|
||||
"nx-post-support.inlayHint": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Use the Inlay Hints in from `NX Postprocessor Support`"
|
||||
},
|
||||
"nx-post-support.importStrategy": {
|
||||
"default": "useBundled",
|
||||
"description": "Defines where `NX Postprocessor Support` is imported from.",
|
||||
@@ -107,6 +124,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "node esbuild.js --production",
|
||||
"compile:debug": "node esbuild.js",
|
||||
"watch": "node esbuild.js --watch",
|
||||
"package": "node esbuild.js --production"
|
||||
},
|
||||
|
||||
@@ -176,7 +176,7 @@ def attrib(
|
||||
type: None = ...,
|
||||
converter: None = ...,
|
||||
factory: None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -200,7 +200,7 @@ def attrib(
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -223,7 +223,7 @@ def attrib(
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -246,7 +246,7 @@ def attrib(
|
||||
| tuple[_ConverterType]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -308,7 +308,7 @@ def attrs(
|
||||
match_args: bool = ...,
|
||||
unsafe_hash: bool | None = ...,
|
||||
) -> Callable[[_C], _C]: ...
|
||||
def fields(cls: type[AttrsInstance]) -> Any: ...
|
||||
def fields(cls: type[AttrsInstance] | AttrsInstance) -> Any: ...
|
||||
def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ...
|
||||
def validate(inst: AttrsInstance) -> None: ...
|
||||
def resolve_types(
|
||||
|
||||
@@ -10,7 +10,6 @@ from typing import _GenericAlias
|
||||
|
||||
|
||||
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_11_PLUS = sys.version_info[:2] >= (3, 11)
|
||||
PY_3_12_PLUS = sys.version_info[:2] >= (3, 12)
|
||||
@@ -18,10 +17,16 @@ PY_3_13_PLUS = sys.version_info[:2] >= (3, 13)
|
||||
PY_3_14_PLUS = sys.version_info[:2] >= (3, 14)
|
||||
|
||||
|
||||
if PY_3_14_PLUS: # pragma: no cover
|
||||
if PY_3_14_PLUS:
|
||||
import annotationlib
|
||||
|
||||
_get_annotations = annotationlib.get_annotations
|
||||
# We request forward-ref annotations to not break in the presence of
|
||||
# forward references.
|
||||
|
||||
def _get_annotations(cls):
|
||||
return annotationlib.get_annotations(
|
||||
cls, format=annotationlib.Format.FORWARDREF
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
|
||||
+45
-16
@@ -3,11 +3,28 @@
|
||||
|
||||
import copy
|
||||
|
||||
from ._compat import PY_3_9_PLUS, get_generic_base
|
||||
from ._compat import get_generic_base
|
||||
from ._make import _OBJ_SETATTR, NOTHING, fields
|
||||
from .exceptions import AttrsAttributeNotFoundError
|
||||
|
||||
|
||||
_ATOMIC_TYPES = frozenset(
|
||||
{
|
||||
type(None),
|
||||
bool,
|
||||
int,
|
||||
float,
|
||||
str,
|
||||
complex,
|
||||
bytes,
|
||||
type(...),
|
||||
type,
|
||||
range,
|
||||
property,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def asdict(
|
||||
inst,
|
||||
recurse=True,
|
||||
@@ -71,7 +88,10 @@ def asdict(
|
||||
v = value_serializer(inst, a, v)
|
||||
|
||||
if recurse is True:
|
||||
if has(v.__class__):
|
||||
value_type = type(v)
|
||||
if value_type in _ATOMIC_TYPES:
|
||||
rv[a.name] = v
|
||||
elif has(value_type):
|
||||
rv[a.name] = asdict(
|
||||
v,
|
||||
recurse=True,
|
||||
@@ -80,8 +100,8 @@ def asdict(
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
)
|
||||
elif isinstance(v, (tuple, list, set, frozenset)):
|
||||
cf = v.__class__ if retain_collection_types is True else list
|
||||
elif issubclass(value_type, (tuple, list, set, frozenset)):
|
||||
cf = value_type if retain_collection_types is True else list
|
||||
items = [
|
||||
_asdict_anything(
|
||||
i,
|
||||
@@ -101,7 +121,7 @@ def asdict(
|
||||
# Workaround for TypeError: cf.__new__() missing 1 required
|
||||
# positional argument (which appears, for a namedturle)
|
||||
rv[a.name] = cf(*items)
|
||||
elif isinstance(v, dict):
|
||||
elif issubclass(value_type, dict):
|
||||
df = dict_factory
|
||||
rv[a.name] = df(
|
||||
(
|
||||
@@ -142,7 +162,12 @@ def _asdict_anything(
|
||||
"""
|
||||
``asdict`` only works on attrs instances, this works on anything.
|
||||
"""
|
||||
if getattr(val.__class__, "__attrs_attrs__", None) is not None:
|
||||
val_type = type(val)
|
||||
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.
|
||||
rv = asdict(
|
||||
val,
|
||||
@@ -152,7 +177,7 @@ def _asdict_anything(
|
||||
retain_collection_types=retain_collection_types,
|
||||
value_serializer=value_serializer,
|
||||
)
|
||||
elif isinstance(val, (tuple, list, set, frozenset)):
|
||||
elif issubclass(val_type, (tuple, list, set, frozenset)):
|
||||
if retain_collection_types is True:
|
||||
cf = val.__class__
|
||||
elif is_key:
|
||||
@@ -173,7 +198,7 @@ def _asdict_anything(
|
||||
for i in val
|
||||
]
|
||||
)
|
||||
elif isinstance(val, dict):
|
||||
elif issubclass(val_type, dict):
|
||||
df = dict_factory
|
||||
rv = df(
|
||||
(
|
||||
@@ -253,8 +278,11 @@ def astuple(
|
||||
v = getattr(inst, a.name)
|
||||
if filter is not None and not filter(a, v):
|
||||
continue
|
||||
value_type = type(v)
|
||||
if recurse is True:
|
||||
if has(v.__class__):
|
||||
if value_type in _ATOMIC_TYPES:
|
||||
rv.append(v)
|
||||
elif has(value_type):
|
||||
rv.append(
|
||||
astuple(
|
||||
v,
|
||||
@@ -264,7 +292,7 @@ def astuple(
|
||||
retain_collection_types=retain,
|
||||
)
|
||||
)
|
||||
elif isinstance(v, (tuple, list, set, frozenset)):
|
||||
elif issubclass(value_type, (tuple, list, set, frozenset)):
|
||||
cf = v.__class__ if retain is True else list
|
||||
items = [
|
||||
(
|
||||
@@ -288,8 +316,8 @@ def astuple(
|
||||
# Workaround for TypeError: cf.__new__() missing 1 required
|
||||
# positional argument (which appears, for a namedturle)
|
||||
rv.append(cf(*items))
|
||||
elif isinstance(v, dict):
|
||||
df = v.__class__ if retain is True else dict
|
||||
elif issubclass(value_type, dict):
|
||||
df = value_type if retain is True else dict
|
||||
rv.append(
|
||||
df(
|
||||
(
|
||||
@@ -450,10 +478,11 @@ def resolve_types(
|
||||
if getattr(cls, "__attrs_types_resolved__", None) != cls:
|
||||
import typing
|
||||
|
||||
kwargs = {"globalns": globalns, "localns": localns}
|
||||
|
||||
if PY_3_9_PLUS:
|
||||
kwargs["include_extras"] = include_extras
|
||||
kwargs = {
|
||||
"globalns": globalns,
|
||||
"localns": localns,
|
||||
"include_extras": include_extras,
|
||||
}
|
||||
|
||||
hints = typing.get_type_hints(cls, **kwargs)
|
||||
for field in fields(cls) if attribs is None else attribs:
|
||||
|
||||
+394
-111
@@ -12,6 +12,7 @@ import linecache
|
||||
import sys
|
||||
import types
|
||||
import unicodedata
|
||||
import weakref
|
||||
|
||||
from collections.abc import Callable, Mapping
|
||||
from functools import cached_property
|
||||
@@ -113,7 +114,7 @@ def attrib(
|
||||
type=None,
|
||||
converter=None,
|
||||
factory=None,
|
||||
kw_only=False,
|
||||
kw_only=None,
|
||||
eq=None,
|
||||
order=None,
|
||||
on_setattr=None,
|
||||
@@ -156,6 +157,9 @@ def attrib(
|
||||
*eq*, *order*, and *cmp* also accept a custom callable
|
||||
.. versionchanged:: 21.1.0 *cmp* undeprecated
|
||||
.. 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(
|
||||
cmp, eq, order, True
|
||||
@@ -373,7 +377,12 @@ def _collect_base_attrs_broken(cls, taken_attr_names):
|
||||
|
||||
|
||||
def _transform_attrs(
|
||||
cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer
|
||||
cls,
|
||||
these,
|
||||
auto_attribs,
|
||||
kw_only,
|
||||
collect_by_mro,
|
||||
field_transformer,
|
||||
) -> _Attributes:
|
||||
"""
|
||||
Transform all `_CountingAttr`s on a class into `Attribute`s.
|
||||
@@ -428,8 +437,15 @@ def _transform_attrs(
|
||||
)
|
||||
|
||||
fca = Attribute.from_counting_attr
|
||||
no = ClassProps.KeywordOnly.NO
|
||||
own_attrs = [
|
||||
fca(attr_name, ca, anns.get(attr_name)) for attr_name, ca in ca_list
|
||||
fca(
|
||||
attr_name,
|
||||
ca,
|
||||
kw_only is not no,
|
||||
anns.get(attr_name),
|
||||
)
|
||||
for attr_name, ca in ca_list
|
||||
]
|
||||
|
||||
if collect_by_mro:
|
||||
@@ -441,12 +457,21 @@ def _transform_attrs(
|
||||
cls, {a.name for a in own_attrs}
|
||||
)
|
||||
|
||||
if kw_only:
|
||||
if kw_only is ClassProps.KeywordOnly.FORCE:
|
||||
own_attrs = [a.evolve(kw_only=True) for a in own_attrs]
|
||||
base_attrs = [a.evolve(kw_only=True) for a in base_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:
|
||||
attrs = tuple(field_transformer(cls, attrs))
|
||||
|
||||
@@ -464,13 +489,12 @@ def _transform_attrs(
|
||||
if had_default is False and a.default is not NOTHING:
|
||||
had_default = True
|
||||
|
||||
# Resolve default field alias after executing field_transformer.
|
||||
# This allows field_transformer to differentiate between explicit vs
|
||||
# default aliases and supply their own defaults.
|
||||
# Resolve default field alias for any new attributes that the
|
||||
# field_transformer may have added without setting an alias.
|
||||
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)
|
||||
|
||||
# Create AttrsClass *after* applying the field_transformer since it may
|
||||
# add or remove attributes!
|
||||
@@ -553,7 +577,7 @@ def _frozen_delattrs(self, name):
|
||||
"""
|
||||
Attached to frozen classes as __delattr__.
|
||||
"""
|
||||
if isinstance(self, BaseException) and name in ("__notes__",):
|
||||
if isinstance(self, BaseException) and name == "__notes__":
|
||||
BaseException.__delattr__(self, name)
|
||||
return
|
||||
|
||||
@@ -651,38 +675,31 @@ class _ClassBuilder:
|
||||
self,
|
||||
cls: type,
|
||||
these,
|
||||
slots,
|
||||
frozen,
|
||||
weakref_slot,
|
||||
getstate_setstate,
|
||||
auto_attribs,
|
||||
kw_only,
|
||||
cache_hash,
|
||||
is_exc,
|
||||
collect_by_mro,
|
||||
on_setattr,
|
||||
has_custom_setattr,
|
||||
field_transformer,
|
||||
auto_attribs: bool,
|
||||
props: ClassProps,
|
||||
has_custom_setattr: bool,
|
||||
):
|
||||
attrs, base_attrs, base_map = _transform_attrs(
|
||||
cls,
|
||||
these,
|
||||
auto_attribs,
|
||||
kw_only,
|
||||
collect_by_mro,
|
||||
field_transformer,
|
||||
props.kw_only,
|
||||
props.collected_fields_by_mro,
|
||||
props.field_transformer,
|
||||
)
|
||||
|
||||
self._cls = cls
|
||||
self._cls_dict = dict(cls.__dict__) if slots else {}
|
||||
self._cls_dict = dict(cls.__dict__) if props.is_slotted else {}
|
||||
self._attrs = attrs
|
||||
self._base_names = {a.name for a in base_attrs}
|
||||
self._base_attr_map = base_map
|
||||
self._attr_names = tuple(a.name for a in attrs)
|
||||
self._slots = slots
|
||||
self._frozen = frozen
|
||||
self._weakref_slot = weakref_slot
|
||||
self._cache_hash = cache_hash
|
||||
self._slots = props.is_slotted
|
||||
self._frozen = props.is_frozen
|
||||
self._weakref_slot = props.has_weakref_slot
|
||||
self._cache_hash = (
|
||||
props.hashability is ClassProps.Hashability.HASHABLE_CACHED
|
||||
)
|
||||
self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False))
|
||||
self._pre_init_has_args = False
|
||||
if self._has_pre_init:
|
||||
@@ -693,20 +710,21 @@ class _ClassBuilder:
|
||||
self._pre_init_has_args = len(pre_init_signature.parameters) > 1
|
||||
self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False))
|
||||
self._delete_attribs = not bool(these)
|
||||
self._is_exc = is_exc
|
||||
self._on_setattr = on_setattr
|
||||
self._is_exc = props.is_exception
|
||||
self._on_setattr = props.on_setattr_hook
|
||||
|
||||
self._has_custom_setattr = has_custom_setattr
|
||||
self._wrote_own_setattr = False
|
||||
|
||||
self._cls_dict["__attrs_attrs__"] = self._attrs
|
||||
self._cls_dict["__attrs_props__"] = props
|
||||
|
||||
if frozen:
|
||||
if props.is_frozen:
|
||||
self._cls_dict["__setattr__"] = _frozen_setattrs
|
||||
self._cls_dict["__delattr__"] = _frozen_delattrs
|
||||
|
||||
self._wrote_own_setattr = True
|
||||
elif on_setattr in (
|
||||
elif self._on_setattr in (
|
||||
_DEFAULT_ON_SETATTR,
|
||||
setters.validate,
|
||||
setters.convert,
|
||||
@@ -722,18 +740,18 @@ class _ClassBuilder:
|
||||
break
|
||||
if (
|
||||
(
|
||||
on_setattr == _DEFAULT_ON_SETATTR
|
||||
self._on_setattr == _DEFAULT_ON_SETATTR
|
||||
and not (has_validator or has_converter)
|
||||
)
|
||||
or (on_setattr == setters.validate and not has_validator)
|
||||
or (on_setattr == setters.convert and not has_converter)
|
||||
or (self._on_setattr == setters.validate and not has_validator)
|
||||
or (self._on_setattr == setters.convert and not has_converter)
|
||||
):
|
||||
# If class-level on_setattr is set to convert + validate, but
|
||||
# there's no field to convert or validate, pretend like there's
|
||||
# no on_setattr.
|
||||
self._on_setattr = None
|
||||
|
||||
if getstate_setstate:
|
||||
if props.added_pickling:
|
||||
(
|
||||
self._cls_dict["__getstate__"],
|
||||
self._cls_dict["__setstate__"],
|
||||
@@ -784,6 +802,7 @@ class _ClassBuilder:
|
||||
self._eval_snippets()
|
||||
if self._slots is True:
|
||||
cls = self._create_slots_class()
|
||||
self._cls.__attrs_base_of_slotted__ = weakref.ref(cls)
|
||||
else:
|
||||
cls = self._patch_original_class()
|
||||
if PY_3_10_PLUS:
|
||||
@@ -845,6 +864,10 @@ class _ClassBuilder:
|
||||
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__
|
||||
# (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
|
||||
@@ -1081,9 +1104,7 @@ class _ClassBuilder:
|
||||
return self
|
||||
|
||||
def add_replace(self):
|
||||
self._cls_dict["__replace__"] = self._add_method_dunders(
|
||||
lambda self, **changes: evolve(self, **changes)
|
||||
)
|
||||
self._cls_dict["__replace__"] = self._add_method_dunders(evolve)
|
||||
return self
|
||||
|
||||
def add_match_args(self):
|
||||
@@ -1326,6 +1347,7 @@ def attrs(
|
||||
field_transformer=None,
|
||||
match_args=True,
|
||||
unsafe_hash=None,
|
||||
force_kw_only=True,
|
||||
):
|
||||
r"""
|
||||
A class decorator that adds :term:`dunder methods` according to the
|
||||
@@ -1392,6 +1414,10 @@ def attrs(
|
||||
If a class has an *inherited* classmethod called
|
||||
``__attrs_init_subclass__``, it is executed after the class is created.
|
||||
.. 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:
|
||||
import warnings
|
||||
@@ -1413,6 +1439,7 @@ def attrs(
|
||||
on_setattr = setters.pipe(*on_setattr)
|
||||
|
||||
def wrap(cls):
|
||||
nonlocal hash
|
||||
is_frozen = frozen or _has_frozen_base_class(cls)
|
||||
is_exc = auto_exc is True and issubclass(cls, BaseException)
|
||||
has_own_setattr = auto_detect and _has_own_attribute(
|
||||
@@ -1423,84 +1450,112 @@ def attrs(
|
||||
msg = "Can't freeze a class with a custom __setattr__."
|
||||
raise ValueError(msg)
|
||||
|
||||
builder = _ClassBuilder(
|
||||
eq = not is_exc and _determine_whether_to_implement(
|
||||
cls, eq_, auto_detect, ("__eq__", "__ne__")
|
||||
)
|
||||
|
||||
Hashability = ClassProps.Hashability
|
||||
|
||||
if is_exc:
|
||||
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,
|
||||
these,
|
||||
slots,
|
||||
is_frozen,
|
||||
weakref_slot,
|
||||
_determine_whether_to_implement(
|
||||
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,
|
||||
getstate_setstate,
|
||||
auto_detect,
|
||||
("__getstate__", "__setstate__"),
|
||||
default=slots,
|
||||
),
|
||||
auto_attribs,
|
||||
kw_only,
|
||||
cache_hash,
|
||||
is_exc,
|
||||
collect_by_mro,
|
||||
on_setattr,
|
||||
has_own_setattr,
|
||||
field_transformer,
|
||||
on_setattr_hook=on_setattr,
|
||||
field_transformer=field_transformer,
|
||||
)
|
||||
|
||||
if _determine_whether_to_implement(
|
||||
cls, repr, auto_detect, ("__repr__",)
|
||||
):
|
||||
if not props.is_hashable and cache_hash:
|
||||
msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled."
|
||||
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)
|
||||
|
||||
if str is True:
|
||||
if props.added_str:
|
||||
builder.add_str()
|
||||
|
||||
eq = _determine_whether_to_implement(
|
||||
cls, eq_, auto_detect, ("__eq__", "__ne__")
|
||||
)
|
||||
if not is_exc and eq is True:
|
||||
if props.added_eq:
|
||||
builder.add_eq()
|
||||
if not is_exc and _determine_whether_to_implement(
|
||||
cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__")
|
||||
):
|
||||
if props.added_ordering:
|
||||
builder.add_order()
|
||||
|
||||
if not frozen:
|
||||
builder.add_setattr()
|
||||
|
||||
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.
|
||||
if props.is_hashable:
|
||||
builder.add_hash()
|
||||
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)
|
||||
elif props.hashability is Hashability.UNHASHABLE:
|
||||
builder.make_unhashable()
|
||||
|
||||
if _determine_whether_to_implement(
|
||||
cls, init, auto_detect, ("__init__",)
|
||||
):
|
||||
if props.added_init:
|
||||
builder.add_init()
|
||||
else:
|
||||
builder.add_attrs_init()
|
||||
@@ -1835,16 +1890,16 @@ def _add_repr(cls, ns=None, attrs=None):
|
||||
|
||||
def fields(cls):
|
||||
"""
|
||||
Return the tuple of *attrs* attributes for a class.
|
||||
Return the tuple of *attrs* attributes for a class or instance.
|
||||
|
||||
The tuple also allows accessing the fields by their names (see below for
|
||||
examples).
|
||||
|
||||
Args:
|
||||
cls (type): Class to introspect.
|
||||
cls (type): Class or instance to introspect.
|
||||
|
||||
Raises:
|
||||
TypeError: If *cls* is not a class.
|
||||
TypeError: If *cls* is neither a class nor an *attrs* instance.
|
||||
|
||||
attrs.exceptions.NotAnAttrsClassError:
|
||||
If *cls* is not an *attrs* class.
|
||||
@@ -1855,13 +1910,18 @@ def fields(cls):
|
||||
.. versionchanged:: 16.2.0 Returned tuple allows accessing the fields
|
||||
by name.
|
||||
.. versionchanged:: 23.1.0 Add support for generic classes.
|
||||
.. versionchanged:: 26.1.0 Add support for instances.
|
||||
"""
|
||||
generic_base = get_generic_base(cls)
|
||||
|
||||
if generic_base is None and not isinstance(cls, type):
|
||||
msg = "Passed object must be a class."
|
||||
type_ = type(cls)
|
||||
if getattr(type_, "__attrs_attrs__", None) is None:
|
||||
msg = "Passed object must be a class or attrs instance."
|
||||
raise TypeError(msg)
|
||||
|
||||
return fields(type_)
|
||||
|
||||
attrs = getattr(cls, "__attrs_attrs__", None)
|
||||
|
||||
if attrs is None:
|
||||
@@ -1967,7 +2027,7 @@ def _make_init_script(
|
||||
attr_dict[a.name] = a
|
||||
|
||||
if a.on_setattr is not None:
|
||||
if frozen is True:
|
||||
if frozen is True and a.on_setattr is not setters.NO_OP:
|
||||
msg = "Frozen classes can't use on_setattr."
|
||||
raise ValueError(msg)
|
||||
|
||||
@@ -2126,8 +2186,9 @@ def _attrs_to_init_script(
|
||||
)
|
||||
lines.extend(extra_lines)
|
||||
|
||||
args = []
|
||||
kw_only_args = []
|
||||
args = [] # Parameters in the definition of __init__
|
||||
pre_init_args = [] # Parameters in the call to __attrs_pre_init__
|
||||
kw_only_args = [] # Used for both 'args' and 'pre_init_args' above
|
||||
attrs_to_validate = []
|
||||
|
||||
# This is a dictionary of names to validator and converter callables.
|
||||
@@ -2205,6 +2266,7 @@ def _attrs_to_init_script(
|
||||
kw_only_args.append(arg)
|
||||
else:
|
||||
args.append(arg)
|
||||
pre_init_args.append(arg_name)
|
||||
|
||||
if converter is not None:
|
||||
lines.append(
|
||||
@@ -2224,6 +2286,7 @@ def _attrs_to_init_script(
|
||||
kw_only_args.append(arg)
|
||||
else:
|
||||
args.append(arg)
|
||||
pre_init_args.append(arg_name)
|
||||
lines.append(f"if {arg_name} is not NOTHING:")
|
||||
|
||||
init_factory_name = _INIT_FACTORY_PAT % (a.name,)
|
||||
@@ -2266,6 +2329,7 @@ def _attrs_to_init_script(
|
||||
kw_only_args.append(arg_name)
|
||||
else:
|
||||
args.append(arg_name)
|
||||
pre_init_args.append(arg_name)
|
||||
|
||||
if converter is not None:
|
||||
lines.append(
|
||||
@@ -2322,7 +2386,7 @@ def _attrs_to_init_script(
|
||||
lines.append(f"BaseException.__init__(self, {vals})")
|
||||
|
||||
args = ", ".join(args)
|
||||
pre_init_args = args
|
||||
pre_init_args = ", ".join(pre_init_args)
|
||||
if kw_only_args:
|
||||
# leading comma & kw_only args
|
||||
args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}"
|
||||
@@ -2337,7 +2401,7 @@ def _attrs_to_init_script(
|
||||
pre_init_args += pre_init_kw_only_args
|
||||
|
||||
if call_pre_init and pre_init_has_args:
|
||||
# If pre init method has arguments, pass same arguments as `__init__`.
|
||||
# If pre init method has arguments, pass the values given to __init__.
|
||||
lines[0] = f"self.__attrs_pre_init__({pre_init_args})"
|
||||
|
||||
# Python <3.12 doesn't allow backslashes in f-strings.
|
||||
@@ -2376,6 +2440,8 @@ class Attribute:
|
||||
- ``name`` (`str`): The name of the attribute.
|
||||
- ``alias`` (`str`): The __init__ parameter name of the attribute, after
|
||||
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
|
||||
from a base class.
|
||||
- ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The
|
||||
@@ -2401,6 +2467,7 @@ class Attribute:
|
||||
equality checks and hashing anymore.
|
||||
.. versionadded:: 21.1.0 *eq_key* and *order_key*
|
||||
.. versionadded:: 22.2.0 *alias*
|
||||
.. versionadded:: 26.1.0 *alias_is_default*
|
||||
|
||||
For the full version history of the fields, see `attr.ib`.
|
||||
"""
|
||||
@@ -2425,6 +2492,7 @@ class Attribute:
|
||||
"inherited",
|
||||
"on_setattr",
|
||||
"alias",
|
||||
"alias_is_default",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -2447,6 +2515,7 @@ class Attribute:
|
||||
order_key=None,
|
||||
on_setattr=None,
|
||||
alias=None,
|
||||
alias_is_default=None,
|
||||
):
|
||||
eq, eq_key, order, order_key = _determine_attrib_eq_order(
|
||||
cmp, eq_key or eq, order_key or order, True
|
||||
@@ -2481,12 +2550,20 @@ class Attribute:
|
||||
bound_setattr("inherited", inherited)
|
||||
bound_setattr("on_setattr", on_setattr)
|
||||
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):
|
||||
raise FrozenInstanceError
|
||||
|
||||
@classmethod
|
||||
def from_counting_attr(cls, name: str, ca: _CountingAttr, type=None):
|
||||
def from_counting_attr(
|
||||
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:
|
||||
if type is None:
|
||||
type = ca.type
|
||||
@@ -2505,13 +2582,14 @@ class Attribute:
|
||||
ca.metadata,
|
||||
type,
|
||||
ca.converter,
|
||||
ca.kw_only,
|
||||
kw_only if ca.kw_only is None else ca.kw_only,
|
||||
ca.eq,
|
||||
ca.eq_key,
|
||||
ca.order,
|
||||
ca.order_key,
|
||||
ca.on_setattr,
|
||||
ca.alias,
|
||||
ca.alias is None,
|
||||
)
|
||||
|
||||
# Don't use attrs.evolve since fields(Attribute) doesn't work
|
||||
@@ -2530,6 +2608,20 @@ class Attribute:
|
||||
|
||||
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
|
||||
|
||||
# Don't use _add_pickle since fields(Attribute) doesn't work
|
||||
@@ -2546,6 +2638,17 @@ class Attribute:
|
||||
"""
|
||||
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))
|
||||
|
||||
def _setattrs(self, name_values_pairs):
|
||||
@@ -2569,7 +2672,7 @@ _a = [
|
||||
name=name,
|
||||
default=NOTHING,
|
||||
validator=None,
|
||||
repr=True,
|
||||
repr=(name != "alias_is_default"),
|
||||
cmp=None,
|
||||
eq=True,
|
||||
order=False,
|
||||
@@ -2741,6 +2844,188 @@ class _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:
|
||||
"""
|
||||
Stores a factory callable.
|
||||
@@ -2848,9 +3133,7 @@ class Converter:
|
||||
value, field
|
||||
)
|
||||
else:
|
||||
self.__call__ = lambda value, instance, field: self.converter(
|
||||
value, instance, field
|
||||
)
|
||||
self.__call__ = self.converter
|
||||
|
||||
rt = ex.get_return_type()
|
||||
if rt is not None:
|
||||
|
||||
@@ -17,7 +17,7 @@ from ._make import (
|
||||
attrib,
|
||||
attrs,
|
||||
)
|
||||
from .exceptions import UnannotatedAttributeError
|
||||
from .exceptions import NotAnAttrsClassError, UnannotatedAttributeError
|
||||
|
||||
|
||||
def define(
|
||||
@@ -43,6 +43,7 @@ def define(
|
||||
on_setattr=None,
|
||||
field_transformer=None,
|
||||
match_args=True,
|
||||
force_kw_only=False,
|
||||
):
|
||||
r"""
|
||||
A class decorator that adds :term:`dunder methods` according to
|
||||
@@ -76,7 +77,7 @@ def define(
|
||||
sensible ``__ne__`` by default, so it *should* be enough to only
|
||||
implement ``__eq__`` in most cases).
|
||||
|
||||
Passing True or False` to *init*, *repr*, *eq*, or *hash*
|
||||
Passing :data:`True` or :data:`False` to *init*, *repr*, *eq*, or *hash*
|
||||
overrides whatever *auto_detect* would determine.
|
||||
|
||||
auto_exc (bool):
|
||||
@@ -214,8 +215,12 @@ def define(
|
||||
5. Subclasses of a frozen class are frozen too.
|
||||
|
||||
kw_only (bool):
|
||||
Make all attributes keyword-only in the generated ``__init__`` (if
|
||||
*init* is False, this parameter is ignored).
|
||||
Make attributes keyword-only in the generated ``__init__`` (if
|
||||
*init* is False, this parameter is ignored). Attributes that
|
||||
explicitly set ``kw_only=False`` are not affected; base class
|
||||
attributes are also not affected.
|
||||
|
||||
Also see *force_kw_only*.
|
||||
|
||||
weakref_slot (bool):
|
||||
Make instances weak-referenceable. This has no effect unless
|
||||
@@ -244,6 +249,15 @@ def define(
|
||||
See also `issue #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):
|
||||
.. note::
|
||||
|
||||
@@ -319,6 +333,11 @@ def define(
|
||||
.. versionadded:: 24.3.0
|
||||
Unless already present, a ``__replace__`` method is automatically
|
||||
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::
|
||||
|
||||
@@ -337,6 +356,7 @@ def define(
|
||||
- *auto_exc=True*
|
||||
- *auto_detect=True*
|
||||
- *order=False*
|
||||
- *force_kw_only=False*
|
||||
- Some options that were only relevant on Python 2 or were kept around
|
||||
for backwards-compatibility have been removed.
|
||||
|
||||
@@ -366,6 +386,7 @@ def define(
|
||||
on_setattr=on_setattr,
|
||||
field_transformer=field_transformer,
|
||||
match_args=match_args,
|
||||
force_kw_only=force_kw_only,
|
||||
)
|
||||
|
||||
def wrap(cls):
|
||||
@@ -424,7 +445,7 @@ def field(
|
||||
type=None,
|
||||
converter=None,
|
||||
factory=None,
|
||||
kw_only=False,
|
||||
kw_only=None,
|
||||
eq=None,
|
||||
order=None,
|
||||
on_setattr=None,
|
||||
@@ -550,9 +571,10 @@ def field(
|
||||
itself. You can use it as part of your own code or for `static type
|
||||
checking <types>`.
|
||||
|
||||
kw_only (bool):
|
||||
kw_only (bool | None):
|
||||
Make this attribute keyword-only in the generated ``__init__`` (if
|
||||
``init`` is False, this parameter is ignored).
|
||||
*init* is False, this parameter is ignored). If None (default),
|
||||
mirror the setting from `attrs.define`.
|
||||
|
||||
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
|
||||
@@ -572,6 +594,9 @@ def field(
|
||||
.. versionadded:: 23.1.0
|
||||
The *type* parameter has been re-added; mostly for `attrs.make_class`.
|
||||
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::
|
||||
|
||||
@@ -621,3 +646,29 @@ def astuple(inst, *, recurse=True, filter=None):
|
||||
return _astuple(
|
||||
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,3 +84,6 @@ class VersionInfo:
|
||||
# Since alphabetically "dev0" < "final" < "post1" < "post2", we don't
|
||||
# have to do anything special with releaselevel for now.
|
||||
return us < them
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.year, self.minor, self.micro, self.releaselevel))
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar
|
||||
|
||||
|
||||
class FrozenError(AttributeError):
|
||||
"""
|
||||
@@ -16,8 +14,10 @@ class FrozenError(AttributeError):
|
||||
.. versionadded:: 20.1.0
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
msg = "can't set attribute"
|
||||
args: ClassVar[tuple[str]] = [msg]
|
||||
super().__init__(msg)
|
||||
self.msg = msg
|
||||
|
||||
|
||||
class FrozenInstanceError(FrozenError):
|
||||
|
||||
@@ -79,12 +79,14 @@ def disabled():
|
||||
This context manager is not thread-safe!
|
||||
|
||||
.. versionadded:: 21.3.0
|
||||
.. versionchanged:: 26.1.0 The contextmanager is nestable.
|
||||
"""
|
||||
prev = get_run_validators()
|
||||
set_run_validators(False)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
set_run_validators(True)
|
||||
set_run_validators(prev)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
@@ -361,26 +363,32 @@ def deep_iterable(member_validator, iterable_validator=None):
|
||||
A validator that performs deep validation of an iterable.
|
||||
|
||||
Args:
|
||||
member_validator: Validator to apply to iterable members.
|
||||
member_validator: Validator(s) to apply to iterable members.
|
||||
|
||||
iterable_validator:
|
||||
Validator to apply to iterable itself (optional).
|
||||
Validator(s) to apply to iterable itself (optional).
|
||||
|
||||
Raises
|
||||
TypeError: if any sub-validators fail
|
||||
|
||||
.. 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)):
|
||||
member_validator = and_(*member_validator)
|
||||
if isinstance(iterable_validator, (list, tuple)):
|
||||
iterable_validator = and_(*iterable_validator)
|
||||
return _DeepIterable(member_validator, iterable_validator)
|
||||
|
||||
|
||||
@attrs(repr=False, slots=True, unsafe_hash=True)
|
||||
class _DeepMapping:
|
||||
key_validator = attrib(validator=is_callable())
|
||||
value_validator = attrib(validator=is_callable())
|
||||
mapping_validator = attrib(default=None, validator=optional(is_callable()))
|
||||
key_validator = attrib(validator=optional(is_callable()))
|
||||
value_validator = attrib(validator=optional(is_callable()))
|
||||
mapping_validator = attrib(validator=optional(is_callable()))
|
||||
|
||||
def __call__(self, inst, attr, value):
|
||||
"""
|
||||
@@ -390,30 +398,62 @@ class _DeepMapping:
|
||||
self.mapping_validator(inst, attr, value)
|
||||
|
||||
for key in value:
|
||||
if self.key_validator is not None:
|
||||
self.key_validator(inst, attr, key)
|
||||
if self.value_validator is not None:
|
||||
self.value_validator(inst, attr, value[key])
|
||||
|
||||
def __repr__(self):
|
||||
return f"<deep_mapping validator for objects mapping {self.key_validator!r} to {self.value_validator!r}>"
|
||||
|
||||
|
||||
def deep_mapping(key_validator, value_validator, mapping_validator=None):
|
||||
def deep_mapping(
|
||||
key_validator=None, value_validator=None, mapping_validator=None
|
||||
):
|
||||
"""
|
||||
A validator that performs deep validation of a dictionary.
|
||||
|
||||
Args:
|
||||
key_validator: Validator to apply to dictionary keys.
|
||||
All validators are optional, but at least one of *key_validator* or
|
||||
*value_validator* must be provided.
|
||||
|
||||
value_validator: Validator to apply to dictionary values.
|
||||
Args:
|
||||
key_validator: Validator(s) to apply to dictionary keys.
|
||||
|
||||
value_validator: Validator(s) to apply to dictionary values.
|
||||
|
||||
mapping_validator:
|
||||
Validator to apply to top-level mapping attribute (optional).
|
||||
Validator(s) to apply to top-level mapping attribute.
|
||||
|
||||
.. 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:
|
||||
TypeError: if any sub-validators fail
|
||||
TypeError: If any sub-validator fails on validation.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -485,7 +525,7 @@ def gt(val):
|
||||
A validator that raises `ValueError` if the initializer is called with a
|
||||
number smaller or equal to *val*.
|
||||
|
||||
The validator uses `operator.ge` to compare the values.
|
||||
The validator uses `operator.gt` to compare the values.
|
||||
|
||||
Args:
|
||||
val: Exclusive lower bound for values
|
||||
|
||||
@@ -20,6 +20,9 @@ _T = TypeVar("_T")
|
||||
_T1 = TypeVar("_T1")
|
||||
_T2 = TypeVar("_T2")
|
||||
_T3 = TypeVar("_T3")
|
||||
_T4 = TypeVar("_T4")
|
||||
_T5 = TypeVar("_T5")
|
||||
_T6 = TypeVar("_T6")
|
||||
_I = TypeVar("_I", bound=Iterable)
|
||||
_K = TypeVar("_K")
|
||||
_V = TypeVar("_V")
|
||||
@@ -51,7 +54,7 @@ def optional(
|
||||
validator: (
|
||||
_ValidatorType[_T]
|
||||
| list[_ValidatorType[_T]]
|
||||
| tuple[_ValidatorType[_T]]
|
||||
| tuple[_ValidatorType[_T], ...]
|
||||
),
|
||||
) -> _ValidatorType[_T | None]: ...
|
||||
def in_(options: Container[_T]) -> _ValidatorType[_T]: ...
|
||||
@@ -63,12 +66,19 @@ def matches_re(
|
||||
) -> _ValidatorType[AnyStr]: ...
|
||||
def deep_iterable(
|
||||
member_validator: _ValidatorArgType[_T],
|
||||
iterable_validator: _ValidatorType[_I] | None = ...,
|
||||
iterable_validator: _ValidatorArgType[_I] | None = ...,
|
||||
) -> _ValidatorType[_I]: ...
|
||||
@overload
|
||||
def deep_mapping(
|
||||
key_validator: _ValidatorType[_K],
|
||||
value_validator: _ValidatorType[_V],
|
||||
mapping_validator: _ValidatorType[_M] | None = ...,
|
||||
key_validator: _ValidatorArgType[_K],
|
||||
value_validator: _ValidatorArgType[_V] | None = ...,
|
||||
mapping_validator: _ValidatorArgType[_M] | None = ...,
|
||||
) -> _ValidatorType[_M]: ...
|
||||
@overload
|
||||
def deep_mapping(
|
||||
key_validator: _ValidatorArgType[_K] | None = ...,
|
||||
value_validator: _ValidatorArgType[_V] = ...,
|
||||
mapping_validator: _ValidatorArgType[_M] | None = ...,
|
||||
) -> _ValidatorType[_M]: ...
|
||||
def is_callable() -> _ValidatorType[_T]: ...
|
||||
def lt(val: _T) -> _ValidatorType[_T]: ...
|
||||
@@ -83,4 +93,48 @@ def not_(
|
||||
msg: str | None = None,
|
||||
exc_types: type[Exception] | Iterable[type[Exception]] = ...,
|
||||
) -> _ValidatorType[_T]: ...
|
||||
def or_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ...
|
||||
@overload
|
||||
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]: ...
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,56 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uv
|
||||
+27
-60
@@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: attrs
|
||||
Version: 25.3.0
|
||||
Version: 26.1.0
|
||||
Summary: Classes Without Boilerplate
|
||||
Project-URL: Documentation, https://www.attrs.org/
|
||||
Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html
|
||||
@@ -12,62 +12,16 @@ License-Expression: MIT
|
||||
License-File: LICENSE
|
||||
Keywords: attribute,boilerplate,class
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Typing :: Typed
|
||||
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'
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
<p align="center">
|
||||
@@ -78,7 +32,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)).
|
||||
[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!
|
||||
Trusted by NASA for [Mars missions since 2020](https://github.com/readme/featured/nasa-ingenuity-helicopter)!
|
||||
|
||||
Its main goal is to help you to write **concise** and **correct** software without slowing down your code.
|
||||
|
||||
@@ -96,15 +50,15 @@ Especially those generously supporting us at the *The Organization* tier and hig
|
||||
import pathlib, tomllib
|
||||
|
||||
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/25.3.0/_static/sponsors/{sponsor["img"]}" width="190" /></a>')
|
||||
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>')
|
||||
]]] -->
|
||||
<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/25.3.0/_static/sponsors/Tidelift.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/25.3.0/_static/sponsors/Privacy-Solutions.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://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/25.3.0/_static/sponsors/Polar.svg" 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://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://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://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://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.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://polar.sh/"><img title="Polar" src="https://www.attrs.org/en/26.1.0/_static/sponsors/Polar.svg" width="190" /></a>
|
||||
<!-- [[[end]]] -->
|
||||
|
||||
</p>
|
||||
@@ -220,10 +174,23 @@ Save time, reduce risk, and improve code health, while paying the maintainers of
|
||||
|
||||
## 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
|
||||
|
||||
- Restore support for generator-based `field_transformer`s.
|
||||
[#1417](https://github.com/python-attrs/attrs/issues/1417)
|
||||
- Fix type annotations for `attrs.validators.optional()`, so it no longer rejects tuples with more than one validator.
|
||||
[#1496](https://github.com/python-attrs/attrs/issues/1496)
|
||||
- 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,37 @@
|
||||
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
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.27.0
|
||||
Generator: hatchling 1.29.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -22,7 +22,8 @@ from attr import (
|
||||
resolve_types,
|
||||
validate,
|
||||
)
|
||||
from attr._next_gen import asdict, astuple
|
||||
from attr._make import ClassProps
|
||||
from attr._next_gen import asdict, astuple, inspect
|
||||
|
||||
from . import converters, exceptions, filters, setters, validators
|
||||
|
||||
@@ -31,6 +32,7 @@ __all__ = [
|
||||
"NOTHING",
|
||||
"Attribute",
|
||||
"AttrsInstance",
|
||||
"ClassProps",
|
||||
"Converter",
|
||||
"Factory",
|
||||
"NothingType",
|
||||
@@ -58,6 +60,7 @@ __all__ = [
|
||||
"filters",
|
||||
"frozen",
|
||||
"has",
|
||||
"inspect",
|
||||
"make_class",
|
||||
"mutable",
|
||||
"resolve_types",
|
||||
|
||||
@@ -77,7 +77,7 @@ def field(
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: None = ...,
|
||||
factory: None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: bool | None = ...,
|
||||
order: bool | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -98,10 +98,10 @@ def field(
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| tuple[_ConverterType, ...]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -121,10 +121,10 @@ def field(
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| tuple[_ConverterType, ...]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -144,10 +144,10 @@ def field(
|
||||
metadata: Mapping[Any, Any] | None = ...,
|
||||
converter: _ConverterType
|
||||
| list[_ConverterType]
|
||||
| tuple[_ConverterType]
|
||||
| tuple[_ConverterType, ...]
|
||||
| None = ...,
|
||||
factory: Callable[[], _T] | None = ...,
|
||||
kw_only: bool = ...,
|
||||
kw_only: bool | None = ...,
|
||||
eq: _EqOrderType | None = ...,
|
||||
order: _EqOrderType | None = ...,
|
||||
on_setattr: _OnSetAttrArgType | None = ...,
|
||||
@@ -261,3 +261,54 @@ def frozen(
|
||||
field_transformer: _FieldTransformer | None = ...,
|
||||
match_args: bool = ...,
|
||||
) -> 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.
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,102 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
uv
|
||||
+9
-6
@@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: cattrs
|
||||
Version: 25.1.1
|
||||
Version: 26.1.0
|
||||
Summary: Composable complex class support for attrs and dataclasses.
|
||||
Project-URL: Homepage, https://catt.rs
|
||||
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: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Typing :: Typed
|
||||
Requires-Python: >=3.9
|
||||
Requires-Dist: attrs>=24.3.0
|
||||
Requires-Python: >=3.10
|
||||
Requires-Dist: attrs>=25.4.0
|
||||
Requires-Dist: exceptiongroup>=1.1.1; python_version < '3.11'
|
||||
Requires-Dist: typing-extensions>=4.12.2
|
||||
Requires-Dist: typing-extensions>=4.14.0
|
||||
Provides-Extra: bson
|
||||
Requires-Dist: pymongo>=4.4.0; extra == 'bson'
|
||||
Provides-Extra: cbor2
|
||||
@@ -35,11 +35,14 @@ Requires-Dist: msgpack>=1.0.5; extra == 'msgpack'
|
||||
Provides-Extra: msgspec
|
||||
Requires-Dist: msgspec>=0.19.0; (implementation_name == 'cpython') and extra == 'msgspec'
|
||||
Provides-Extra: orjson
|
||||
Requires-Dist: orjson>=3.10.7; (implementation_name == 'cpython') and extra == 'orjson'
|
||||
Requires-Dist: orjson>=3.11.3; (implementation_name == 'cpython') and extra == 'orjson'
|
||||
Provides-Extra: pyyaml
|
||||
Requires-Dist: pyyaml>=6.0; extra == 'pyyaml'
|
||||
Provides-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
|
||||
Requires-Dist: ujson>=5.10.0; extra == 'ujson'
|
||||
Description-Content-Type: text/markdown
|
||||
@@ -0,0 +1,58 @@
|
||||
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
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.27.0
|
||||
Generator: hatchling 1.28.0
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -29,7 +29,6 @@ from typing import (
|
||||
_AnnotatedAlias,
|
||||
_GenericAlias,
|
||||
_SpecialGenericAlias,
|
||||
_UnionGenericAlias,
|
||||
get_args,
|
||||
get_origin,
|
||||
get_type_hints,
|
||||
@@ -209,10 +208,7 @@ def get_final_base(type) -> Optional[type]:
|
||||
OriginAbstractSet = AbcSet
|
||||
OriginMutableSet = AbcMutableSet
|
||||
|
||||
signature = _signature
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
signature = partial(_signature, eval_str=True)
|
||||
signature = partial(_signature, eval_str=True)
|
||||
|
||||
|
||||
try:
|
||||
@@ -256,10 +252,25 @@ def is_tuple(type):
|
||||
)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
if sys.version_info >= (3, 14):
|
||||
|
||||
def is_union_type(obj):
|
||||
from types import UnionType
|
||||
from types import UnionType # noqa: PLC0415
|
||||
|
||||
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 (
|
||||
obj is Union
|
||||
@@ -277,25 +288,6 @@ if sys.version_info >= (3, 10):
|
||||
else:
|
||||
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]:
|
||||
if is_annotated(type):
|
||||
@@ -306,6 +298,25 @@ def get_notrequired_base(type) -> Union[Any, NothingType]:
|
||||
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:
|
||||
"""A predicate function for sequences.
|
||||
|
||||
@@ -313,19 +324,8 @@ def is_sequence(type: Any) -> bool:
|
||||
tuples.
|
||||
"""
|
||||
origin = getattr(type, "__origin__", None)
|
||||
return (
|
||||
type
|
||||
in (
|
||||
List,
|
||||
list,
|
||||
TypingSequence,
|
||||
TypingMutableSequence,
|
||||
AbcMutableSequence,
|
||||
tuple,
|
||||
Tuple,
|
||||
deque,
|
||||
Deque,
|
||||
)
|
||||
return is_mutable_sequence(type) or (
|
||||
type in (TypingSequence, tuple, Tuple)
|
||||
or (
|
||||
type.__class__ is _GenericAlias
|
||||
and (
|
||||
@@ -333,7 +333,7 @@ def is_sequence(type: Any) -> bool:
|
||||
or (origin is tuple and type.__args__[1] is ...)
|
||||
)
|
||||
)
|
||||
or (origin in (list, deque, AbcMutableSequence, AbcSequence))
|
||||
or (origin is AbcSequence)
|
||||
or (origin is tuple and type.__args__[1] is ...)
|
||||
)
|
||||
|
||||
@@ -403,8 +403,10 @@ def is_generic(type) -> bool:
|
||||
"""Whether `type` is a generic type."""
|
||||
# Inheriting from protocol will inject `Generic` into the MRO
|
||||
# without `__orig_bases__`.
|
||||
return isinstance(type, (_GenericAlias, GenericAlias)) or (
|
||||
is_subclass(type, Generic) and hasattr(type, "__orig_bases__")
|
||||
return (
|
||||
isinstance(type, (_GenericAlias, GenericAlias))
|
||||
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 typing import Any
|
||||
from typing import Any, get_args
|
||||
|
||||
from attrs import NOTHING
|
||||
from typing_extensions import Self
|
||||
|
||||
from ._compat import copy_with, get_args, is_annotated, is_generic
|
||||
from ._compat import copy_with, is_annotated, is_generic
|
||||
|
||||
|
||||
def deep_copy_with(t, mapping: Mapping[str, Any], self_is=NOTHING):
|
||||
|
||||
+54
-10
@@ -5,25 +5,20 @@ from __future__ import annotations
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterable
|
||||
from functools import partial
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
DefaultDict,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
TypeVar,
|
||||
get_type_hints,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, DefaultDict, Literal, NamedTuple, TypeVar
|
||||
|
||||
from attrs import NOTHING, Attribute, NothingType
|
||||
|
||||
from ._compat import (
|
||||
ANIES,
|
||||
AbcSet,
|
||||
get_args,
|
||||
get_full_type_hints,
|
||||
get_origin,
|
||||
is_bare,
|
||||
is_frozenset,
|
||||
is_mapping,
|
||||
is_mutable_sequence,
|
||||
is_sequence,
|
||||
is_subclass,
|
||||
)
|
||||
@@ -47,10 +42,13 @@ if TYPE_CHECKING:
|
||||
|
||||
__all__ = [
|
||||
"defaultdict_structure_factory",
|
||||
"homogenous_tuple_structure_factory",
|
||||
"is_abstract_set",
|
||||
"is_any_set",
|
||||
"is_defaultdict",
|
||||
"is_frozenset",
|
||||
"is_mapping",
|
||||
"is_mutable_sequence",
|
||||
"is_namedtuple",
|
||||
"is_sequence",
|
||||
"is_set",
|
||||
@@ -70,6 +68,11 @@ def is_any_set(type) -> bool:
|
||||
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:
|
||||
"""A predicate function for named tuples."""
|
||||
|
||||
@@ -151,6 +154,47 @@ def list_structure_factory(type: type, converter: BaseConverter) -> StructureHoo
|
||||
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(
|
||||
cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None
|
||||
) -> UnstructureHook:
|
||||
@@ -195,7 +239,7 @@ def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]:
|
||||
type=a,
|
||||
alias=name,
|
||||
)
|
||||
for name, a in get_type_hints(cl).items()
|
||||
for name, a in get_full_type_hints(cl).items()
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -43,10 +43,11 @@ from ._compat import (
|
||||
is_hetero_tuple,
|
||||
is_literal,
|
||||
is_mapping,
|
||||
is_mutable_sequence,
|
||||
is_mutable_set,
|
||||
is_optional,
|
||||
is_protocol,
|
||||
is_sequence,
|
||||
is_subclass,
|
||||
is_tuple,
|
||||
is_typeddict,
|
||||
is_union_type,
|
||||
@@ -54,8 +55,11 @@ from ._compat import (
|
||||
)
|
||||
from .cols import (
|
||||
defaultdict_structure_factory,
|
||||
homogenous_tuple_structure_factory,
|
||||
is_abstract_set,
|
||||
is_defaultdict,
|
||||
is_namedtuple,
|
||||
is_sequence,
|
||||
iterable_unstructure_factory,
|
||||
list_structure_factory,
|
||||
mapping_structure_factory,
|
||||
@@ -73,6 +77,7 @@ from .dispatch import (
|
||||
UnstructuredValue,
|
||||
UnstructureHook,
|
||||
)
|
||||
from .enums import enum_structure_factory, enum_unstructure_factory
|
||||
from .errors import (
|
||||
IterableValidationError,
|
||||
IterableValidationNote,
|
||||
@@ -225,6 +230,10 @@ class BaseConverter:
|
||||
)
|
||||
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,
|
||||
lambda o: self.unstructure(o, unstructure_as=o.__class__),
|
||||
@@ -239,12 +248,12 @@ class BaseConverter:
|
||||
lambda t: self.get_unstructure_hook(get_type_alias_base(t)),
|
||||
True,
|
||||
),
|
||||
(is_literal_containing_enums, self.unstructure),
|
||||
(is_mapping, self._unstructure_mapping),
|
||||
(is_sequence, self._unstructure_seq),
|
||||
(is_mutable_set, self._unstructure_seq),
|
||||
(is_frozenset, self._unstructure_seq),
|
||||
(lambda t: issubclass(t, Enum), self._unstructure_enum),
|
||||
(is_literal_containing_enums, self.unstructure),
|
||||
(lambda t: is_subclass(t, Enum), enum_unstructure_factory, "extended"),
|
||||
(has, self._unstructure_attrs),
|
||||
(is_union_type, self._unstructure_union),
|
||||
(lambda t: t in ANIES, self.unstructure),
|
||||
@@ -271,20 +280,27 @@ class BaseConverter:
|
||||
),
|
||||
(is_literal, self._structure_simple_literal),
|
||||
(is_literal_containing_enums, self._structure_enum_literal),
|
||||
(is_sequence, list_structure_factory, "extended"),
|
||||
(is_sequence, homogenous_tuple_structure_factory, "extended"),
|
||||
(is_mutable_sequence, list_structure_factory, "extended"),
|
||||
(is_deque, self._structure_deque),
|
||||
(is_mutable_set, self._structure_set),
|
||||
(is_abstract_set, self._structure_frozenset),
|
||||
(is_frozenset, self._structure_frozenset),
|
||||
(is_tuple, self._structure_tuple),
|
||||
(is_namedtuple, namedtuple_structure_factory, "extended"),
|
||||
(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),
|
||||
(
|
||||
lambda t: is_union_type(t) and t in self._union_struct_registry,
|
||||
self._union_struct_registry.__getitem__,
|
||||
True,
|
||||
),
|
||||
(lambda t: is_subclass(t, Enum), enum_structure_factory, "extended"),
|
||||
(has, self._structure_attrs),
|
||||
]
|
||||
)
|
||||
@@ -295,7 +311,6 @@ class BaseConverter:
|
||||
(bytes, self._structure_call),
|
||||
(int, self._structure_call),
|
||||
(float, self._structure_call),
|
||||
(Enum, self._structure_call),
|
||||
(Path, self._structure_call),
|
||||
]
|
||||
)
|
||||
@@ -617,10 +632,6 @@ class BaseConverter:
|
||||
res.append(dispatch(a.type or v.__class__)(v))
|
||||
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]:
|
||||
"""Convert a sequence to primitive equivalents."""
|
||||
# We can reuse the sequence class, so tuples stay tuples.
|
||||
@@ -1034,6 +1045,7 @@ class Converter(BaseConverter):
|
||||
"forbid_extra_keys",
|
||||
"omit_if_default",
|
||||
"type_overrides",
|
||||
"use_alias",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
@@ -1050,6 +1062,7 @@ class Converter(BaseConverter):
|
||||
structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error(
|
||||
None, t
|
||||
),
|
||||
use_alias: bool = False,
|
||||
):
|
||||
"""
|
||||
:param detailed_validation: Whether to use a slightly slower mode for detailed
|
||||
@@ -1058,12 +1071,15 @@ class Converter(BaseConverter):
|
||||
registered unstructuring hooks match.
|
||||
:param structure_fallback_factory: A hook factory to be called when no
|
||||
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 *structure_fallback_factory*
|
||||
.. versionchanged:: 24.2.0
|
||||
The default `structure_fallback_factory` now raises errors for missing handlers
|
||||
more eagerly, surfacing problems earlier.
|
||||
.. versionadded:: 25.2.0 *use_alias*
|
||||
"""
|
||||
super().__init__(
|
||||
dict_factory=dict_factory,
|
||||
@@ -1076,6 +1092,7 @@ class Converter(BaseConverter):
|
||||
self.omit_if_default = omit_if_default
|
||||
self.forbid_extra_keys = forbid_extra_keys
|
||||
self.type_overrides = dict(type_overrides)
|
||||
self.use_alias = use_alias
|
||||
|
||||
unstruct_collection_overrides = {
|
||||
get_origin(k) or k: v for k, v in unstruct_collection_overrides.items()
|
||||
@@ -1246,7 +1263,7 @@ class Converter(BaseConverter):
|
||||
attribs = fields(origin or cl)
|
||||
if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs):
|
||||
# PEP 563 annotations - need to be resolved.
|
||||
resolve_types(cl)
|
||||
resolve_types(origin or cl)
|
||||
attrib_overrides = {
|
||||
a.name: self.type_overrides[a.type]
|
||||
for a in attribs
|
||||
@@ -1284,10 +1301,11 @@ class Converter(BaseConverter):
|
||||
def gen_structure_attrs_fromdict(
|
||||
self, cl: type[T]
|
||||
) -> Callable[[Mapping[str, Any], Any], T]:
|
||||
attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl)
|
||||
origin = get_origin(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):
|
||||
# PEP 563 annotations - need to be resolved.
|
||||
resolve_types(cl)
|
||||
resolve_types(origin or cl)
|
||||
attrib_overrides = {
|
||||
a.name: self.type_overrides[a.type]
|
||||
for a in attribs
|
||||
@@ -1299,6 +1317,7 @@ class Converter(BaseConverter):
|
||||
_cattrs_forbid_extra_keys=self.forbid_extra_keys,
|
||||
_cattrs_prefer_attrib_converters=self._prefer_attrib_converters,
|
||||
_cattrs_detailed_validation=self.detailed_validation,
|
||||
_cattrs_use_alias=self.use_alias,
|
||||
**attrib_overrides,
|
||||
)
|
||||
|
||||
@@ -1377,6 +1396,7 @@ class Converter(BaseConverter):
|
||||
unstruct_collection_overrides: Mapping[type, UnstructureHook] | None = None,
|
||||
prefer_attrib_converters: bool | None = None,
|
||||
detailed_validation: bool | None = None,
|
||||
use_alias: bool | None = None,
|
||||
) -> Self:
|
||||
"""Create a copy of the converter, keeping all existing custom hooks.
|
||||
|
||||
@@ -1416,6 +1436,7 @@ class Converter(BaseConverter):
|
||||
if detailed_validation is not None
|
||||
else self.detailed_validation
|
||||
),
|
||||
use_alias=(use_alias if use_alias is not None else self.use_alias),
|
||||
)
|
||||
|
||||
self._unstructure_func.copy_to(
|
||||
|
||||
@@ -7,7 +7,7 @@ from collections.abc import Mapping
|
||||
from dataclasses import MISSING
|
||||
from functools import reduce
|
||||
from operator import or_
|
||||
from typing import TYPE_CHECKING, Any, Callable, Literal, Union
|
||||
from typing import TYPE_CHECKING, Any, Callable, Literal, Union, get_origin
|
||||
|
||||
from attrs import NOTHING, Attribute, AttrsInstance
|
||||
|
||||
@@ -16,7 +16,6 @@ from ._compat import (
|
||||
adapted_fields,
|
||||
fields_dict,
|
||||
get_args,
|
||||
get_origin,
|
||||
has,
|
||||
is_literal,
|
||||
is_union_type,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
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,14 +13,18 @@ class StructureHandlerNotFoundError(Exception):
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, type_: type) -> None:
|
||||
super().__init__(message)
|
||||
super().__init__(message, type_)
|
||||
self.message = message
|
||||
self.type_ = type_
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.message
|
||||
|
||||
|
||||
class BaseValidationError(ExceptionGroup):
|
||||
cl: type
|
||||
|
||||
def __new__(cls, message: str, excs: Sequence[Exception], cl: type):
|
||||
def __new__(cls, message: str, excs: Sequence[Exception], cl: type) -> Self:
|
||||
obj = super().__new__(cls, message, excs)
|
||||
obj.cl = cl
|
||||
return obj
|
||||
@@ -35,9 +39,7 @@ class IterableValidationNote(str):
|
||||
index: Union[int, str] # Ints for list indices, strs for dict keys
|
||||
type: Any
|
||||
|
||||
def __new__(
|
||||
cls, string: str, index: Union[int, str], type: Any
|
||||
) -> "IterableValidationNote":
|
||||
def __new__(cls, string: str, index: Union[int, str], type: Any) -> Self:
|
||||
instance = str.__new__(cls, string)
|
||||
instance.index = index
|
||||
instance.type = type
|
||||
@@ -76,7 +78,7 @@ class AttributeValidationNote(str):
|
||||
name: str
|
||||
type: Any
|
||||
|
||||
def __new__(cls, string: str, name: str, type: Any) -> "AttributeValidationNote":
|
||||
def __new__(cls, string: str, name: str, type: Any) -> Self:
|
||||
instance = str.__new__(cls, string)
|
||||
instance.name = name
|
||||
instance.type = type
|
||||
@@ -122,11 +124,15 @@ class ForbiddenExtraKeysError(Exception):
|
||||
def __init__(
|
||||
self, message: Optional[str], cl: type, extra_fields: set[str]
|
||||
) -> None:
|
||||
self.message = message
|
||||
self.cl = cl
|
||||
self.extra_fields = extra_fields
|
||||
cln = cl.__name__
|
||||
|
||||
super().__init__(
|
||||
message
|
||||
or f"Extra fields in constructor for {cln}: {', '.join(extra_fields)}"
|
||||
super().__init__(message, cl, extra_fields)
|
||||
|
||||
def __str__(self) -> str:
|
||||
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 typing import TYPE_CHECKING, Any, Final, Literal, TypeVar
|
||||
|
||||
from attrs import NOTHING, Attribute, Factory
|
||||
from attrs import NOTHING, Attribute, Converter, Factory, evolve
|
||||
from typing_extensions import NoDefault
|
||||
|
||||
from .._compat import (
|
||||
@@ -33,7 +33,7 @@ from ..types import SimpleStructureHook
|
||||
from ._consts import AttributeOverride, already_generating, neutral
|
||||
from ._generics import generate_mapping
|
||||
from ._lc import generate_unique_filename
|
||||
from ._shared import find_structure_handler
|
||||
from ._shared import _annotated_override_or_default, find_structure_handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..converters import BaseConverter
|
||||
@@ -74,7 +74,7 @@ def make_dict_unstructure_fn_from_attrs(
|
||||
typevar_map: dict[str, Any] = {},
|
||||
_cattrs_omit_if_default: bool = False,
|
||||
_cattrs_use_linecache: bool = True,
|
||||
_cattrs_use_alias: bool = False,
|
||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
||||
_cattrs_include_init_false: bool = False,
|
||||
**kwargs: AttributeOverride,
|
||||
) -> Callable[[T], dict[str, Any]]:
|
||||
@@ -96,6 +96,16 @@ def make_dict_unstructure_fn_from_attrs(
|
||||
will be included.
|
||||
|
||||
.. 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__
|
||||
@@ -104,15 +114,27 @@ def make_dict_unstructure_fn_from_attrs(
|
||||
invocation_lines = []
|
||||
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:
|
||||
attr_name = a.name
|
||||
override = kwargs.get(attr_name, neutral)
|
||||
if attr_name in kwargs:
|
||||
override = kwargs[attr_name]
|
||||
else:
|
||||
override = _annotated_override_or_default(a.type, neutral)
|
||||
if override != neutral:
|
||||
kwargs[attr_name] = override
|
||||
|
||||
if override.omit:
|
||||
continue
|
||||
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
||||
continue
|
||||
if override.rename is None:
|
||||
kn = attr_name if not _cattrs_use_alias else a.alias
|
||||
if kn != attr_name:
|
||||
kwargs[attr_name] = evolve(override, rename=kn)
|
||||
else:
|
||||
kn = override.rename
|
||||
d = a.default
|
||||
@@ -170,15 +192,31 @@ def make_dict_unstructure_fn_from_attrs(
|
||||
if isinstance(d, Factory):
|
||||
globs[def_name] = d.factory
|
||||
internal_arg_parts[def_name] = d.factory
|
||||
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}")
|
||||
def_str = f"{def_name}(instance)" if d.takes_self else f"{def_name}()"
|
||||
else:
|
||||
globs[def_name] = d
|
||||
internal_arg_parts[def_name] = d
|
||||
lines.append(f" if instance.{attr_name} != {def_name}:")
|
||||
def_str = def_name
|
||||
|
||||
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:
|
||||
@@ -217,7 +255,7 @@ def make_dict_unstructure_fn(
|
||||
converter: BaseConverter,
|
||||
_cattrs_omit_if_default: bool = False,
|
||||
_cattrs_use_linecache: bool = True,
|
||||
_cattrs_use_alias: bool = False,
|
||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
||||
_cattrs_include_init_false: bool = False,
|
||||
**kwargs: AttributeOverride,
|
||||
) -> Callable[[T], dict[str, Any]]:
|
||||
@@ -237,11 +275,20 @@ def make_dict_unstructure_fn(
|
||||
|
||||
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
||||
.. 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)
|
||||
attrs = adapted_fields(origin or cl) # type: ignore
|
||||
|
||||
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):
|
||||
mapping = generate_mapping(cl, mapping)
|
||||
|
||||
@@ -289,7 +336,7 @@ def make_dict_structure_fn_from_attrs(
|
||||
bool | Literal["from_converter"]
|
||||
) = "from_converter",
|
||||
_cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
|
||||
_cattrs_use_alias: bool = False,
|
||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
||||
_cattrs_include_init_false: bool = False,
|
||||
**kwargs: AttributeOverride,
|
||||
) -> SimpleStructureHook[Mapping[str, Any], T]:
|
||||
@@ -315,6 +362,12 @@ def make_dict_structure_fn_from_attrs(
|
||||
will be included.
|
||||
|
||||
.. 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__
|
||||
@@ -350,6 +403,9 @@ def make_dict_structure_fn_from_attrs(
|
||||
if _cattrs_forbid_extra_keys == "from_converter":
|
||||
# BaseConverter doesn't have it so we're careful.
|
||||
_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":
|
||||
_cattrs_detailed_validation = converter.detailed_validation
|
||||
if _cattrs_prefer_attrib_converters == "from_converter":
|
||||
@@ -367,7 +423,13 @@ def make_dict_structure_fn_from_attrs(
|
||||
internal_arg_parts["__c_avn"] = AttributeValidationNote
|
||||
for a in attrs:
|
||||
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:
|
||||
continue
|
||||
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
||||
@@ -396,6 +458,8 @@ def make_dict_structure_fn_from_attrs(
|
||||
ian = a.alias
|
||||
if override.rename is None:
|
||||
kn = an if not _cattrs_use_alias else a.alias
|
||||
if kn != an:
|
||||
kwargs[an] = evolve(override, rename=kn)
|
||||
else:
|
||||
kn = override.rename
|
||||
|
||||
@@ -496,14 +560,24 @@ def make_dict_structure_fn_from_attrs(
|
||||
# The first loop deals with required args.
|
||||
for a in attrs:
|
||||
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:
|
||||
continue
|
||||
if override.omit is None and not a.init and not _cattrs_include_init_false:
|
||||
continue
|
||||
|
||||
if a.default is not NOTHING:
|
||||
non_required.append(a)
|
||||
# The next loop will handle it.
|
||||
continue
|
||||
|
||||
t = a.type
|
||||
if isinstance(t, TypeVar):
|
||||
t = typevar_map.get(t.__name__, t)
|
||||
@@ -523,6 +597,8 @@ def make_dict_structure_fn_from_attrs(
|
||||
|
||||
if override.rename is None:
|
||||
kn = an if not _cattrs_use_alias else a.alias
|
||||
if kn != an:
|
||||
kwargs[an] = evolve(override, rename=kn)
|
||||
else:
|
||||
kn = override.rename
|
||||
allowed_fields.add(kn)
|
||||
@@ -592,6 +668,8 @@ def make_dict_structure_fn_from_attrs(
|
||||
|
||||
if override.rename is None:
|
||||
kn = an if not _cattrs_use_alias else a.alias
|
||||
if kn != an:
|
||||
kwargs[an] = evolve(override, rename=kn)
|
||||
else:
|
||||
kn = override.rename
|
||||
allowed_fields.add(kn)
|
||||
@@ -682,7 +760,7 @@ def make_dict_structure_fn(
|
||||
bool | Literal["from_converter"]
|
||||
) = "from_converter",
|
||||
_cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter",
|
||||
_cattrs_use_alias: bool = False,
|
||||
_cattrs_use_alias: bool | Literal["from_converter"] = "from_converter",
|
||||
_cattrs_include_init_false: bool = False,
|
||||
**kwargs: AttributeOverride,
|
||||
) -> SimpleStructureHook[Mapping[str, Any], T]:
|
||||
@@ -714,6 +792,12 @@ def make_dict_structure_fn(
|
||||
.. versionchanged:: 24.1.0
|
||||
The `_cattrs_prefer_attrib_converters` parameter takes its value from the given
|
||||
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 = {}
|
||||
|
||||
@@ -4,15 +4,31 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from attrs import NOTHING, Attribute, Factory
|
||||
|
||||
from .._compat import is_bare_final
|
||||
from .._compat import get_args, is_annotated, is_bare_final
|
||||
from ..dispatch import StructureHook
|
||||
from ..errors import StructureHandlerNotFoundError
|
||||
from ..fns import raise_error
|
||||
from ._consts import AttributeOverride
|
||||
|
||||
if TYPE_CHECKING:
|
||||
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(
|
||||
a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False
|
||||
) -> StructureHook | None:
|
||||
|
||||
@@ -3,23 +3,12 @@ from __future__ import annotations
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Mapping
|
||||
from inspect import get_annotations
|
||||
from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar
|
||||
|
||||
from attrs import NOTHING, Attribute
|
||||
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 (
|
||||
get_full_type_hints,
|
||||
get_notrequired_base,
|
||||
@@ -40,7 +29,7 @@ from . import AttributeOverride
|
||||
from ._consts import already_generating, neutral
|
||||
from ._generics import generate_mapping
|
||||
from ._lc import generate_unique_filename
|
||||
from ._shared import find_structure_handler
|
||||
from ._shared import _annotated_override_or_default, find_structure_handler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..converters import BaseConverter
|
||||
@@ -50,6 +39,10 @@ __all__ = ["make_dict_structure_fn", "make_dict_unstructure_fn"]
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def get_annots(cl) -> dict[str, Any]:
|
||||
return get_annotations(cl, eval_str=True)
|
||||
|
||||
|
||||
def make_dict_unstructure_fn(
|
||||
cl: type[T],
|
||||
converter: BaseConverter,
|
||||
@@ -109,11 +102,20 @@ def make_dict_unstructure_fn(
|
||||
# * all attributes resolve to `converter._unstructure_identity`
|
||||
for a in attrs:
|
||||
attr_name = a.name
|
||||
override = kwargs.get(attr_name, neutral)
|
||||
t = a.type
|
||||
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:
|
||||
break
|
||||
handler = None
|
||||
t = a.type
|
||||
|
||||
if isinstance(t, TypeVar):
|
||||
if t.__name__ in mapping:
|
||||
@@ -125,9 +127,6 @@ def make_dict_unstructure_fn(
|
||||
t = deep_copy_with(t, mapping, cl)
|
||||
|
||||
if handler is None:
|
||||
nrb = get_notrequired_base(t)
|
||||
if nrb is not NOTHING:
|
||||
t = nrb
|
||||
try:
|
||||
handler = converter.get_unstructure_hook(t)
|
||||
except RecursionError:
|
||||
@@ -142,10 +141,22 @@ def make_dict_unstructure_fn(
|
||||
|
||||
for ix, a in enumerate(attrs):
|
||||
attr_name = a.name
|
||||
override = kwargs.get(attr_name, neutral)
|
||||
t = a.type
|
||||
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:
|
||||
lines.append(f" res.pop('{attr_name}', None)")
|
||||
continue
|
||||
|
||||
if override.rename is not None:
|
||||
# We also need to pop when renaming, since we're copying
|
||||
# the original.
|
||||
@@ -160,8 +171,6 @@ def make_dict_unstructure_fn(
|
||||
if override.unstruct_hook is not None:
|
||||
handler = override.unstruct_hook
|
||||
else:
|
||||
t = a.type
|
||||
|
||||
if isinstance(t, TypeVar):
|
||||
if t.__name__ in mapping:
|
||||
t = mapping[t.__name__]
|
||||
@@ -171,9 +180,6 @@ def make_dict_unstructure_fn(
|
||||
t = deep_copy_with(t, mapping, cl)
|
||||
|
||||
if handler is None:
|
||||
nrb = get_notrequired_base(t)
|
||||
if nrb is not NOTHING:
|
||||
t = nrb
|
||||
try:
|
||||
handler = converter.get_unstructure_hook(t)
|
||||
except RecursionError:
|
||||
@@ -220,12 +226,15 @@ def make_dict_unstructure_fn(
|
||||
)
|
||||
|
||||
eval(compile(script, fname, "exec"), globs)
|
||||
|
||||
res = globs[fn_name]
|
||||
res.overrides = kwargs
|
||||
finally:
|
||||
working_set.remove(cl)
|
||||
if not working_set:
|
||||
del already_generating.working_set
|
||||
|
||||
return globs[fn_name]
|
||||
return res
|
||||
|
||||
|
||||
def make_dict_structure_fn(
|
||||
@@ -326,20 +335,25 @@ def make_dict_structure_fn(
|
||||
for ix, a in enumerate(attrs):
|
||||
an = a.name
|
||||
attr_required = an in req_keys
|
||||
override = kwargs.get(an, neutral)
|
||||
t = a.type
|
||||
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:
|
||||
continue
|
||||
t = a.type
|
||||
|
||||
if isinstance(t, TypeVar):
|
||||
t = mapping.get(t.__name__, t)
|
||||
elif is_generic(t) and not is_bare(t) and not is_annotated(t):
|
||||
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):
|
||||
t = deep_copy_with(t, mapping, cl)
|
||||
|
||||
@@ -399,7 +413,12 @@ def make_dict_structure_fn(
|
||||
for ix, a in enumerate(attrs):
|
||||
an = a.name
|
||||
attr_required = an in req_keys
|
||||
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:
|
||||
continue
|
||||
if not attr_required:
|
||||
@@ -448,13 +467,18 @@ def make_dict_structure_fn(
|
||||
if non_required:
|
||||
for ix, a in non_required:
|
||||
an = a.name
|
||||
override = kwargs.get(an, neutral)
|
||||
t = a.type
|
||||
|
||||
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 isinstance(t, TypeVar):
|
||||
t = mapping.get(t.__name__, t)
|
||||
elif is_generic(t) and not is_bare(t) and not is_annotated(t):
|
||||
@@ -514,7 +538,9 @@ def make_dict_structure_fn(
|
||||
)
|
||||
|
||||
eval(compile(script, fname, "exec"), globs)
|
||||
return globs[fn_name]
|
||||
res = globs[fn_name]
|
||||
res.overrides = kwargs
|
||||
return res
|
||||
|
||||
|
||||
def _adapted_fields(cls: Any) -> list[Attribute]:
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Any, Callable, TypeVar, get_args
|
||||
from typing import Any, ParamSpec, TypeVar, get_args
|
||||
|
||||
from .._compat import is_subclass
|
||||
from ..converters import Converter, UnstructureHook
|
||||
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, _):
|
||||
if not isinstance(v, datetime):
|
||||
|
||||
@@ -99,11 +99,11 @@ def configure_converter(converter: BaseConverter):
|
||||
|
||||
# datetime inherits from date, so identity unstructure hook used
|
||||
# here to prevent the date unstructure hook running.
|
||||
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||
converter.register_unstructure_hook(datetime, identity)
|
||||
converter.register_structure_hook(datetime, validate_datetime)
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
||||
converter.register_unstructure_hook_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_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
||||
converter.register_unstructure_hook_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(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda _: identity)
|
||||
configure_union_passthrough(Union[str, bool, int, float, None], converter)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ def configure_converter(converter: BaseConverter) -> None:
|
||||
converter.register_structure_hook(
|
||||
date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date()
|
||||
)
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
|
||||
@@ -3,18 +3,27 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from base64 import b64decode
|
||||
from collections.abc import Callable
|
||||
from dataclasses import is_dataclass
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any, Callable, TypeVar, Union, get_type_hints
|
||||
from typing import Any, TypeVar, Union, get_type_hints
|
||||
|
||||
from attrs import has as attrs_has
|
||||
from attrs import resolve_types
|
||||
from msgspec import Struct, convert, to_builtins
|
||||
from msgspec.json import Encoder, decode
|
||||
|
||||
from .._compat import fields, get_args, get_origin, is_bare, is_mapping, is_sequence
|
||||
from .._compat import (
|
||||
fields,
|
||||
get_args,
|
||||
get_origin,
|
||||
is_bare,
|
||||
is_mapping,
|
||||
is_sequence,
|
||||
is_subclass,
|
||||
)
|
||||
from ..cols import is_namedtuple
|
||||
from ..converters import BaseConverter, Converter
|
||||
from ..dispatch import UnstructureHook
|
||||
@@ -74,7 +83,9 @@ def configure_converter(converter: Converter) -> None:
|
||||
configure_passthroughs(converter)
|
||||
|
||||
converter.register_unstructure_hook(Struct, to_builtins)
|
||||
converter.register_unstructure_hook(Enum, identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
lambda t: is_subclass(t, Enum), lambda t, c: identity
|
||||
)
|
||||
|
||||
converter.register_structure_hook(Struct, convert)
|
||||
converter.register_structure_hook(bytes, lambda v, _: b64decode(v))
|
||||
|
||||
@@ -87,8 +87,8 @@ def configure_converter(converter: Converter) -> None:
|
||||
),
|
||||
]
|
||||
)
|
||||
converter.register_unstructure_hook_func(
|
||||
partial(is_primitive_enum, include_bare_enums=True), identity
|
||||
converter.register_unstructure_hook_factory(
|
||||
partial(is_primitive_enum, include_bare_enums=True), lambda t: identity
|
||||
)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
|
||||
@@ -12,6 +12,7 @@ from tomlkit.items import Float, Integer, String
|
||||
|
||||
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
|
||||
|
||||
@@ -37,6 +38,9 @@ def configure_converter(converter: BaseConverter):
|
||||
* sets are serialized as lists
|
||||
* tuples are serializas as lists
|
||||
* 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_unstructure_hook(
|
||||
@@ -67,10 +71,12 @@ def configure_converter(converter: BaseConverter):
|
||||
|
||||
# datetime inherits from date, so identity unstructure hook used
|
||||
# here to prevent the date unstructure hook running.
|
||||
converter.register_unstructure_hook(datetime, lambda v: v)
|
||||
converter.register_unstructure_hook(datetime, identity)
|
||||
converter.register_structure_hook(datetime, validate_datetime)
|
||||
converter.register_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
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, String, bool, int, Integer, float, Float], converter
|
||||
)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"""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_unstructure_hook(date, lambda v: v.isoformat())
|
||||
converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v))
|
||||
converter.register_unstructure_hook_func(is_primitive_enum, identity)
|
||||
converter.register_unstructure_hook_factory(is_primitive_enum, lambda t: identity)
|
||||
converter.register_unstructure_hook_factory(
|
||||
is_literal_containing_enums, literals_with_enums_unstructure_factory
|
||||
)
|
||||
|
||||
@@ -9,21 +9,25 @@ from typing import Any, Callable, TypeVar, Union
|
||||
from ..converters import BaseConverter
|
||||
from ..gen import AttributeOverride, make_dict_structure_fn, make_dict_unstructure_fn
|
||||
from ..gen._consts import already_generating
|
||||
from ..subclasses import subclasses
|
||||
|
||||
|
||||
def _make_subclasses_tree(cl: type) -> list[type]:
|
||||
# get class origin for accessing subclasses (see #648 for more info)
|
||||
cls_origin = typing.get_origin(cl) or cl
|
||||
return [cl] + [
|
||||
sscl
|
||||
for scl in cls_origin.__subclasses__()
|
||||
for sscl in _make_subclasses_tree(scl)
|
||||
]
|
||||
|
||||
# Use a dict to deduplicate and keep insertion order.
|
||||
seen = {cl: None}
|
||||
for scl in subclasses(cls_origin):
|
||||
for sscl in _make_subclasses_tree(scl):
|
||||
seen[sscl] = None
|
||||
return list(seen)
|
||||
|
||||
|
||||
def _has_subclasses(cl: type, given_subclasses: tuple[type, ...]) -> bool:
|
||||
"""Whether the given class has subclasses from `given_subclasses`."""
|
||||
actual = set(cl.__subclasses__())
|
||||
cls_origin = typing.get_origin(cl) or cl
|
||||
actual = set(subclasses(cls_origin))
|
||||
given = set(given_subclasses)
|
||||
return bool(actual & given)
|
||||
|
||||
@@ -68,6 +72,9 @@ def include_subclasses(
|
||||
.. versionchanged:: 24.1.0
|
||||
When overrides are not provided, hooks for individual classes are retrieved from
|
||||
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
|
||||
collect()
|
||||
@@ -231,7 +238,13 @@ def _include_subclasses_with_union_strategy(
|
||||
return cls is _cl
|
||||
|
||||
converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook)
|
||||
subclasses = tuple([c for c in union_classes if issubclass(c, cl)])
|
||||
subclasses = tuple(
|
||||
[
|
||||
c
|
||||
for c in union_classes
|
||||
if issubclass(typing.get_origin(c) or c, typing.get_origin(cl) or cl)
|
||||
]
|
||||
)
|
||||
if len(subclasses) > 1:
|
||||
u = Union[subclasses] # type: ignore
|
||||
union_strategy(u, converter)
|
||||
|
||||
@@ -52,23 +52,10 @@ def configure_tagged_union(
|
||||
if is_type_alias(union):
|
||||
union = union.__value__
|
||||
args = union.__args__
|
||||
|
||||
tag_to_hook = {}
|
||||
exact_cl_unstruct_hooks = {}
|
||||
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}
|
||||
cl_to_tag = {}
|
||||
|
||||
if default is not NOTHING:
|
||||
default_handler = converter.get_structure_hook(default)
|
||||
@@ -76,36 +63,9 @@ def configure_tagged_union(
|
||||
def structure_default(val: dict, _cl=default, _h=default_handler):
|
||||
return _h(val, _cl)
|
||||
|
||||
tag_to_hook = defaultdict(lambda: structure_default, tag_to_hook)
|
||||
cl_to_tag = defaultdict(lambda: default, cl_to_tag)
|
||||
tag_to_hook = defaultdict(lambda: structure_default)
|
||||
cl_to_tag = defaultdict(lambda: default)
|
||||
|
||||
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):
|
||||
|
||||
def structure_tagged_union(
|
||||
@@ -135,11 +95,54 @@ def configure_tagged_union(
|
||||
return _tag_to_hook[val[_tag_name]](val)
|
||||
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_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 configure_union_passthrough(union: Any, converter: BaseConverter) -> None:
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
provided types and their subsets.
|
||||
@@ -162,7 +165,14 @@ def configure_union_passthrough(union: Any, converter: BaseConverter) -> None:
|
||||
If the union contains a class and one or more of its subclasses, the subclasses
|
||||
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
|
||||
.. versionchanged:: 25.2.0
|
||||
Introduced the `accept_ints_as_floats` parameter.
|
||||
"""
|
||||
args = set(union.__args__)
|
||||
|
||||
@@ -205,6 +215,16 @@ def configure_union_passthrough(union: Any, converter: BaseConverter) -> None:
|
||||
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:
|
||||
spillover_type = (
|
||||
Union[tuple(spillover)] if len(spillover) > 1 else next(iter(spillover))
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
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__")
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
uv
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,138 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: importlib-metadata
|
||||
Version: 6.8.0
|
||||
Summary: Read metadata from Python packages
|
||||
Home-page: https://github.com/python/importlib_metadata
|
||||
Author: Jason R. Coombs
|
||||
Author-email: jaraco@jaraco.com
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Requires-Python: >=3.8
|
||||
License-File: LICENSE
|
||||
Requires-Dist: zipp (>=0.5)
|
||||
Requires-Dist: typing-extensions (>=3.6.4) ; python_version < "3.8"
|
||||
Provides-Extra: docs
|
||||
Requires-Dist: sphinx (>=3.5) ; extra == 'docs'
|
||||
Requires-Dist: jaraco.packaging (>=9) ; extra == 'docs'
|
||||
Requires-Dist: rst.linker (>=1.9) ; extra == 'docs'
|
||||
Requires-Dist: furo ; extra == 'docs'
|
||||
Requires-Dist: sphinx-lint ; extra == 'docs'
|
||||
Requires-Dist: jaraco.tidelift (>=1.4) ; extra == 'docs'
|
||||
Provides-Extra: perf
|
||||
Requires-Dist: ipython ; extra == 'perf'
|
||||
Provides-Extra: testing
|
||||
Requires-Dist: pytest (>=6) ; extra == 'testing'
|
||||
Requires-Dist: pytest-checkdocs (>=2.4) ; extra == 'testing'
|
||||
Requires-Dist: pytest-cov ; extra == 'testing'
|
||||
Requires-Dist: pytest-enabler (>=2.2) ; extra == 'testing'
|
||||
Requires-Dist: pytest-ruff ; extra == 'testing'
|
||||
Requires-Dist: packaging ; extra == 'testing'
|
||||
Requires-Dist: pyfakefs ; extra == 'testing'
|
||||
Requires-Dist: flufl.flake8 ; extra == 'testing'
|
||||
Requires-Dist: pytest-perf (>=0.9.2) ; extra == 'testing'
|
||||
Requires-Dist: pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == 'testing'
|
||||
Requires-Dist: pytest-mypy (>=0.9.1) ; (platform_python_implementation != "PyPy") and extra == 'testing'
|
||||
Requires-Dist: importlib-resources (>=1.3) ; (python_version < "3.9") and extra == 'testing'
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/importlib_metadata.svg
|
||||
:target: https://pypi.org/project/importlib_metadata
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/importlib_metadata.svg
|
||||
|
||||
.. image:: https://github.com/python/importlib_metadata/workflows/tests/badge.svg
|
||||
:target: https://github.com/python/importlib_metadata/actions?query=workflow%3A%22tests%22
|
||||
:alt: tests
|
||||
|
||||
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
|
||||
:target: https://github.com/astral-sh/ruff
|
||||
:alt: Ruff
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:alt: Code style: Black
|
||||
|
||||
.. image:: https://readthedocs.org/projects/importlib-metadata/badge/?version=latest
|
||||
:target: https://importlib-metadata.readthedocs.io/en/latest/?badge=latest
|
||||
|
||||
.. image:: https://img.shields.io/badge/skeleton-2023-informational
|
||||
:target: https://blog.jaraco.com/skeleton
|
||||
|
||||
.. image:: https://tidelift.com/badges/package/pypi/importlib-metadata
|
||||
:target: https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=readme
|
||||
|
||||
Library to access the metadata for a Python package.
|
||||
|
||||
This package supplies third-party access to the functionality of
|
||||
`importlib.metadata <https://docs.python.org/3/library/importlib.metadata.html>`_
|
||||
including improvements added to subsequent Python versions.
|
||||
|
||||
|
||||
Compatibility
|
||||
=============
|
||||
|
||||
New features are introduced in this third-party library and later merged
|
||||
into CPython. The following table indicates which versions of this library
|
||||
were contributed to different versions in the standard library:
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - importlib_metadata
|
||||
- stdlib
|
||||
* - 6.5
|
||||
- 3.12
|
||||
* - 4.13
|
||||
- 3.11
|
||||
* - 4.6
|
||||
- 3.10
|
||||
* - 1.4
|
||||
- 3.8
|
||||
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
See the `online documentation <https://importlib-metadata.readthedocs.io/>`_
|
||||
for usage details.
|
||||
|
||||
`Finder authors
|
||||
<https://docs.python.org/3/reference/import.html#finders-and-loaders>`_ can
|
||||
also add support for custom package installers. See the above documentation
|
||||
for details.
|
||||
|
||||
|
||||
Caveats
|
||||
=======
|
||||
|
||||
This project primarily supports third-party packages installed by PyPA
|
||||
tools (or other conforming packages). It does not support:
|
||||
|
||||
- Packages in the stdlib.
|
||||
- Packages installed without metadata.
|
||||
|
||||
Project details
|
||||
===============
|
||||
|
||||
* Project home: https://github.com/python/importlib_metadata
|
||||
* Report bugs at: https://github.com/python/importlib_metadata/issues
|
||||
* Code hosting: https://github.com/python/importlib_metadata
|
||||
* Documentation: https://importlib-metadata.readthedocs.io/
|
||||
|
||||
For Enterprise
|
||||
==============
|
||||
|
||||
Available as part of the Tidelift Subscription.
|
||||
|
||||
This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use.
|
||||
|
||||
`Learn more <https://tidelift.com/subscription/pkg/pypi-importlib-metadata?utm_source=pypi-importlib-metadata&utm_medium=referral&utm_campaign=github>`_.
|
||||
|
||||
Security Contact
|
||||
================
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
`Tidelift security contact <https://tidelift.com/security>`_.
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
@@ -0,0 +1,17 @@
|
||||
importlib_metadata-6.8.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
||||
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/RECORD,,
|
||||
importlib_metadata-6.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
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/__init__.py,sha256=EiH0qTKP_6oa6pRGJgPrq0kvjnL3hJ18BJH8VaAYSBA,30749
|
||||
importlib_metadata/_adapters.py,sha256=i8S6Ib1OQjcILA-l4gkzktMZe18TaeUNI49PLRp6OBU,2454
|
||||
importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
|
||||
importlib_metadata/_compat.py,sha256=zhjcWMfA9SNExFVVVBozOYbuiok0A4tdMsNk9ZDZi-A,1554
|
||||
importlib_metadata/_functools.py,sha256=PsY2-4rrKX4RVeRC1oGp1lB1pmC9eKN88_f-bD9uOoA,2895
|
||||
importlib_metadata/_itertools.py,sha256=cvr_2v8BRbxcIl5x5ldfqdHjhI8Yi8s8yk50G_nm6jQ,2068
|
||||
importlib_metadata/_meta.py,sha256=kypMW_-xSStooSm0WpJc6eupjT-Ipc2ZBIl23PyC3No,1613
|
||||
importlib_metadata/_py39compat.py,sha256=2Tk5twb_VgLCY-1NEAQjdZp_S9OFMC-pUzP2isuaPsQ,1098
|
||||
importlib_metadata/_text.py,sha256=HCsFksZpJLeTP3NEk_ngrAeXVRRtTrtyh9eOABoRP4A,2166
|
||||
importlib_metadata/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.40.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
importlib_metadata
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
import functools
|
||||
import warnings
|
||||
import re
|
||||
import textwrap
|
||||
import email.message
|
||||
|
||||
from ._text import FoldedCase
|
||||
from ._compat import pypy_partial
|
||||
|
||||
|
||||
# Do not remove prior to 2024-01-01 or Python 3.14
|
||||
_warn = functools.partial(
|
||||
warnings.warn,
|
||||
"Implicit None on return values is deprecated and will raise KeyErrors.",
|
||||
DeprecationWarning,
|
||||
stacklevel=pypy_partial(2),
|
||||
)
|
||||
|
||||
|
||||
class Message(email.message.Message):
|
||||
multiple_use_keys = set(
|
||||
map(
|
||||
FoldedCase,
|
||||
[
|
||||
'Classifier',
|
||||
'Obsoletes-Dist',
|
||||
'Platform',
|
||||
'Project-URL',
|
||||
'Provides-Dist',
|
||||
'Provides-Extra',
|
||||
'Requires-Dist',
|
||||
'Requires-External',
|
||||
'Supported-Platform',
|
||||
'Dynamic',
|
||||
],
|
||||
)
|
||||
)
|
||||
"""
|
||||
Keys that may be indicated multiple times per PEP 566.
|
||||
"""
|
||||
|
||||
def __new__(cls, orig: email.message.Message):
|
||||
res = super().__new__(cls)
|
||||
vars(res).update(vars(orig))
|
||||
return res
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._headers = self._repair_headers()
|
||||
|
||||
# suppress spurious error from mypy
|
||||
def __iter__(self):
|
||||
return super().__iter__()
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""
|
||||
Warn users that a ``KeyError`` can be expected when a
|
||||
mising key is supplied. Ref python/importlib_metadata#371.
|
||||
"""
|
||||
res = super().__getitem__(item)
|
||||
if res is None:
|
||||
_warn()
|
||||
return res
|
||||
|
||||
def _repair_headers(self):
|
||||
def redent(value):
|
||||
"Correct for RFC822 indentation"
|
||||
if not value or '\n' not in value:
|
||||
return value
|
||||
return textwrap.dedent(' ' * 8 + value)
|
||||
|
||||
headers = [(key, redent(value)) for key, value in vars(self)['_headers']]
|
||||
if self._payload:
|
||||
headers.append(('Description', self.get_payload()))
|
||||
return headers
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""
|
||||
Convert PackageMetadata to a JSON-compatible format
|
||||
per PEP 0566.
|
||||
"""
|
||||
|
||||
def transform(key):
|
||||
value = self.get_all(key) if key in self.multiple_use_keys else self[key]
|
||||
if key == 'Keywords':
|
||||
value = re.split(r'\s+', value)
|
||||
tk = key.lower().replace('-', '_')
|
||||
return tk, value
|
||||
|
||||
return dict(map(transform, map(FoldedCase, self)))
|
||||
@@ -0,0 +1,30 @@
|
||||
import collections
|
||||
|
||||
|
||||
# from jaraco.collections 3.3
|
||||
class FreezableDefaultDict(collections.defaultdict):
|
||||
"""
|
||||
Often it is desirable to prevent the mutation of
|
||||
a default dict after its initial construction, such
|
||||
as to prevent mutation during iteration.
|
||||
|
||||
>>> dd = FreezableDefaultDict(list)
|
||||
>>> dd[0].append('1')
|
||||
>>> dd.freeze()
|
||||
>>> dd[1]
|
||||
[]
|
||||
>>> len(dd)
|
||||
1
|
||||
"""
|
||||
|
||||
def __missing__(self, key):
|
||||
return getattr(self, '_frozen', super().__missing__)(key)
|
||||
|
||||
def freeze(self):
|
||||
self._frozen = lambda key: self.default_factory()
|
||||
|
||||
|
||||
class Pair(collections.namedtuple('Pair', 'name value')):
|
||||
@classmethod
|
||||
def parse(cls, text):
|
||||
return cls(*map(str.strip, text.split("=", 1)))
|
||||
@@ -0,0 +1,67 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
__all__ = ['install', 'NullFinder']
|
||||
|
||||
|
||||
def install(cls):
|
||||
"""
|
||||
Class decorator for installation on sys.meta_path.
|
||||
|
||||
Adds the backport DistributionFinder to sys.meta_path and
|
||||
attempts to disable the finder functionality of the stdlib
|
||||
DistributionFinder.
|
||||
"""
|
||||
sys.meta_path.append(cls())
|
||||
disable_stdlib_finder()
|
||||
return cls
|
||||
|
||||
|
||||
def disable_stdlib_finder():
|
||||
"""
|
||||
Give the backport primacy for discovering path-based distributions
|
||||
by monkey-patching the stdlib O_O.
|
||||
|
||||
See #91 for more background for rationale on this sketchy
|
||||
behavior.
|
||||
"""
|
||||
|
||||
def matches(finder):
|
||||
return getattr(
|
||||
finder, '__module__', None
|
||||
) == '_frozen_importlib_external' and hasattr(finder, 'find_distributions')
|
||||
|
||||
for finder in filter(matches, sys.meta_path): # pragma: nocover
|
||||
del finder.find_distributions
|
||||
|
||||
|
||||
class NullFinder:
|
||||
"""
|
||||
A "Finder" (aka "MetaClassFinder") that never finds any modules,
|
||||
but may find distributions.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def find_spec(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
def pypy_partial(val):
|
||||
"""
|
||||
Adjust for variable stacklevel on partial under PyPy.
|
||||
|
||||
Workaround for #327.
|
||||
"""
|
||||
is_pypy = platform.python_implementation() == 'PyPy'
|
||||
return val + is_pypy
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
StrPath = Union[str, os.PathLike[str]]
|
||||
else:
|
||||
# PathLike is only subscriptable at runtime in 3.9+
|
||||
StrPath = Union[str, "os.PathLike[str]"] # pragma: no cover
|
||||
@@ -0,0 +1,104 @@
|
||||
import types
|
||||
import functools
|
||||
|
||||
|
||||
# from jaraco.functools 3.3
|
||||
def method_cache(method, cache_wrapper=None):
|
||||
"""
|
||||
Wrap lru_cache to support storing the cache data in the object instances.
|
||||
|
||||
Abstracts the common paradigm where the method explicitly saves an
|
||||
underscore-prefixed protected property on first call and returns that
|
||||
subsequently.
|
||||
|
||||
>>> class MyClass:
|
||||
... calls = 0
|
||||
...
|
||||
... @method_cache
|
||||
... def method(self, value):
|
||||
... self.calls += 1
|
||||
... return value
|
||||
|
||||
>>> a = MyClass()
|
||||
>>> a.method(3)
|
||||
3
|
||||
>>> for x in range(75):
|
||||
... res = a.method(x)
|
||||
>>> a.calls
|
||||
75
|
||||
|
||||
Note that the apparent behavior will be exactly like that of lru_cache
|
||||
except that the cache is stored on each instance, so values in one
|
||||
instance will not flush values from another, and when an instance is
|
||||
deleted, so are the cached values for that instance.
|
||||
|
||||
>>> b = MyClass()
|
||||
>>> for x in range(35):
|
||||
... res = b.method(x)
|
||||
>>> b.calls
|
||||
35
|
||||
>>> a.method(0)
|
||||
0
|
||||
>>> a.calls
|
||||
75
|
||||
|
||||
Note that if method had been decorated with ``functools.lru_cache()``,
|
||||
a.calls would have been 76 (due to the cached value of 0 having been
|
||||
flushed by the 'b' instance).
|
||||
|
||||
Clear the cache with ``.cache_clear()``
|
||||
|
||||
>>> a.method.cache_clear()
|
||||
|
||||
Same for a method that hasn't yet been called.
|
||||
|
||||
>>> c = MyClass()
|
||||
>>> c.method.cache_clear()
|
||||
|
||||
Another cache wrapper may be supplied:
|
||||
|
||||
>>> cache = functools.lru_cache(maxsize=2)
|
||||
>>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache)
|
||||
>>> a = MyClass()
|
||||
>>> a.method2()
|
||||
3
|
||||
|
||||
Caution - do not subsequently wrap the method with another decorator, such
|
||||
as ``@property``, which changes the semantics of the function.
|
||||
|
||||
See also
|
||||
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
|
||||
for another implementation and additional justification.
|
||||
"""
|
||||
cache_wrapper = cache_wrapper or functools.lru_cache()
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
# it's the first call, replace the method with a cached, bound method
|
||||
bound_method = types.MethodType(method, self)
|
||||
cached_method = cache_wrapper(bound_method)
|
||||
setattr(self, method.__name__, cached_method)
|
||||
return cached_method(*args, **kwargs)
|
||||
|
||||
# Support cache clear even before cache has been created.
|
||||
wrapper.cache_clear = lambda: None
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# From jaraco.functools 3.3
|
||||
def pass_none(func):
|
||||
"""
|
||||
Wrap func so it's not called if its first param is None
|
||||
|
||||
>>> print_text = pass_none(print)
|
||||
>>> print_text('text')
|
||||
text
|
||||
>>> print_text(None)
|
||||
"""
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(param, *args, **kwargs):
|
||||
if param is not None:
|
||||
return func(param, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
@@ -0,0 +1,73 @@
|
||||
from itertools import filterfalse
|
||||
|
||||
|
||||
def unique_everseen(iterable, key=None):
|
||||
"List unique elements, preserving order. Remember all elements ever seen."
|
||||
# unique_everseen('AAAABBBCCDAABBB') --> A B C D
|
||||
# unique_everseen('ABBCcAD', str.lower) --> A B C D
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
if key is None:
|
||||
for element in filterfalse(seen.__contains__, iterable):
|
||||
seen_add(element)
|
||||
yield element
|
||||
else:
|
||||
for element in iterable:
|
||||
k = key(element)
|
||||
if k not in seen:
|
||||
seen_add(k)
|
||||
yield element
|
||||
|
||||
|
||||
# copied from more_itertools 8.8
|
||||
def always_iterable(obj, base_type=(str, bytes)):
|
||||
"""If *obj* is iterable, return an iterator over its items::
|
||||
|
||||
>>> obj = (1, 2, 3)
|
||||
>>> list(always_iterable(obj))
|
||||
[1, 2, 3]
|
||||
|
||||
If *obj* is not iterable, return a one-item iterable containing *obj*::
|
||||
|
||||
>>> obj = 1
|
||||
>>> list(always_iterable(obj))
|
||||
[1]
|
||||
|
||||
If *obj* is ``None``, return an empty iterable:
|
||||
|
||||
>>> obj = None
|
||||
>>> list(always_iterable(None))
|
||||
[]
|
||||
|
||||
By default, binary and text strings are not considered iterable::
|
||||
|
||||
>>> obj = 'foo'
|
||||
>>> list(always_iterable(obj))
|
||||
['foo']
|
||||
|
||||
If *base_type* is set, objects for which ``isinstance(obj, base_type)``
|
||||
returns ``True`` won't be considered iterable.
|
||||
|
||||
>>> obj = {'a': 1}
|
||||
>>> list(always_iterable(obj)) # Iterate over the dict's keys
|
||||
['a']
|
||||
>>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit
|
||||
[{'a': 1}]
|
||||
|
||||
Set *base_type* to ``None`` to avoid any special handling and treat objects
|
||||
Python considers iterable as iterable:
|
||||
|
||||
>>> obj = 'foo'
|
||||
>>> list(always_iterable(obj, base_type=None))
|
||||
['f', 'o', 'o']
|
||||
"""
|
||||
if obj is None:
|
||||
return iter(())
|
||||
|
||||
if (base_type is not None) and isinstance(obj, base_type):
|
||||
return iter((obj,))
|
||||
|
||||
try:
|
||||
return iter(obj)
|
||||
except TypeError:
|
||||
return iter((obj,))
|
||||
@@ -0,0 +1,63 @@
|
||||
from typing import Protocol
|
||||
from typing import Any, Dict, Iterator, List, Optional, TypeVar, Union, overload
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class PackageMetadata(Protocol):
|
||||
def __len__(self) -> int:
|
||||
... # pragma: no cover
|
||||
|
||||
def __contains__(self, item: str) -> bool:
|
||||
... # pragma: no cover
|
||||
|
||||
def __getitem__(self, key: str) -> str:
|
||||
... # pragma: no cover
|
||||
|
||||
def __iter__(self) -> Iterator[str]:
|
||||
... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get(self, name: str, failobj: None = None) -> Optional[str]:
|
||||
... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get(self, name: str, failobj: _T) -> Union[str, _T]:
|
||||
... # pragma: no cover
|
||||
|
||||
# overload per python/importlib_metadata#435
|
||||
@overload
|
||||
def get_all(self, name: str, failobj: None = None) -> Optional[List[Any]]:
|
||||
... # pragma: no cover
|
||||
|
||||
@overload
|
||||
def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
|
||||
"""
|
||||
Return all values associated with a possibly multi-valued key.
|
||||
"""
|
||||
|
||||
@property
|
||||
def json(self) -> Dict[str, Union[str, List[str]]]:
|
||||
"""
|
||||
A JSON-compatible form of the metadata.
|
||||
"""
|
||||
|
||||
|
||||
class SimplePath(Protocol[_T]):
|
||||
"""
|
||||
A minimal subset of pathlib.Path required by PathDistribution.
|
||||
"""
|
||||
|
||||
def joinpath(self, other: Union[str, _T]) -> _T:
|
||||
... # pragma: no cover
|
||||
|
||||
def __truediv__(self, other: Union[str, _T]) -> _T:
|
||||
... # pragma: no cover
|
||||
|
||||
@property
|
||||
def parent(self) -> _T:
|
||||
... # pragma: no cover
|
||||
|
||||
def read_text(self) -> str:
|
||||
... # pragma: no cover
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
Compatibility layer with Python 3.8/3.9
|
||||
"""
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
# Prevent circular imports on runtime.
|
||||
from . import Distribution, EntryPoint
|
||||
else:
|
||||
Distribution = EntryPoint = Any
|
||||
|
||||
|
||||
def normalized_name(dist: Distribution) -> Optional[str]:
|
||||
"""
|
||||
Honor name normalization for distributions that don't provide ``_normalized_name``.
|
||||
"""
|
||||
try:
|
||||
return dist._normalized_name
|
||||
except AttributeError:
|
||||
from . import Prepared # -> delay to prevent circular imports.
|
||||
|
||||
return Prepared.normalize(getattr(dist, "name", None) or dist.metadata['Name'])
|
||||
|
||||
|
||||
def ep_matches(ep: EntryPoint, **params) -> bool:
|
||||
"""
|
||||
Workaround for ``EntryPoint`` objects without the ``matches`` method.
|
||||
"""
|
||||
try:
|
||||
return ep.matches(**params)
|
||||
except AttributeError:
|
||||
from . import EntryPoint # -> delay to prevent circular imports.
|
||||
|
||||
# Reconstruct the EntryPoint object to make sure it is compatible.
|
||||
return EntryPoint(ep.name, ep.value, ep.group).matches(**params)
|
||||
@@ -0,0 +1,99 @@
|
||||
import re
|
||||
|
||||
from ._functools import method_cache
|
||||
|
||||
|
||||
# from jaraco.text 3.5
|
||||
class FoldedCase(str):
|
||||
"""
|
||||
A case insensitive string class; behaves just like str
|
||||
except compares equal when the only variation is case.
|
||||
|
||||
>>> s = FoldedCase('hello world')
|
||||
|
||||
>>> s == 'Hello World'
|
||||
True
|
||||
|
||||
>>> 'Hello World' == s
|
||||
True
|
||||
|
||||
>>> s != 'Hello World'
|
||||
False
|
||||
|
||||
>>> s.index('O')
|
||||
4
|
||||
|
||||
>>> s.split('O')
|
||||
['hell', ' w', 'rld']
|
||||
|
||||
>>> sorted(map(FoldedCase, ['GAMMA', 'alpha', 'Beta']))
|
||||
['alpha', 'Beta', 'GAMMA']
|
||||
|
||||
Sequence membership is straightforward.
|
||||
|
||||
>>> "Hello World" in [s]
|
||||
True
|
||||
>>> s in ["Hello World"]
|
||||
True
|
||||
|
||||
You may test for set inclusion, but candidate and elements
|
||||
must both be folded.
|
||||
|
||||
>>> FoldedCase("Hello World") in {s}
|
||||
True
|
||||
>>> s in {FoldedCase("Hello World")}
|
||||
True
|
||||
|
||||
String inclusion works as long as the FoldedCase object
|
||||
is on the right.
|
||||
|
||||
>>> "hello" in FoldedCase("Hello World")
|
||||
True
|
||||
|
||||
But not if the FoldedCase object is on the left:
|
||||
|
||||
>>> FoldedCase('hello') in 'Hello World'
|
||||
False
|
||||
|
||||
In that case, use in_:
|
||||
|
||||
>>> FoldedCase('hello').in_('Hello World')
|
||||
True
|
||||
|
||||
>>> FoldedCase('hello') > FoldedCase('Hello')
|
||||
False
|
||||
"""
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.lower() < other.lower()
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.lower() > other.lower()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.lower() == other.lower()
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.lower() != other.lower()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.lower())
|
||||
|
||||
def __contains__(self, other):
|
||||
return super().lower().__contains__(other.lower())
|
||||
|
||||
def in_(self, other):
|
||||
"Does self appear in other?"
|
||||
return self in FoldedCase(other)
|
||||
|
||||
# cache lower since it's likely to be called frequently.
|
||||
@method_cache
|
||||
def lower(self):
|
||||
return super().lower()
|
||||
|
||||
def index(self, sub):
|
||||
return self.lower().index(sub.lower())
|
||||
|
||||
def split(self, splitter=' ', maxsplit=0):
|
||||
pattern = re.compile(re.escape(splitter), re.I)
|
||||
return pattern.split(self, maxsplit)
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,18 +0,0 @@
|
||||
Copyright © 2017 Erez Shinan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,47 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: lark
|
||||
Version: 1.2.2
|
||||
Summary: a modern parsing library
|
||||
Author-email: Erez Shinan <erezshin@gmail.com>
|
||||
License: MIT
|
||||
Project-URL: Homepage, https://github.com/lark-parser/lark
|
||||
Project-URL: Download, https://github.com/lark-parser/lark/tarball/master
|
||||
Keywords: Earley,LALR,parser,parsing,ast
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Text Processing :: General
|
||||
Classifier: Topic :: Text Processing :: Linguistic
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/markdown
|
||||
License-File: LICENSE
|
||||
Provides-Extra: atomic_cache
|
||||
Requires-Dist: atomicwrites ; extra == 'atomic_cache'
|
||||
Provides-Extra: interegular
|
||||
Requires-Dist: interegular <0.4.0,>=0.3.1 ; extra == 'interegular'
|
||||
Provides-Extra: nearley
|
||||
Requires-Dist: js2py ; extra == 'nearley'
|
||||
Provides-Extra: regex
|
||||
Requires-Dist: regex ; extra == 'regex'
|
||||
|
||||
Lark is a modern general-purpose parsing library for Python.
|
||||
With Lark, you can parse any context-free grammar, efficiently, with very little code.
|
||||
Main Features:
|
||||
- Builds a parse-tree (AST) automagically, based on the structure of the grammar
|
||||
- Earley parser
|
||||
- Can parse all context-free grammars
|
||||
- Full support for ambiguous grammars
|
||||
- LALR(1) parser
|
||||
- Fast and light, competitive with PLY
|
||||
- Can generate a stand-alone parser
|
||||
- CYK parser, for highly ambiguous grammars
|
||||
- EBNF grammar
|
||||
- Unicode fully supported
|
||||
- Automatic line & column tracking
|
||||
- Standard library of terminals (strings, numbers, names, etc.)
|
||||
- Import grammars from Nearley.js
|
||||
- Extensive test suite
|
||||
- And much more!
|
||||
Since version 1.2, only Python versions 3.8 and up are supported.
|
||||
@@ -1,83 +0,0 @@
|
||||
lark-1.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
lark-1.2.2.dist-info/LICENSE,sha256=Lu5g9S1OETV7-J5ysDTQUOKF5H_aE2HlZi-zIu4n13E,1055
|
||||
lark-1.2.2.dist-info/METADATA,sha256=S-69HuNJr0ktlvb7J5XE48ghb_6ahYn8ksdW9HcB-d0,1831
|
||||
lark-1.2.2.dist-info/RECORD,,
|
||||
lark-1.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lark-1.2.2.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
|
||||
lark-1.2.2.dist-info/entry_points.txt,sha256=WXYg_uCUdFlxQDPUhli3HFah37bNNFQfXLdzCqsacGI,61
|
||||
lark-1.2.2.dist-info/top_level.txt,sha256=dyS6jg8hCHHkXWvsfcIMO8rjlv_bdzAxiE0lkkzJ5hk,5
|
||||
lark/__init__.py,sha256=bc0tK7h7XwHA-Y4vVeJoNIqSMA-MHVTihq8yy795WXo,744
|
||||
lark/__pycache__/__init__.cpython-311.pyc,,
|
||||
lark/__pycache__/ast_utils.cpython-311.pyc,,
|
||||
lark/__pycache__/common.cpython-311.pyc,,
|
||||
lark/__pycache__/exceptions.cpython-311.pyc,,
|
||||
lark/__pycache__/grammar.cpython-311.pyc,,
|
||||
lark/__pycache__/indenter.cpython-311.pyc,,
|
||||
lark/__pycache__/lark.cpython-311.pyc,,
|
||||
lark/__pycache__/lexer.cpython-311.pyc,,
|
||||
lark/__pycache__/load_grammar.cpython-311.pyc,,
|
||||
lark/__pycache__/parse_tree_builder.cpython-311.pyc,,
|
||||
lark/__pycache__/parser_frontends.cpython-311.pyc,,
|
||||
lark/__pycache__/reconstruct.cpython-311.pyc,,
|
||||
lark/__pycache__/tree.cpython-311.pyc,,
|
||||
lark/__pycache__/tree_matcher.cpython-311.pyc,,
|
||||
lark/__pycache__/tree_templates.cpython-311.pyc,,
|
||||
lark/__pycache__/utils.cpython-311.pyc,,
|
||||
lark/__pycache__/visitors.cpython-311.pyc,,
|
||||
lark/__pyinstaller/__init__.py,sha256=_PpFm44f_mwHlCpvYgv9ZgubLfNDc3PlePVir4sxRfI,182
|
||||
lark/__pyinstaller/__pycache__/__init__.cpython-311.pyc,,
|
||||
lark/__pyinstaller/__pycache__/hook-lark.cpython-311.pyc,,
|
||||
lark/__pyinstaller/hook-lark.py,sha256=5aFHiZWVHPRdHT8qnb4kW4JSOql5GusHodHR25_q9sU,599
|
||||
lark/ast_utils.py,sha256=jwn44ocNQhZGbfcFsEZnwi_gGvPbNgzjQ-0RuEtwDzI,2117
|
||||
lark/common.py,sha256=M9-CFAUP3--OkftyyWjke-Kc1-pQMczT1MluHCFwdy4,3008
|
||||
lark/exceptions.py,sha256=g76ygMPfSMl6ukKqFAZVpR2EAJTOOdyfJ_ALXc_MCR8,10939
|
||||
lark/grammar.py,sha256=DR17QSLSKCRhMOqx2UQh4n-Ywu4CD-wjdQxtuM8OHkY,3665
|
||||
lark/grammars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lark/grammars/__pycache__/__init__.cpython-311.pyc,,
|
||||
lark/grammars/common.lark,sha256=FV9xGIPiPqHRM4ULAxP6jApXRTVsSwbOe697I9s7DLs,885
|
||||
lark/grammars/lark.lark,sha256=nq1NTZYqm_DPI2mjRIlpd3ZcxPjGhapA4GUzkcfBTQs,1541
|
||||
lark/grammars/python.lark,sha256=WMakTkpzCqOd0jUjYONI3LOnSy2KRN9NoL9pFtAZYCI,10641
|
||||
lark/grammars/unicode.lark,sha256=d9YCz0XWimdl4F8M5YCptavBcFG9D58Yd4aMwxjYtEI,96
|
||||
lark/indenter.py,sha256=L5uNDYUMNrk4ZTWKmW0Tu-H-3GGErLOHygMC32N_twE,4221
|
||||
lark/lark.py,sha256=_IHWmTxt43kfd9eYVtwx58zEWWSFAq9_gKH7Oeu5PZs,28184
|
||||
lark/lexer.py,sha256=OwgQPCpQ-vUi-2aeZztsydd4DLkEgCbZeucvEPvHFi4,24037
|
||||
lark/load_grammar.py,sha256=WYZDxyO6omhA8NKyMjSckfAMwVKuIMF3liiYXE_-kHo,53946
|
||||
lark/parse_tree_builder.py,sha256=jT_3gCEkBGZoTXAWSnhMn1kRuJILWB-E7XkUciYNHI4,14412
|
||||
lark/parser_frontends.py,sha256=mxMXxux2hkfTfE859wuVp4-Fr1no6YVEUt8toDjEdPQ,10165
|
||||
lark/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lark/parsers/__pycache__/__init__.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/cyk.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/earley.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/earley_common.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/earley_forest.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/grammar_analysis.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/lalr_analysis.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/lalr_interactive_parser.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/lalr_parser.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/lalr_parser_state.cpython-311.pyc,,
|
||||
lark/parsers/__pycache__/xearley.cpython-311.pyc,,
|
||||
lark/parsers/cyk.py,sha256=c3GLk3kq23Xwb8MqUOjvivwP488KJY6NUWgxqeR5980,12192
|
||||
lark/parsers/earley.py,sha256=03sW9vfBkcH4NR72EBt8HkndDKSVSH3IdRnDulXWy24,15117
|
||||
lark/parsers/earley_common.py,sha256=e2e6NrNucw-WMiNV8HqQ_TpGx6P7v_S8f5aEcF0Tkqo,1620
|
||||
lark/parsers/earley_forest.py,sha256=w4JTb4tVMewue8dL-gCO96-Uo0wd4BbQUfSfIhr7txY,31332
|
||||
lark/parsers/grammar_analysis.py,sha256=rQ4Sn9EP8gjXGTZXEiWLW0KByPPpeKpN5hSIQZgNl3I,7141
|
||||
lark/parsers/lalr_analysis.py,sha256=DGHFk2tIluIyeFEVFfsMRU77DVbd598IJnUUOXO04yo,12207
|
||||
lark/parsers/lalr_interactive_parser.py,sha256=LsgfT1gdne8pXHTCsN6bl6zD6Pdh2dDqp1rIWOzp7Yw,5757
|
||||
lark/parsers/lalr_parser.py,sha256=6U8jP1AlUsuGxgJBWMq15WuGuyaolsLPevcf8HZ_zZk,4586
|
||||
lark/parsers/lalr_parser_state.py,sha256=QZ12p4CtvcvFAIKIqkeDBJYgEU3ntQllBJDYXb419ls,3793
|
||||
lark/parsers/xearley.py,sha256=DboXMNtuN0G-SXrrDm5zgUDUekz85h0Rih2PRvcf1LM,7825
|
||||
lark/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lark/reconstruct.py,sha256=s7CevBXchUG_fe2otdAITxIaSXCEIiSjy4Sbh5QC0hs,3763
|
||||
lark/tools/__init__.py,sha256=FeKYmVUjXSt-vlQm2ktyWkcxaOCTOkZnHD_kOUWjUuA,2469
|
||||
lark/tools/__pycache__/__init__.cpython-311.pyc,,
|
||||
lark/tools/__pycache__/nearley.cpython-311.pyc,,
|
||||
lark/tools/__pycache__/serialize.cpython-311.pyc,,
|
||||
lark/tools/__pycache__/standalone.cpython-311.pyc,,
|
||||
lark/tools/nearley.py,sha256=QaLYdW6mYQdDq8JKMisV3lvPqzF0wPgu8q8BtsSA33g,6265
|
||||
lark/tools/serialize.py,sha256=nwt46LNxkDm0T_Uh9k2wS4fcfgvZQ2dy4-YC_aKhTQk,965
|
||||
lark/tools/standalone.py,sha256=6eXDqBuzZSpE5BGZm_Fh6X5yRhAPYxNVyl2aUU3ABzA,5627
|
||||
lark/tree.py,sha256=aWWHMazid8bbJanhmCjK9XK2jRFJ6N6WmlwXJGTsz28,8522
|
||||
lark/tree_matcher.py,sha256=jHdZJggn405SXmPpGf9U9HLrrsfP4eNNZaj267UTB00,6003
|
||||
lark/tree_templates.py,sha256=sSnfw1m8txAkJOYhcQrooG7xajVyVplunzTnNsxY720,6139
|
||||
lark/utils.py,sha256=3qd1-c0YgHYklvx1hA28qF7N_Ty1Zz6TbtCFMzQanNk,11270
|
||||
lark/visitors.py,sha256=VJ3T1m8p78MwXJotpOAvn06mYEqKyuIlhsAF51U-a3w,21422
|
||||
@@ -1,2 +0,0 @@
|
||||
[pyinstaller40]
|
||||
hook-dirs = lark.__pyinstaller:get_hook_dirs
|
||||
@@ -1 +0,0 @@
|
||||
lark
|
||||
@@ -1,38 +0,0 @@
|
||||
from .exceptions import (
|
||||
GrammarError,
|
||||
LarkError,
|
||||
LexError,
|
||||
ParseError,
|
||||
UnexpectedCharacters,
|
||||
UnexpectedEOF,
|
||||
UnexpectedInput,
|
||||
UnexpectedToken,
|
||||
)
|
||||
from .lark import Lark
|
||||
from .lexer import Token
|
||||
from .tree import ParseTree, Tree
|
||||
from .utils import logger
|
||||
from .visitors import Discard, Transformer, Transformer_NonRecursive, Visitor, v_args
|
||||
|
||||
__version__: str = "1.2.2"
|
||||
|
||||
__all__ = (
|
||||
"GrammarError",
|
||||
"LarkError",
|
||||
"LexError",
|
||||
"ParseError",
|
||||
"UnexpectedCharacters",
|
||||
"UnexpectedEOF",
|
||||
"UnexpectedInput",
|
||||
"UnexpectedToken",
|
||||
"Lark",
|
||||
"Token",
|
||||
"ParseTree",
|
||||
"Tree",
|
||||
"logger",
|
||||
"Discard",
|
||||
"Transformer",
|
||||
"Transformer_NonRecursive",
|
||||
"Visitor",
|
||||
"v_args",
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user