Compare commits
184
Commits
0.4.10
...
2026.9.700
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1eccf90f4 | ||
|
|
da9091fa38 | ||
|
|
439c700226 | ||
|
|
fa13531668 | ||
|
|
4f11ed7ecf | ||
|
|
c1355970bf | ||
|
|
3167c18bea | ||
|
|
0666a87d15 | ||
|
|
ca7c23a0f6 | ||
|
|
5d23df8da7 | ||
|
|
de310a6604 | ||
|
|
abf32a5e50 | ||
|
|
aa0780dd51 | ||
|
|
b2656599b2 | ||
|
|
01e8670cc1 | ||
|
|
b2e6e9d250 | ||
|
|
d9d619c1dc | ||
|
|
b6228503fc | ||
|
|
4de05caac7 | ||
|
|
5c3ebc4c82 | ||
|
|
5e5c7dcc42 | ||
|
|
84821d2852 | ||
|
|
547da644ce | ||
|
|
757b885f28 | ||
|
|
3762a01d40 | ||
|
|
91b86f29cb | ||
|
|
f88a50d4ab | ||
|
|
c28836933c | ||
|
|
91c8c4aff3 | ||
|
|
d8611d7aea | ||
|
|
7e9a4359cd | ||
|
|
b7f28ab1a3 | ||
|
|
9b368ba761 | ||
|
|
82e13cf7ce | ||
|
|
d104906508 | ||
|
|
a5ff3b55ff | ||
|
|
26225eb8ed | ||
|
|
844138b383 | ||
|
|
d7eb72f417 | ||
|
|
af5acfc946 | ||
|
|
20f76b6a20 | ||
|
|
081b488fe3 | ||
|
|
b37eea43d9 | ||
|
|
89add18273 | ||
|
|
e47c3bda69 | ||
|
|
41d117fe2c | ||
|
|
53ebc5d055 | ||
|
|
a0a0d38fe5 | ||
|
|
52b81b3dcf | ||
|
|
c3d5116885 | ||
|
|
07ccd2d26a | ||
|
|
af195a577b | ||
|
|
ecb50be2b8 | ||
|
|
61d4785775 | ||
|
|
541704f45a | ||
|
|
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 |
@@ -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
|
||||
|
||||
+3
-1
@@ -8,4 +8,6 @@ __pycache__
|
||||
*.pyc
|
||||
.nox
|
||||
*.g4
|
||||
.antlr
|
||||
.antlr
|
||||
.claude
|
||||
/test/postprocessor/
|
||||
|
||||
Vendored
+112
-11
@@ -10,12 +10,75 @@
|
||||
"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/postprocessor"
|
||||
],
|
||||
"cwd": "${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"outFiles": ["${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js"],
|
||||
"linux": {
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/test/postprocessor"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
]
|
||||
},
|
||||
"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"
|
||||
},
|
||||
{
|
||||
// Linux workaround: js-debug's extensionHost attach probes localhost and [::1]
|
||||
// in parallel and aborts when [::1] is refused. Start the dev host ourselves
|
||||
// with a fixed inspector port and attach directly to 127.0.0.1.
|
||||
"name": "Run Extension (Linux)",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"address": "127.0.0.1",
|
||||
"port": 9333,
|
||||
"timeout": 30000,
|
||||
"continueOnAttach": true,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"preLaunchTask": "NX Post Support: Start Dev Host (Linux)"
|
||||
},
|
||||
{
|
||||
"name": "Debug Extension (Linux, hidden)",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"address": "127.0.0.1",
|
||||
"port": 9333,
|
||||
"timeout": 30000,
|
||||
"continueOnAttach": true,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"preLaunchTask": "NX Post Support: Start Dev Host with debugpy (Linux)",
|
||||
"presentation": {
|
||||
"hidden": true,
|
||||
"group": "",
|
||||
"order": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -34,10 +97,37 @@
|
||||
"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/postprocessor"
|
||||
],
|
||||
"cwd": "${env:TEMP}/nx-post-support-vscode-debug",
|
||||
"outFiles": ["${env:TEMP}/nx-post-support-vscode-debug/dist/**/*.js"],
|
||||
"linux": {
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"${workspaceFolder}",
|
||||
"${workspaceFolder}/test/postprocessor"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/dist/**/*.js",
|
||||
"!**/node_modules/**"
|
||||
]
|
||||
},
|
||||
"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 +139,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,12 +154,22 @@
|
||||
"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": "",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Debug Extension and Python (Linux)",
|
||||
"configurations": ["Python debug server (hidden)", "Debug Extension (Linux, hidden)"],
|
||||
"stopAll": true,
|
||||
"presentation": {
|
||||
"hidden": false,
|
||||
"group": "",
|
||||
"order": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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
+74
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"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"
|
||||
],
|
||||
"linux": {
|
||||
"command": "npm",
|
||||
"args": ["run", "compile:debug"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "NX Post Support: Start Dev Host (Linux)",
|
||||
"detail": "Opens the Extension Development Host with the inspector on 127.0.0.1:9333 (workaround for js-debug localhost/::1 attach bug).",
|
||||
"type": "shell",
|
||||
"command": "\"$(dirname \"${execPath}\")/bin/$(basename \"${execPath}\")\" --new-window --inspect-brk-extensions=9333 --extensionDevelopmentPath=\"${workspaceFolder}\" \"${workspaceFolder}\" \"${workspaceFolder}/test/postprocessor\"",
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "/bin/bash",
|
||||
"args": ["-c"]
|
||||
}
|
||||
},
|
||||
"dependsOn": "NX Post Support: Compile Debug",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"panel": "dedicated"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "NX Post Support: Start Dev Host with debugpy (Linux)",
|
||||
"detail": "Same as above, but the language server connects to the debugpy listener on 127.0.0.1:5678.",
|
||||
"type": "shell",
|
||||
"command": "\"$(dirname \"${execPath}\")/bin/$(basename \"${execPath}\")\" --new-window --inspect-brk-extensions=9333 --extensionDevelopmentPath=\"${workspaceFolder}\" \"${workspaceFolder}\" \"${workspaceFolder}/test/postprocessor\"",
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "/bin/bash",
|
||||
"args": ["-c"]
|
||||
},
|
||||
"env": {
|
||||
"USE_DEBUGPY": "True",
|
||||
"NXPS_DEBUG_HOST": "127.0.0.1",
|
||||
"NXPS_DEBUG_PORT": "5678"
|
||||
}
|
||||
},
|
||||
"dependsOn": "NX Post Support: Compile Debug",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"panel": "dedicated"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+9
-1
@@ -9,9 +9,17 @@ vite.config.js
|
||||
.prettierrc.json
|
||||
esbuild.js
|
||||
.gitea
|
||||
**/.ruff_cache/**
|
||||
.venv/**
|
||||
.nox/**
|
||||
server/.venv/**
|
||||
server/.nox/**
|
||||
**/__pycache__/**
|
||||
**/requirements.txt
|
||||
**/requirements.in
|
||||
**/server/src/_debug_server.py
|
||||
**/server/src/_debug_server.py
|
||||
server/noxfile.py
|
||||
server/.claude/**
|
||||
server/tests/**
|
||||
client/**
|
||||
dist/**/*.map
|
||||
|
||||
+42
-1
@@ -1,3 +1,36 @@
|
||||
## Unreleased
|
||||
|
||||
- Reparse only the top-level TCL commands touched by an edit instead of the whole file
|
||||
- Cache the workspace index in the extension storage so restarts skip reparsing unchanged files; the cache is discarded automatically when the server or bundled tclint changes
|
||||
- Speed up TclOO completion, signature help, inlay hints, and Go to Definition by reusing the cached syntax tree and skipping files without classes
|
||||
- Speed up references, document highlights, and call hierarchy with cached definition lookups
|
||||
- Keep background indexing from blocking requests that need a fresh syntax tree
|
||||
- Add Go to Definition for TclOO classes, constructors, and resolved methods, including PSC library definitions
|
||||
- Index PSC layer scripts (including external paths and legacy Windows encoding) and share TclOO class metadata across files for completion, signatures, inlay hints, and highlighting
|
||||
- Add document-local TclOO method completion for `new`/`create` instances, `my`, and statically inferred return chains
|
||||
- Suggest TclOO class names as classes and semantically highlight their declarations and calls
|
||||
- Show signature help and parameter inlay hints for resolved TclOO methods and constructors, including optional and variadic arguments
|
||||
- Add incoming and outgoing call hierarchy for custom TCL procedures and MOM event handlers
|
||||
- Add document highlights for procedure and variable occurrences
|
||||
- Make completion context-aware and prioritize local, current-file, workspace, and built-in symbols
|
||||
- Add command-aware completion for Tcl subcommands, fixed arguments, and valid options
|
||||
- Add semantic argument completion for variables, procedures, namespaces, and local file paths
|
||||
- Add placeholder-based snippets for common Tcl structures and `dict for`
|
||||
- Integrate the NX Tcl Remote Debugger directly into NX Postprocessor Support
|
||||
- Add `nx-tcl` attach configurations and breakpoint support for TCL and DEF files
|
||||
- Support breakpoints, stepping, stack frames, variables, watches, evaluation, logpoints, hit conditions, and Tcl error stops
|
||||
- Add generation-safe stack and variable references so delayed VS Code requests cannot address stale NX frames
|
||||
- Add command-buffer breakpoint handling for `LIB_GE_command_buffer_edit_*` source bodies
|
||||
- Extend the README with launch configuration, path mapping, and debugger usage
|
||||
- Prevent truncated TCL inlay hints and add configurable parameter hint modes
|
||||
- Add inlay hints for built-in NX procedures, variadic arguments, and visible ranges
|
||||
- Add inlay hint documentation and navigation to custom procedure definitions
|
||||
- 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
|
||||
- Improve TCL response times with debounced edits and cached semantic, inlay, hover, completion, and variable indexes
|
||||
- Debounce CDL/DEF diagnostics and remove per-line diagnostic logging
|
||||
|
||||
## [0.0.1]
|
||||
|
||||
- Initial release
|
||||
@@ -12,4 +45,12 @@
|
||||
|
||||
## [0.2.0]
|
||||
|
||||
- Add DEF File Support
|
||||
- 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,116 @@
|
||||
# NX Postprocessor Support
|
||||
|
||||
A comprehensive VS Code extension providing language support and remote debugging 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
|
||||
- **Call Hierarchy** - Traces incoming and outgoing calls between custom TCL procedures and MOM event handlers
|
||||
- **Document Highlights** - Highlights all reads, writes, and calls of the symbol under the cursor
|
||||
- **Context-aware Completion** - Prioritizes local symbols and suggests variables, procedures, namespaces, paths, Tcl subcommands, valid argument values, and options based on cursor context
|
||||
- **PSC and TclOO Classes** - Indexes Tcl scripts referenced by PSC layers, including external folders. Classes from indexed files provide method completion, signature help, parameter hints, and class highlighting in other files. PSC script names may omit `.tcl`; relative folders resolve from the PSC directory, and environment-variable folders are supported. Missing scripts are reported in the output channel; encrypted libraries cannot supply static class metadata.
|
||||
- **Tcl Snippets** - Inserts placeholder-based structures for `if`, `foreach`, `proc`, `switch`, `try`, and `dict for`
|
||||
- **NX Tcl Remote Debugger** - Breakpoints, stepping, call stack, scopes, variables, watches, evaluation, logpoints, hit conditions, and Tcl error stops directly in a running NX Post process
|
||||
|
||||
## 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.12 or higher
|
||||
3. Open any `.cdl`, `.tcl`, or `.def` file
|
||||
4. The extension will automatically activate and provide language support
|
||||
|
||||
The former standalone `NX Tcl Remote Debugger` extension is no longer required. Disable or
|
||||
uninstall `local-nx.nx-tcl-debug` before using the integrated debugger because both extensions
|
||||
register the same `nx-tcl` debug type.
|
||||
|
||||
## 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)
|
||||
- `nx-post-support.inlayHints.parameterNames` - Show parameter names for `all`, only `literals`, or `none` (default: `all`)
|
||||
- `nx-post-support.inlayHints.suppressWhenArgumentMatchesName` - Hide redundant hints such as `value:` before `$value` (default: true)
|
||||
|
||||
TCL files default to unlimited inlay hint length so that VS Code does not
|
||||
truncate later parameter names on a line. An explicit user setting for
|
||||
`editor.inlayHints.maximumLength` still takes precedence.
|
||||
|
||||
## NX Tcl Remote Debugger
|
||||
|
||||
### Add a VS Code attach configuration
|
||||
|
||||
Create `.vscode/launch.json` through **Run and Debug: create a launch.json file** and select
|
||||
**NX Tcl: Attach to NX Post**, or use this configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "nx-tcl",
|
||||
"request": "attach",
|
||||
"name": "Attach to NX Post Tcl",
|
||||
"host": "127.0.0.1",
|
||||
"port": 4711,
|
||||
"connectTimeout": 120000,
|
||||
"stopOnEntry": false,
|
||||
"breakOnError": true,
|
||||
"localRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When VS Code and NX see the source through different roots, set `remoteRoot` to the root used by
|
||||
NX and keep `localRoot` as the corresponding workspace root.
|
||||
|
||||
### Start debugging
|
||||
|
||||
1. Set breakpoints on executable Tcl or DEF commands.
|
||||
2. Start **Attach to NX Post Tcl** in VS Code before running the postprocessor.
|
||||
3. Start postprocessing in NX.
|
||||
4. Use Continue, Step Over, Step Into, Step Out, Pause, Variables, Watch, and the Debug Console as
|
||||
with a normal source debugger.
|
||||
|
||||
Breakpoints remain active and stop at every real invocation. Use a hit condition such as `1` for
|
||||
a one-time stop. Blank lines, comments, declarations, and multiline Tcl commands may be relocated
|
||||
to the nearest executable command; VS Code shows the resolved line. Dynamic
|
||||
`LIB_GE_command_buffer_edit_*` bodies are mapped back to their original source and owning Tcl
|
||||
procedure.
|
||||
|
||||
## 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
|
||||
- Incoming and outgoing call hierarchy for custom procedures and MOM event handlers
|
||||
- Document-wide highlights for procedure and variable occurrences
|
||||
- Context-aware completion with local symbols ranked before workspace and built-in symbols, plus semantic arguments, local paths, Tcl subcommands, and options such as `string compare -nocase`
|
||||
- Placeholder-based snippets for common Tcl control structures and procedures
|
||||
- Remote NX Tcl debugging with breakpoints and full stepping
|
||||
|
||||
## 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"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
export interface CdlEventHandler {
|
||||
eventName: string
|
||||
parameterNames: string[]
|
||||
toggleOffParameterNames?: string[]
|
||||
}
|
||||
|
||||
function structuralCode(line: string): string {
|
||||
let result = ""
|
||||
let inString = false
|
||||
let escaped = false
|
||||
|
||||
for (const character of line) {
|
||||
if (escaped) {
|
||||
escaped = false
|
||||
result += inString ? " " : character
|
||||
continue
|
||||
}
|
||||
if (character === "\\") {
|
||||
escaped = true
|
||||
result += inString ? " " : character
|
||||
continue
|
||||
}
|
||||
if (character === '"') {
|
||||
inString = !inString
|
||||
result += " "
|
||||
continue
|
||||
}
|
||||
if (character === "#" && !inString) {
|
||||
break
|
||||
}
|
||||
result += inString ? " " : character
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function cdlEventHandlerAtLine(
|
||||
source: string,
|
||||
declarationLine: number
|
||||
): CdlEventHandler | undefined {
|
||||
const lines = source.split(/\r?\n/)
|
||||
const declaration = lines[declarationLine]
|
||||
if (declaration === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const eventMatch = /^\s*EVENT\s+([^\s{]+)/.exec(structuralCode(declaration))
|
||||
if (!eventMatch) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const parameterNames: string[] = []
|
||||
const toggleOffParameterNames: string[] = []
|
||||
let currentParameter: string | undefined
|
||||
let eventOpened = false
|
||||
let depth = 0
|
||||
|
||||
eventLines: for (let lineNumber = declarationLine; lineNumber < lines.length; lineNumber++) {
|
||||
const line = lines[lineNumber]
|
||||
const code = structuralCode(line)
|
||||
|
||||
const tokens = code.match(/[{}]|[^\s{}]+/g) ?? []
|
||||
for (let index = 0; index < tokens.length; index++) {
|
||||
const token = tokens[index]
|
||||
if (token === "{") {
|
||||
eventOpened = true
|
||||
depth++
|
||||
} else if (token === "}" && eventOpened) {
|
||||
depth--
|
||||
if (depth === 1) currentParameter = undefined
|
||||
if (depth <= 0) break eventLines
|
||||
} else if (depth === 1 && token === "PARAM") {
|
||||
const name = tokens[index + 1]
|
||||
if (name && name !== "{" && name !== "}") {
|
||||
currentParameter = name
|
||||
parameterNames.push(name)
|
||||
index++
|
||||
}
|
||||
} else if (depth === 2 && currentParameter && token === "TOGGLE") {
|
||||
if (tokens[index + 1]?.toLowerCase() === "off") {
|
||||
toggleOffParameterNames.push(currentParameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
eventName: eventMatch[1],
|
||||
parameterNames,
|
||||
toggleOffParameterNames
|
||||
}
|
||||
}
|
||||
|
||||
function momEventName(eventName: string): string {
|
||||
return `MOM_${eventName.replace(/^MOM_/i, "")}`
|
||||
}
|
||||
|
||||
function momVariableName(parameterName: string): string {
|
||||
return `mom_${parameterName.replace(/^mom_/i, "")}`
|
||||
}
|
||||
|
||||
export function createCdlEventHandlerSnippet(handler: CdlEventHandler): string {
|
||||
const toggleOffParameters = new Set(handler.toggleOffParameterNames ?? [])
|
||||
const globals = [
|
||||
...new Set(handler.parameterNames.flatMap((parameter) => {
|
||||
const variable = momVariableName(parameter)
|
||||
return toggleOffParameters.has(parameter) ? [variable, `${variable}_defined`] : [variable]
|
||||
}))
|
||||
]
|
||||
const lines = [`proc ${momEventName(handler.eventName)} {args} {`]
|
||||
|
||||
if (globals.length > 0) {
|
||||
lines.push(...globals.map((variable) => ` global ${variable}`), "")
|
||||
}
|
||||
lines.push(" #Put your UDE Handler Tcl here", "", "}")
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
+304
-31
@@ -1,4 +1,7 @@
|
||||
import * as vscode from "vscode"
|
||||
import { cdlEventHandlerAtLine, createCdlEventHandlerSnippet } from "./cdlEventHandler"
|
||||
|
||||
const MACHINE_HEADER_REGEX = /^MACHINE\s+\S+/
|
||||
|
||||
export function formatCdlFile(content: string): string {
|
||||
let indentLevel = 0
|
||||
@@ -37,15 +40,19 @@ export function formatDefFile(content: string): string {
|
||||
}
|
||||
|
||||
export function isFirstLineMachine(content: string): boolean {
|
||||
const lines = content.split("\n").map((line) => line.trim())
|
||||
for (const line of lines) {
|
||||
console.log(line)
|
||||
let lineStart = 0
|
||||
while (lineStart <= content.length) {
|
||||
const newline = content.indexOf("\n", lineStart)
|
||||
const lineEnd = newline === -1 ? content.length : newline
|
||||
const line = content.slice(lineStart, lineEnd).trim()
|
||||
if (line === "" || line.startsWith("#")) {
|
||||
console.log("skipping line")
|
||||
if (newline === -1) {
|
||||
return false
|
||||
}
|
||||
lineStart = newline + 1
|
||||
continue
|
||||
}
|
||||
const machineRegex = /^MACHINE\s+\S+/
|
||||
return machineRegex.test(line)
|
||||
return MACHINE_HEADER_REGEX.test(line)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -53,11 +60,9 @@ export function isFirstLineMachine(content: string): boolean {
|
||||
export function diagnosticHandler(document: vscode.TextDocument) {
|
||||
const diagnostics: vscode.Diagnostic[] = []
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
if (!isFirstLineMachine(document.getText())) {
|
||||
const range = new vscode.Range(
|
||||
document.positionAt(0),
|
||||
document.positionAt(document.getText().length)
|
||||
)
|
||||
const text = document.getText()
|
||||
if (!isFirstLineMachine(text)) {
|
||||
const range = new vscode.Range(document.positionAt(0), document.positionAt(text.length))
|
||||
const diagnostic = new vscode.Diagnostic(
|
||||
range,
|
||||
"The first line should contain 'MACHINE'.",
|
||||
@@ -71,7 +76,7 @@ export function diagnosticHandler(document: vscode.TextDocument) {
|
||||
|
||||
export function completionHandlerCdl(document: vscode.TextDocument, position: vscode.Position) {
|
||||
const linePrefix = document.lineAt(position).text.substring(0, position.character)
|
||||
const categories = ["MILL", "LATHE", "DRILL"]
|
||||
const categories = ["MILL", "LATHE", "DRILL", "INVALID"]
|
||||
|
||||
if (linePrefix.endsWith("TYPE ")) {
|
||||
return [
|
||||
@@ -110,33 +115,124 @@ export function completionHandlerCdl(document: vscode.TextDocument, position: vs
|
||||
}
|
||||
|
||||
export function hoverCdlHandler(document: vscode.TextDocument, position: vscode.Position) {
|
||||
const wordRange = document.getWordRangeAtPosition(position)
|
||||
const word = document.getText(wordRange)
|
||||
const text = document.getText()
|
||||
const lines = text.split("\n")
|
||||
|
||||
let hoverText: string | undefined
|
||||
|
||||
for (const line of lines) {
|
||||
const words = line.split(/\s+/)
|
||||
const wordIndex = words.indexOf(word)
|
||||
|
||||
if (wordIndex > 0) {
|
||||
if (words[wordIndex - 1] === "EVENT") {
|
||||
hoverText = `MOM_${word}`
|
||||
} else if (words[wordIndex - 1] === "PARAM") {
|
||||
hoverText = `mom_${word}`
|
||||
const line = document.lineAt(position.line).text
|
||||
const eventMatch = /^\s*EVENT\s+([^\s{]+)/.exec(line)
|
||||
if (eventMatch) {
|
||||
const eventStart = line.indexOf(eventMatch[1], eventMatch.index)
|
||||
const declarationEnd = eventStart + eventMatch[1].length
|
||||
if (position.character <= declarationEnd) {
|
||||
const handler = cdlEventHandlerAtLine(document.getText(), position.line)
|
||||
if (handler) {
|
||||
const markdown = new vscode.MarkdownString()
|
||||
markdown.appendCodeblock(createCdlEventHandlerSnippet(handler), "tcl")
|
||||
return new vscode.Hover(
|
||||
markdown,
|
||||
new vscode.Range(position.line, eventMatch.index, position.line, declarationEnd)
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (hoverText) {
|
||||
return new vscode.Hover(hoverText)
|
||||
const parameterMatch = /^\s*PARAM\s+([^\s{]+)/.exec(line)
|
||||
if (parameterMatch) {
|
||||
const parameterStart = line.indexOf(parameterMatch[1], parameterMatch.index)
|
||||
const parameterEnd = parameterStart + parameterMatch[1].length
|
||||
if (position.character >= parameterStart && position.character <= parameterEnd) {
|
||||
return new vscode.Hover(`mom_${parameterMatch[1].replace(/^mom_/i, "")}`)
|
||||
}
|
||||
}
|
||||
|
||||
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 || []
|
||||
}
|
||||
|
||||
+75
-36
@@ -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,
|
||||
@@ -22,7 +22,19 @@ import {
|
||||
import { getLSClientTraceLevel, getProjectRoot } from "./utilities"
|
||||
import { isVirtualWorkspace } from "./vscodeapi"
|
||||
|
||||
export type IInitOptions = { settings: ISettings[]; globalSettings: ISettings }
|
||||
export type IInitOptions = {
|
||||
settings: ISettings[]
|
||||
globalSettings: ISettings
|
||||
// Folder for the server's persistent index cache; omitted without a workspace.
|
||||
indexCachePath?: string
|
||||
}
|
||||
|
||||
let _disposables: Disposable[] = []
|
||||
|
||||
export function disposeServerResources(): void {
|
||||
_disposables.forEach((disposable) => disposable.dispose())
|
||||
_disposables = []
|
||||
}
|
||||
|
||||
async function createServer(
|
||||
settings: ISettings,
|
||||
@@ -32,13 +44,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 +75,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 +91,7 @@ async function createServer(
|
||||
}
|
||||
|
||||
// Options to control the language client
|
||||
const tclFileWatcher = workspace.createFileSystemWatcher("**/*.{tcl,psc,def}")
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
// Register the server for python documents
|
||||
documentSelector: isVirtualWorkspace()
|
||||
@@ -76,56 +105,66 @@ 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,
|
||||
outputChannel: LogOutputChannel,
|
||||
lsClient?: LanguageClient
|
||||
lsClient?: LanguageClient,
|
||||
indexCachePath?: string
|
||||
): Promise<LanguageClient | undefined> {
|
||||
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, {
|
||||
settings: await getExtensionSettings(serverId, true),
|
||||
globalSettings: await getGlobalSettings(serverId, false)
|
||||
})
|
||||
traceInfo(`Server: Start requested.`)
|
||||
_disposables.push(
|
||||
newLSClient.onDidChangeState((e) => {
|
||||
switch (e.newState) {
|
||||
case State.Stopped:
|
||||
traceVerbose(`Server State: Stopped`)
|
||||
break
|
||||
case State.Starting:
|
||||
traceVerbose(`Server State: Starting`)
|
||||
break
|
||||
case State.Running:
|
||||
traceVerbose(`Server State: Running`)
|
||||
break
|
||||
}
|
||||
})
|
||||
)
|
||||
try {
|
||||
const newLSClient = await createServer(
|
||||
workspaceSetting,
|
||||
serverId,
|
||||
serverName,
|
||||
outputChannel,
|
||||
{
|
||||
settings: await getExtensionSettings(serverId, true),
|
||||
globalSettings: await getGlobalSettings(serverId, false),
|
||||
indexCachePath
|
||||
}
|
||||
)
|
||||
traceInfo(`Server: Start requested.`)
|
||||
_disposables.push(
|
||||
newLSClient.onDidChangeState((e) => {
|
||||
switch (e.newState) {
|
||||
case State.Stopped:
|
||||
traceVerbose(`Server State: Stopped`)
|
||||
break
|
||||
case State.Starting:
|
||||
traceVerbose(`Server State: Starting`)
|
||||
break
|
||||
case State.Running:
|
||||
traceVerbose(`Server State: Running`)
|
||||
break
|
||||
}
|
||||
})
|
||||
)
|
||||
await newLSClient.start()
|
||||
const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel)
|
||||
await newLSClient.setTrace(level)
|
||||
return newLSClient
|
||||
} catch (ex) {
|
||||
traceError(`Server: Start failed: ${ex}`)
|
||||
disposeServerResources()
|
||||
return undefined
|
||||
}
|
||||
|
||||
const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel)
|
||||
await newLSClient.setTrace(level)
|
||||
return newLSClient
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface ISettings {
|
||||
interpreter: string[]
|
||||
importStrategy: string
|
||||
showNotifications: string
|
||||
formatter: boolean
|
||||
inlayHint: boolean
|
||||
inlayHints: {
|
||||
parameterNames: "all" | "literals" | "none"
|
||||
suppressWhenArgumentMatchesName: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function getExtensionSettings(
|
||||
@@ -79,7 +85,14 @@ export async function getWorkspaceSettings(
|
||||
interpreter: resolveVariables(interpreter, workspace),
|
||||
importStrategy: config.get<string>(`importStrategy`) ?? "useBundled",
|
||||
showNotifications: config.get<string>(`showNotifications`) ?? "off",
|
||||
formatter: config.get<boolean>(`formatter`) ?? true
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true,
|
||||
inlayHints: {
|
||||
parameterNames:
|
||||
config.get<"all" | "literals" | "none">(`inlayHints.parameterNames`) ?? "all",
|
||||
suppressWhenArgumentMatchesName:
|
||||
config.get<boolean>(`inlayHints.suppressWhenArgumentMatchesName`) ?? true
|
||||
}
|
||||
}
|
||||
return workspaceSetting
|
||||
}
|
||||
@@ -111,7 +124,14 @@ export async function getGlobalSettings(
|
||||
interpreter: interpreter,
|
||||
importStrategy: getGlobalValue<string>(config, "importStrategy", "useBundled"),
|
||||
showNotifications: getGlobalValue<string>(config, "showNotifications", "off"),
|
||||
formatter: config.get<boolean>(`formatter`) ?? true
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true,
|
||||
inlayHints: {
|
||||
parameterNames:
|
||||
config.get<"all" | "literals" | "none">(`inlayHints.parameterNames`) ?? "all",
|
||||
suppressWhenArgumentMatchesName:
|
||||
config.get<boolean>(`inlayHints.suppressWhenArgumentMatchesName`) ?? true
|
||||
}
|
||||
}
|
||||
return setting
|
||||
}
|
||||
@@ -126,7 +146,10 @@ export function checkIfConfigurationChanged(
|
||||
`${namespace}.interpreter`,
|
||||
`${namespace}.importStrategy`,
|
||||
`${namespace}.showNotifications`,
|
||||
`${namespace}.formatter`
|
||||
`${namespace}.formatter`,
|
||||
`${namespace}.inlayHint`,
|
||||
`${namespace}.inlayHints.parameterNames`,
|
||||
`${namespace}.inlayHints.suppressWhenArgumentMatchesName`
|
||||
]
|
||||
const changed = settings.map((s) => e.affectsConfiguration(s))
|
||||
return changed.includes(true)
|
||||
|
||||
@@ -0,0 +1,650 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { RuntimeConnection, encodeHex, decodeHex } = require('./protocol');
|
||||
|
||||
const THREAD_ID = 1;
|
||||
|
||||
class NxDebugAdapter {
|
||||
constructor(input = process.stdin, output = process.stdout) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
this.inputBuffer = Buffer.alloc(0);
|
||||
this.sequence = 1;
|
||||
this.runtime = null;
|
||||
this.terminated = false;
|
||||
this.explicitDisconnect = false;
|
||||
this.localRoot = '';
|
||||
this.remoteRoot = '';
|
||||
this.nextBreakpointId = 1;
|
||||
// VS Code can expose the same physical file through more than one source
|
||||
// identity (for example, drive mappings or differently cased workspace
|
||||
// roots). SETBPS replaces all breakpoints for a runtime path, so retain
|
||||
// each DAP source group and send their union to NX.
|
||||
this.sourceBreakpointGroups = new Map();
|
||||
this.pendingStartRequest = null;
|
||||
this.resumeInProgress = 0;
|
||||
this.pendingStoppedEvents = [];
|
||||
this.isPaused = false;
|
||||
// DAP frame/variable IDs can outlive a stop in the VS Code UI. Runtime
|
||||
// levels and references, however, are valid only for the current Tcl
|
||||
// pause. Give every stop adapter-owned IDs so a late request can never
|
||||
// accidentally address a same-numbered frame from a later pause.
|
||||
this.nextFrameId = 1;
|
||||
this.frameReferences = new Map();
|
||||
this.runtimeToFrameReference = new Map();
|
||||
this.nextVariablesReference = 1;
|
||||
this.variableReferences = new Map();
|
||||
this.runtimeToVariableReference = new Map();
|
||||
|
||||
input.on('data', (chunk) => this.onData(chunk));
|
||||
input.on('end', () => this.dispose());
|
||||
input.resume();
|
||||
}
|
||||
|
||||
onData(chunk) {
|
||||
this.inputBuffer = Buffer.concat([this.inputBuffer, chunk]);
|
||||
while (true) {
|
||||
const headerEnd = this.inputBuffer.indexOf('\r\n\r\n');
|
||||
if (headerEnd < 0) return;
|
||||
const header = this.inputBuffer.subarray(0, headerEnd).toString('ascii');
|
||||
const match = /Content-Length:\s*(\d+)/i.exec(header);
|
||||
if (!match) throw new Error('missing DAP Content-Length header');
|
||||
const contentLength = Number(match[1]);
|
||||
const messageEnd = headerEnd + 4 + contentLength;
|
||||
if (this.inputBuffer.length < messageEnd) return;
|
||||
const payload = this.inputBuffer.subarray(headerEnd + 4, messageEnd).toString('utf8');
|
||||
this.inputBuffer = this.inputBuffer.subarray(messageEnd);
|
||||
const message = JSON.parse(payload);
|
||||
if (message.type === 'request') {
|
||||
this.handleRequest(message).catch((error) => this.sendErrorResponse(message, error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send(message) {
|
||||
message.seq = this.sequence++;
|
||||
const json = JSON.stringify(message);
|
||||
this.output.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
|
||||
}
|
||||
|
||||
sendResponse(request, body = {}) {
|
||||
this.send({
|
||||
type: 'response',
|
||||
request_seq: request.seq,
|
||||
success: true,
|
||||
command: request.command,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
sendErrorResponse(request, error) {
|
||||
this.send({
|
||||
type: 'response',
|
||||
request_seq: request.seq,
|
||||
success: false,
|
||||
command: request.command,
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
|
||||
sendEvent(event, body = {}) {
|
||||
this.send({ type: 'event', event, body });
|
||||
}
|
||||
|
||||
async handleRequest(request) {
|
||||
const args = request.arguments || {};
|
||||
switch (request.command) {
|
||||
case 'initialize':
|
||||
this.sendResponse(request, {
|
||||
supportsConfigurationDoneRequest: true,
|
||||
supportsConditionalBreakpoints: true,
|
||||
supportsHitConditionalBreakpoints: true,
|
||||
supportsLogPoints: true,
|
||||
supportsEvaluateForHovers: true,
|
||||
supportsSetVariable: true,
|
||||
supportsExceptionFilterOptions: true,
|
||||
exceptionBreakpointFilters: [
|
||||
{ filter: 'tclError', label: 'Tcl errors', description: 'Break when a Tcl command returns TCL_ERROR' },
|
||||
],
|
||||
supportsTerminateRequest: true,
|
||||
});
|
||||
break;
|
||||
|
||||
case 'attach':
|
||||
case 'launch':
|
||||
await this.attach(args);
|
||||
this.pendingStartRequest = request;
|
||||
this.sendEvent('initialized');
|
||||
break;
|
||||
|
||||
case 'configurationDone':
|
||||
await this.runtime.request('CONFIGDONE');
|
||||
this.sendResponse(request);
|
||||
if (this.pendingStartRequest) {
|
||||
this.sendResponse(this.pendingStartRequest);
|
||||
this.pendingStartRequest = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'setBreakpoints':
|
||||
await this.setBreakpoints(request, args);
|
||||
break;
|
||||
|
||||
case 'setExceptionBreakpoints': {
|
||||
const filters = args.filters || [];
|
||||
await this.runtime.request('SETEXCEPTIONS', filters);
|
||||
this.sendResponse(request, {
|
||||
breakpoints: filters.map(() => ({ verified: true })),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'threads':
|
||||
this.sendResponse(request, { threads: [{ id: THREAD_ID, name: 'NX Post Tcl' }] });
|
||||
break;
|
||||
|
||||
case 'stackTrace':
|
||||
await this.stackTrace(request);
|
||||
break;
|
||||
|
||||
case 'scopes':
|
||||
await this.scopes(request, args);
|
||||
break;
|
||||
|
||||
case 'variables':
|
||||
await this.variables(request, args);
|
||||
break;
|
||||
|
||||
case 'evaluate':
|
||||
await this.evaluate(request, args);
|
||||
break;
|
||||
|
||||
case 'setVariable':
|
||||
await this.setVariable(request, args);
|
||||
break;
|
||||
|
||||
case 'continue':
|
||||
await this.resume(request, 'CONTINUE', { allThreadsContinued: true });
|
||||
break;
|
||||
|
||||
case 'next':
|
||||
await this.resume(request, 'NEXT');
|
||||
break;
|
||||
|
||||
case 'stepIn':
|
||||
await this.resume(request, 'STEPIN');
|
||||
break;
|
||||
|
||||
case 'stepOut':
|
||||
await this.resume(request, 'STEPOUT');
|
||||
break;
|
||||
|
||||
case 'pause':
|
||||
await this.runtime.request('PAUSE');
|
||||
this.sendResponse(request);
|
||||
break;
|
||||
|
||||
case 'disconnect':
|
||||
case 'terminate':
|
||||
await this.disconnect(request);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.sendResponse(request);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async attach(args) {
|
||||
const host = args.host || '127.0.0.1';
|
||||
const port = Number(args.port || 4711);
|
||||
const timeout = Number(args.connectTimeout || 120000);
|
||||
this.localRoot = args.localRoot ? path.resolve(args.localRoot) : '';
|
||||
this.remoteRoot = args.remoteRoot ? this.normalizePath(args.remoteRoot) : '';
|
||||
this.sourceBreakpointGroups.clear();
|
||||
this.isPaused = false;
|
||||
this.resetStoppedReferences(true);
|
||||
|
||||
this.runtime = new RuntimeConnection();
|
||||
this.runtime.on('event', (name, fields) => this.onRuntimeEvent(name, fields));
|
||||
this.runtime.on('runtimeError', (error) => {
|
||||
this.sendEvent('output', { category: 'stderr', output: `NX: ${error.message}\n` });
|
||||
});
|
||||
this.runtime.on('close', () => {
|
||||
if (!this.explicitDisconnect) this.sendTerminated();
|
||||
});
|
||||
|
||||
const started = Date.now();
|
||||
let lastError;
|
||||
while (Date.now() - started < timeout) {
|
||||
try {
|
||||
await this.runtime.connect(host, port);
|
||||
lastError = null;
|
||||
break;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
await new Promise((resolve) => setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
if (lastError) {
|
||||
throw new Error(`could not connect to NX at ${host}:${port}: ${lastError.message}`);
|
||||
}
|
||||
await this.runtime.request('CONFIG', [args.stopOnEntry ? 1 : 0, args.breakOnError ? 1 : 0]);
|
||||
}
|
||||
|
||||
async setBreakpoints(request, args) {
|
||||
const sourcePath = args.source && args.source.path ? args.source.path : '';
|
||||
const remotePath = this.toRemotePath(sourcePath);
|
||||
const sourceKey = this.breakpointSourceKey(args.source, sourcePath);
|
||||
const remoteKey = this.breakpointRemoteKey(remotePath);
|
||||
const requested = args.breakpoints || (args.lines || []).map((line) => ({ line }));
|
||||
const records = requested.map((breakpoint) => ({
|
||||
id: this.nextBreakpointId++,
|
||||
line: breakpoint.line,
|
||||
condition: breakpoint.condition || '',
|
||||
hitCondition: breakpoint.hitCondition || '',
|
||||
logMessage: breakpoint.logMessage || '',
|
||||
}));
|
||||
if (!this.isTclSource(sourcePath)) {
|
||||
this.sourceBreakpointGroups.delete(sourceKey);
|
||||
// VS Code forwards breakpoints from every language to every active debug
|
||||
// session. Clear any stale runtime entry left by an older adapter, but do
|
||||
// not make the NX Tcl runtime parse or instrument a Python source file.
|
||||
await this.runtime.request('SETBPS', [encodeHex(remotePath), 0]);
|
||||
this.sendResponse(request, {
|
||||
breakpoints: records.map((breakpoint) => ({
|
||||
id: breakpoint.id,
|
||||
verified: false,
|
||||
line: breakpoint.line,
|
||||
source: args.source,
|
||||
message: 'NX Tcl Remote Debugger supports breakpoints only in .tcl and .def files',
|
||||
})),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.sourceBreakpointGroups.set(sourceKey, { remotePath, remoteKey, records });
|
||||
|
||||
// Collapse exact duplicates shared by aliases, while remembering which
|
||||
// runtime breakpoint represents every DAP breakpoint ID. This keeps hit
|
||||
// counts and log points from firing twice for one Tcl command.
|
||||
const mergedBreakpoints = [];
|
||||
const mergedBySignature = new Map();
|
||||
const representativeById = new Map();
|
||||
for (const group of this.sourceBreakpointGroups.values()) {
|
||||
if (group.remoteKey !== remoteKey) continue;
|
||||
for (const breakpoint of group.records) {
|
||||
const signature = this.breakpointSignature(breakpoint);
|
||||
let representative = mergedBySignature.get(signature);
|
||||
if (!representative) {
|
||||
representative = breakpoint;
|
||||
mergedBySignature.set(signature, representative);
|
||||
mergedBreakpoints.push(representative);
|
||||
}
|
||||
representativeById.set(breakpoint.id, representative.id);
|
||||
}
|
||||
}
|
||||
|
||||
const fields = [encodeHex(remotePath), mergedBreakpoints.length];
|
||||
for (const breakpoint of mergedBreakpoints) {
|
||||
fields.push(
|
||||
breakpoint.id,
|
||||
breakpoint.line,
|
||||
encodeHex(breakpoint.condition),
|
||||
encodeHex(breakpoint.hitCondition),
|
||||
encodeHex(breakpoint.logMessage),
|
||||
);
|
||||
}
|
||||
const runtimeResponse = await this.runtime.request('SETBPS', fields);
|
||||
const responseFields = runtimeResponse.fields || [];
|
||||
const responseCount = Number(responseFields[0] || 0);
|
||||
const responseStride = responseFields.length >= 1 + responseCount * 4 ? 4 : 2;
|
||||
const runtimeBreakpoints = new Map();
|
||||
let responseCursor = 1;
|
||||
for (let index = 0; index < responseCount; index++) {
|
||||
const id = Number(responseFields[responseCursor++]);
|
||||
const line = Number(responseFields[responseCursor++]);
|
||||
let verified = true;
|
||||
let message = '';
|
||||
if (responseStride === 4) {
|
||||
verified = responseFields[responseCursor++] === '1';
|
||||
message = decodeHex(responseFields[responseCursor++]);
|
||||
}
|
||||
runtimeBreakpoints.set(id, { line, verified, message });
|
||||
}
|
||||
this.sendResponse(request, {
|
||||
breakpoints: records.map((breakpoint) => {
|
||||
const representativeId = representativeById.get(breakpoint.id);
|
||||
const runtimeBreakpoint = runtimeBreakpoints.get(representativeId);
|
||||
const result = {
|
||||
id: breakpoint.id,
|
||||
verified: runtimeBreakpoint ? runtimeBreakpoint.verified : false,
|
||||
line: runtimeBreakpoint && runtimeBreakpoint.line ? runtimeBreakpoint.line : breakpoint.line,
|
||||
source: args.source,
|
||||
};
|
||||
if (runtimeBreakpoint && runtimeBreakpoint.message) result.message = runtimeBreakpoint.message;
|
||||
return result;
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
async stackTrace(request) {
|
||||
if (!this.isPaused) {
|
||||
this.sendResponse(request, { stackFrames: [], totalFrames: 0 });
|
||||
return;
|
||||
}
|
||||
const { fields } = await this.runtime.request('STACK');
|
||||
const count = Number(fields[0] || 0);
|
||||
const stackFrames = [];
|
||||
let cursor = 1;
|
||||
for (let index = 0; index < count; index++) {
|
||||
const runtimeId = Number(fields[cursor++]);
|
||||
const name = decodeHex(fields[cursor++]);
|
||||
const remoteFile = decodeHex(fields[cursor++]);
|
||||
const line = Number(fields[cursor++]);
|
||||
const column = Number(fields[cursor++]);
|
||||
const localFile = this.toLocalPath(remoteFile);
|
||||
const frame = {
|
||||
id: this.toAdapterFrameId(runtimeId),
|
||||
name: name || '<Tcl>',
|
||||
line: line || 1,
|
||||
column: column || 1,
|
||||
};
|
||||
if (localFile) frame.source = { name: path.basename(localFile), path: localFile };
|
||||
stackFrames.push(frame);
|
||||
}
|
||||
this.sendResponse(request, { stackFrames, totalFrames: stackFrames.length });
|
||||
}
|
||||
|
||||
async scopes(request, args) {
|
||||
const runtimeFrameId = this.frameReferences.get(Number(args.frameId));
|
||||
if (!this.isPaused || runtimeFrameId === undefined) {
|
||||
this.sendResponse(request, { scopes: [] });
|
||||
return;
|
||||
}
|
||||
const { fields } = await this.runtime.request('SCOPES', [runtimeFrameId]);
|
||||
const count = Number(fields[0] || 0);
|
||||
const scopes = [];
|
||||
let cursor = 1;
|
||||
for (let index = 0; index < count; index++) {
|
||||
const variablesReference = this.toAdapterVariablesReference(Number(fields[cursor++]));
|
||||
const name = decodeHex(fields[cursor++]);
|
||||
cursor++; // wire label: expensive
|
||||
const expensive = fields[cursor++] === '1';
|
||||
scopes.push({ name, variablesReference, expensive });
|
||||
}
|
||||
this.sendResponse(request, { scopes });
|
||||
}
|
||||
|
||||
async variables(request, args) {
|
||||
const runtimeReference = this.variableReferences.get(Number(args.variablesReference));
|
||||
if (!this.isPaused || runtimeReference === undefined) {
|
||||
this.sendResponse(request, { variables: [] });
|
||||
return;
|
||||
}
|
||||
const { fields } = await this.runtime.request('VARIABLES', [
|
||||
runtimeReference,
|
||||
args.start || 0,
|
||||
args.count || 0,
|
||||
encodeHex(args.filter || ''),
|
||||
]);
|
||||
const count = Number(fields[0] || 0);
|
||||
const variables = [];
|
||||
let cursor = 1;
|
||||
for (let index = 0; index < count; index++) {
|
||||
variables.push({
|
||||
name: decodeHex(fields[cursor++]),
|
||||
value: decodeHex(fields[cursor++]),
|
||||
type: decodeHex(fields[cursor++]),
|
||||
variablesReference: this.toAdapterVariablesReference(Number(fields[cursor++])),
|
||||
indexedVariables: Number(fields[cursor++]) || undefined,
|
||||
namedVariables: Number(fields[cursor++]) || undefined,
|
||||
});
|
||||
}
|
||||
this.sendResponse(request, { variables });
|
||||
}
|
||||
|
||||
async evaluate(request, args) {
|
||||
// VS Code can refresh Watch/Hover expressions after Continue but before
|
||||
// it has processed the next stopped event. Do not forward that transient
|
||||
// request to the running NX interpreter (which correctly rejects it).
|
||||
if (!this.isPaused) {
|
||||
this.sendResponse(request, {
|
||||
result: '<NX is running; evaluation is available while paused>',
|
||||
type: 'unavailable',
|
||||
variablesReference: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
let runtimeFrameId = 0;
|
||||
if (Number(args.frameId) > 0) {
|
||||
runtimeFrameId = this.frameReferences.get(Number(args.frameId));
|
||||
if (runtimeFrameId === undefined) {
|
||||
this.sendResponse(request, {
|
||||
result: '<stack frame is no longer available>',
|
||||
type: 'unavailable',
|
||||
variablesReference: 0,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const { fields } = await this.runtime.request('EVALUATE', [runtimeFrameId, encodeHex(args.expression || '')]);
|
||||
this.sendResponse(request, {
|
||||
result: decodeHex(fields[0]),
|
||||
type: decodeHex(fields[1]),
|
||||
variablesReference: this.toAdapterVariablesReference(Number(fields[2])),
|
||||
indexedVariables: Number(fields[3]) || undefined,
|
||||
namedVariables: Number(fields[4]) || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async setVariable(request, args) {
|
||||
const runtimeReference = this.variableReferences.get(Number(args.variablesReference));
|
||||
if (!this.isPaused || runtimeReference === undefined) {
|
||||
throw new Error('variable is no longer available');
|
||||
}
|
||||
const { fields } = await this.runtime.request('SETVARIABLE', [
|
||||
runtimeReference,
|
||||
encodeHex(args.name),
|
||||
encodeHex(args.value),
|
||||
]);
|
||||
this.sendResponse(request, {
|
||||
value: decodeHex(fields[0]),
|
||||
type: decodeHex(fields[1]),
|
||||
variablesReference: this.toAdapterVariablesReference(Number(fields[2])),
|
||||
indexedVariables: Number(fields[3]) || undefined,
|
||||
namedVariables: Number(fields[4]) || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
async resume(request, command, responseBody = {}) {
|
||||
this.isPaused = false;
|
||||
this.resetStoppedReferences();
|
||||
this.resumeInProgress++;
|
||||
try {
|
||||
await this.runtime.request(command);
|
||||
// The runtime can hit the next breakpoint immediately and place its
|
||||
// STOPPED event in the same TCP packet as this response. Always publish
|
||||
// the DAP resume transition before releasing such a queued stop.
|
||||
this.sendResponse(request, responseBody);
|
||||
this.sendEvent('continued', { threadId: THREAD_ID, allThreadsContinued: true });
|
||||
} finally {
|
||||
this.resumeInProgress--;
|
||||
if (this.resumeInProgress === 0) this.flushStoppedEvents();
|
||||
}
|
||||
}
|
||||
|
||||
async disconnect(request) {
|
||||
this.explicitDisconnect = true;
|
||||
this.isPaused = false;
|
||||
this.resetStoppedReferences();
|
||||
if (this.runtime && !this.runtime.closed) {
|
||||
try {
|
||||
await this.runtime.request('DISCONNECT', [], 2000);
|
||||
} catch (_) {
|
||||
// The runtime may close immediately after acknowledging detach.
|
||||
}
|
||||
this.runtime.close();
|
||||
}
|
||||
this.sendResponse(request);
|
||||
this.sendTerminated();
|
||||
}
|
||||
|
||||
onRuntimeEvent(name, fields) {
|
||||
switch (name) {
|
||||
case 'HELLO':
|
||||
this.sendEvent('output', {
|
||||
category: 'console',
|
||||
output: `NX runtime ${decodeHex(fields[0])}, Tcl ${decodeHex(fields[1])}, PID ${fields[2]}\n`,
|
||||
});
|
||||
break;
|
||||
case 'STOPPED':
|
||||
this.isPaused = true;
|
||||
this.resetStoppedReferences();
|
||||
this.publishStoppedEvent({
|
||||
reason: decodeHex(fields[0]) || 'pause',
|
||||
threadId: THREAD_ID,
|
||||
allThreadsStopped: true,
|
||||
text: decodeHex(fields[3]) || undefined,
|
||||
});
|
||||
break;
|
||||
case 'OUTPUT':
|
||||
this.sendEvent('output', { category: 'stdout', output: decodeHex(fields[0]) });
|
||||
break;
|
||||
default:
|
||||
this.sendEvent('output', { category: 'console', output: `NX event ${name}\n` });
|
||||
}
|
||||
}
|
||||
|
||||
publishStoppedEvent(body) {
|
||||
if (this.resumeInProgress > 0) {
|
||||
this.pendingStoppedEvents.push(body);
|
||||
return;
|
||||
}
|
||||
this.sendEvent('stopped', body);
|
||||
}
|
||||
|
||||
flushStoppedEvents() {
|
||||
const queued = this.pendingStoppedEvents;
|
||||
this.pendingStoppedEvents = [];
|
||||
for (const body of queued) this.sendEvent('stopped', body);
|
||||
}
|
||||
|
||||
resetStoppedReferences(resetCounters = false) {
|
||||
this.frameReferences.clear();
|
||||
this.runtimeToFrameReference.clear();
|
||||
this.variableReferences.clear();
|
||||
this.runtimeToVariableReference.clear();
|
||||
if (resetCounters) {
|
||||
this.nextFrameId = 1;
|
||||
this.nextVariablesReference = 1;
|
||||
}
|
||||
}
|
||||
|
||||
toAdapterFrameId(runtimeId) {
|
||||
const normalized = Number(runtimeId);
|
||||
if (!Number.isFinite(normalized) || normalized <= 0) return 0;
|
||||
let adapterId = this.runtimeToFrameReference.get(normalized);
|
||||
if (adapterId !== undefined) return adapterId;
|
||||
adapterId = this.nextFrameId++;
|
||||
this.runtimeToFrameReference.set(normalized, adapterId);
|
||||
this.frameReferences.set(adapterId, normalized);
|
||||
return adapterId;
|
||||
}
|
||||
|
||||
toAdapterVariablesReference(runtimeReference) {
|
||||
const normalized = Number(runtimeReference);
|
||||
if (!Number.isFinite(normalized) || normalized <= 0) return 0;
|
||||
let adapterReference = this.runtimeToVariableReference.get(normalized);
|
||||
if (adapterReference !== undefined) return adapterReference;
|
||||
adapterReference = this.nextVariablesReference++;
|
||||
this.runtimeToVariableReference.set(normalized, adapterReference);
|
||||
this.variableReferences.set(adapterReference, normalized);
|
||||
return adapterReference;
|
||||
}
|
||||
|
||||
isTclSource(sourcePath) {
|
||||
const extension = path.extname(String(sourcePath || '')).toLowerCase();
|
||||
return extension === '.tcl' || extension === '.def';
|
||||
}
|
||||
|
||||
breakpointSourceKey(source = {}, sourcePath = '') {
|
||||
if (source && Number(source.sourceReference) > 0) {
|
||||
return `reference:${source.sourceReference}`;
|
||||
}
|
||||
// Deliberately preserve spelling and case here: they are precisely what
|
||||
// distinguish two VS Code source identities that map to the same runtime
|
||||
// path. Runtime comparison is handled separately below.
|
||||
return `path:${String(sourcePath || '')}`;
|
||||
}
|
||||
|
||||
breakpointRemoteKey(remotePath) {
|
||||
let normalized = this.normalizePath(remotePath);
|
||||
if (process.platform === 'win32' || /^[A-Za-z]:\//.test(normalized) || normalized.startsWith('//')) {
|
||||
normalized = normalized.toLowerCase();
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
breakpointSignature(breakpoint) {
|
||||
return JSON.stringify([
|
||||
Number(breakpoint.line),
|
||||
breakpoint.condition || '',
|
||||
breakpoint.hitCondition || '',
|
||||
breakpoint.logMessage || '',
|
||||
]);
|
||||
}
|
||||
|
||||
normalizePath(value) {
|
||||
const normalized = String(value || '').replace(/\\/g, '/');
|
||||
if (normalized === '/' || /^[A-Za-z]:\/$/.test(normalized)) return normalized;
|
||||
return normalized.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
samePathPrefix(candidate, prefix) {
|
||||
let normalizedCandidate = this.normalizePath(candidate);
|
||||
let normalizedPrefix = this.normalizePath(prefix);
|
||||
if (process.platform === 'win32') {
|
||||
normalizedCandidate = normalizedCandidate.toLowerCase();
|
||||
normalizedPrefix = normalizedPrefix.toLowerCase();
|
||||
}
|
||||
if (normalizedCandidate === normalizedPrefix) return true;
|
||||
const boundaryPrefix = normalizedPrefix.endsWith('/') ? normalizedPrefix : `${normalizedPrefix}/`;
|
||||
return normalizedCandidate.startsWith(boundaryPrefix);
|
||||
}
|
||||
|
||||
toRemotePath(localPath) {
|
||||
const normalized = this.normalizePath(path.resolve(localPath || '.'));
|
||||
const localRoot = this.normalizePath(this.localRoot);
|
||||
if (localRoot && this.remoteRoot && this.samePathPrefix(normalized, localRoot)) {
|
||||
return this.remoteRoot + normalized.slice(localRoot.length);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
toLocalPath(remotePath) {
|
||||
const normalized = this.normalizePath(remotePath);
|
||||
if (this.remoteRoot && this.localRoot && this.samePathPrefix(normalized, this.remoteRoot)) {
|
||||
const suffix = normalized.slice(this.remoteRoot.length).replace(/^\//, '');
|
||||
return path.join(this.localRoot, ...suffix.split('/'));
|
||||
}
|
||||
return process.platform === 'win32' ? normalized.replace(/\//g, '\\') : normalized;
|
||||
}
|
||||
|
||||
sendTerminated() {
|
||||
if (this.terminated) return;
|
||||
this.terminated = true;
|
||||
this.sendEvent('terminated');
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.runtime) this.runtime.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
new NxDebugAdapter();
|
||||
}
|
||||
|
||||
module.exports = { NxDebugAdapter, THREAD_ID };
|
||||
@@ -0,0 +1,68 @@
|
||||
'use strict';
|
||||
|
||||
const { PassThrough } = require('node:stream');
|
||||
const { NxDebugAdapter } = require('./debugAdapter');
|
||||
|
||||
function frameMessage(message) {
|
||||
const json = JSON.stringify(message);
|
||||
return `Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`;
|
||||
}
|
||||
|
||||
class NxInlineDebugAdapter {
|
||||
constructor() {
|
||||
this.input = new PassThrough();
|
||||
this.output = new PassThrough();
|
||||
this.outputBuffer = Buffer.alloc(0);
|
||||
this.listeners = new Set();
|
||||
this.disposed = false;
|
||||
|
||||
this.onDidSendMessage = (listener, thisArgs, disposables) => {
|
||||
const registration = { listener, thisArgs };
|
||||
this.listeners.add(registration);
|
||||
const disposable = {
|
||||
dispose: () => this.listeners.delete(registration),
|
||||
};
|
||||
if (Array.isArray(disposables)) disposables.push(disposable);
|
||||
return disposable;
|
||||
};
|
||||
|
||||
this.output.on('data', (chunk) => this.onOutput(chunk));
|
||||
this.adapter = new NxDebugAdapter(this.input, this.output);
|
||||
}
|
||||
|
||||
handleMessage(message) {
|
||||
if (!this.disposed) this.input.write(frameMessage(message));
|
||||
}
|
||||
|
||||
onOutput(chunk) {
|
||||
this.outputBuffer = Buffer.concat([this.outputBuffer, chunk]);
|
||||
while (true) {
|
||||
const headerEnd = this.outputBuffer.indexOf('\r\n\r\n');
|
||||
if (headerEnd < 0) return;
|
||||
const header = this.outputBuffer.subarray(0, headerEnd).toString('ascii');
|
||||
const match = /Content-Length:\s*(\d+)/i.exec(header);
|
||||
if (!match) throw new Error('missing DAP Content-Length header from NX adapter');
|
||||
const contentLength = Number(match[1]);
|
||||
const messageEnd = headerEnd + 4 + contentLength;
|
||||
if (this.outputBuffer.length < messageEnd) return;
|
||||
|
||||
const payload = this.outputBuffer.subarray(headerEnd + 4, messageEnd).toString('utf8');
|
||||
this.outputBuffer = this.outputBuffer.subarray(messageEnd);
|
||||
const message = JSON.parse(payload);
|
||||
for (const { listener, thisArgs } of [...this.listeners]) {
|
||||
listener.call(thisArgs, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.disposed) return;
|
||||
this.disposed = true;
|
||||
this.adapter.dispose();
|
||||
this.input.end();
|
||||
this.output.destroy();
|
||||
this.listeners.clear();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { NxInlineDebugAdapter, frameMessage };
|
||||
@@ -0,0 +1,116 @@
|
||||
'use strict';
|
||||
|
||||
const net = require('net');
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
function encodeHex(value) {
|
||||
return Buffer.from(String(value ?? ''), 'utf8').toString('hex') || '-';
|
||||
}
|
||||
|
||||
function decodeHex(value) {
|
||||
if (!value || value === '-') return '';
|
||||
return Buffer.from(value, 'hex').toString('utf8');
|
||||
}
|
||||
|
||||
class RuntimeConnection extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.socket = null;
|
||||
this.buffer = '';
|
||||
this.nextRequestId = 1;
|
||||
this.pending = new Map();
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
connect(host, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.createConnection({ host, port });
|
||||
const onError = (error) => {
|
||||
socket.destroy();
|
||||
reject(error);
|
||||
};
|
||||
socket.once('error', onError);
|
||||
socket.once('connect', () => {
|
||||
socket.removeListener('error', onError);
|
||||
this.socket = socket;
|
||||
this.closed = false;
|
||||
socket.setEncoding('utf8');
|
||||
socket.on('data', (chunk) => this.onData(chunk));
|
||||
socket.on('error', (error) => this.emit('runtimeError', error));
|
||||
socket.on('close', () => this.onClose());
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
request(command, fields = [], timeoutMs = 30000) {
|
||||
if (!this.socket || this.closed) {
|
||||
return Promise.reject(new Error('NX runtime is not connected'));
|
||||
}
|
||||
const id = this.nextRequestId++;
|
||||
const normalized = fields.map((field) => String(field ?? ''));
|
||||
if (normalized.some((field) => field.includes('\t') || field.includes('\n'))) {
|
||||
return Promise.reject(new Error('protocol fields must be hex-encoded before transmission'));
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.pending.delete(id);
|
||||
reject(new Error(`NX ${command} request timed out`));
|
||||
}, timeoutMs);
|
||||
this.pending.set(id, { resolve, reject, timer, command });
|
||||
this.socket.write(['REQ', id, command, ...normalized].join('\t') + '\n');
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.closed = true;
|
||||
if (this.socket) this.socket.destroy();
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
onData(chunk) {
|
||||
this.buffer += chunk;
|
||||
let newline;
|
||||
while ((newline = this.buffer.indexOf('\n')) >= 0) {
|
||||
const line = this.buffer.slice(0, newline).replace(/\r$/, '');
|
||||
this.buffer = this.buffer.slice(newline + 1);
|
||||
if (line) this.onLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
onLine(line) {
|
||||
const fields = line.split('\t');
|
||||
if (fields[0] === 'RES') {
|
||||
const id = Number(fields[1]);
|
||||
const pending = this.pending.get(id);
|
||||
if (!pending) return;
|
||||
this.pending.delete(id);
|
||||
clearTimeout(pending.timer);
|
||||
if (fields[2] === 'OK') {
|
||||
pending.resolve({ command: fields[3], fields: fields.slice(4) });
|
||||
} else {
|
||||
pending.reject(new Error(decodeHex(fields[4]) || `NX ${pending.command} failed`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (fields[0] === 'EVENT') {
|
||||
this.emit('event', fields[1], fields.slice(2));
|
||||
return;
|
||||
}
|
||||
this.emit('runtimeError', new Error(`unrecognized NX message: ${line}`));
|
||||
}
|
||||
|
||||
onClose() {
|
||||
if (this.closed) return;
|
||||
this.closed = true;
|
||||
for (const pending of this.pending.values()) {
|
||||
clearTimeout(pending.timer);
|
||||
pending.reject(new Error('NX runtime disconnected'));
|
||||
}
|
||||
this.pending.clear();
|
||||
this.emit('close');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { RuntimeConnection, encodeHex, decodeHex };
|
||||
@@ -0,0 +1,42 @@
|
||||
import * as vscode from "vscode"
|
||||
|
||||
// The adapter is shared with the standalone NX Tcl Remote Debugger. It is
|
||||
// bundled by esbuild into this extension, so no child process or additional
|
||||
// VS Code extension is required.
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { NxInlineDebugAdapter } = require("./inlineAdapter")
|
||||
|
||||
class NxDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
|
||||
createDebugAdapterDescriptor(): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
|
||||
return new vscode.DebugAdapterInlineImplementation(new NxInlineDebugAdapter())
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDebugConfiguration(
|
||||
folder: vscode.WorkspaceFolder | undefined,
|
||||
config: vscode.DebugConfiguration
|
||||
): vscode.DebugConfiguration {
|
||||
const resolved = { ...config }
|
||||
if (!resolved.type) resolved.type = "nx-tcl"
|
||||
if (!resolved.request) resolved.request = "attach"
|
||||
if (!resolved.name) resolved.name = "Attach to NX Post Tcl"
|
||||
if (!resolved.host) resolved.host = "127.0.0.1"
|
||||
if (!resolved.port) resolved.port = 4711
|
||||
if (!resolved.connectTimeout) resolved.connectTimeout = 120000
|
||||
if (resolved.stopOnEntry === undefined) resolved.stopOnEntry = false
|
||||
if (resolved.breakOnError === undefined) resolved.breakOnError = true
|
||||
if (!resolved.localRoot && folder) resolved.localRoot = folder.uri.fsPath
|
||||
return resolved
|
||||
}
|
||||
|
||||
export function registerNxTclDebugger(context: vscode.ExtensionContext): void {
|
||||
const factory = new NxDebugAdapterFactory()
|
||||
context.subscriptions.push(
|
||||
vscode.debug.registerDebugAdapterDescriptorFactory("nx-tcl", factory),
|
||||
vscode.debug.registerDebugConfigurationProvider("nx-tcl", {
|
||||
resolveDebugConfiguration
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export { resolveDebugConfiguration }
|
||||
+143
-31
@@ -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,20 +24,24 @@ 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"
|
||||
import { registerNxTclDebugger } from "./debugger/register"
|
||||
|
||||
let client: LanguageClient
|
||||
let client: LanguageClient | undefined
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
registerNxTclDebugger(context)
|
||||
|
||||
// This is required to get server name and module. This should be
|
||||
// the first thing that we do in this extension.
|
||||
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,14 +66,20 @@ 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))) {
|
||||
traceVerbose(
|
||||
`Using interpreter from ${serverInfo.module}.interpreter: ${interpreter.join(" ")}`
|
||||
)
|
||||
client = await restartServer(serverId, serverName, outputChannel, client)
|
||||
client = await restartServer(
|
||||
serverId,
|
||||
serverName,
|
||||
outputChannel,
|
||||
client,
|
||||
context.storageUri?.fsPath
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -77,7 +89,13 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
traceVerbose(
|
||||
`Using interpreter from Python extension: ${interpreterDetails.path.join(" ")}`
|
||||
)
|
||||
client = await restartServer(serverId, serverName, outputChannel, client)
|
||||
client = await restartServer(
|
||||
serverId,
|
||||
serverName,
|
||||
outputChannel,
|
||||
client,
|
||||
context.storageUri?.fsPath
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -89,10 +107,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(
|
||||
onDidChangePythonInterpreter(async () => {
|
||||
await runServer()
|
||||
}),
|
||||
onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
|
||||
if (checkIfConfigurationChanged(e, serverId)) {
|
||||
await runServer()
|
||||
@@ -104,6 +137,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 +156,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 +208,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,43 +235,99 @@ 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")
|
||||
const diagnosticCollectionDef = vscode.languages.createDiagnosticCollection("def")
|
||||
context.subscriptions.push(diagnosticCollectionCdl, diagnosticCollectionDef)
|
||||
|
||||
// Check if the first line of the CDL file contains "MACHINE"
|
||||
vscode.workspace.onDidOpenTextDocument((document) => {
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
if (document.languageId === "cdl") {
|
||||
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
||||
} else if (document.languageId === "def") {
|
||||
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
||||
}
|
||||
const diagnosticTimers = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
const updateDiagnostics = (document: vscode.TextDocument) => {
|
||||
if (document.languageId === "cdl") {
|
||||
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
||||
} else if (document.languageId === "def") {
|
||||
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
||||
}
|
||||
})
|
||||
}
|
||||
const scheduleDiagnostics = (document: vscode.TextDocument) => {
|
||||
const key = document.uri.toString()
|
||||
const previous = diagnosticTimers.get(key)
|
||||
if (previous !== undefined) {
|
||||
clearTimeout(previous)
|
||||
}
|
||||
diagnosticTimers.set(
|
||||
key,
|
||||
setTimeout(() => {
|
||||
diagnosticTimers.delete(key)
|
||||
updateDiagnostics(document)
|
||||
}, 120)
|
||||
)
|
||||
}
|
||||
|
||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||
const document = event.document
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
if (document.languageId === "cdl") {
|
||||
diagnosticCollectionCdl.set(document.uri, diagnosticHandler(document))
|
||||
} else if (document.languageId === "def") {
|
||||
diagnosticCollectionDef.set(document.uri, diagnosticHandler(document))
|
||||
// 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") {
|
||||
updateDiagnostics(document)
|
||||
}
|
||||
}),
|
||||
vscode.workspace.onDidChangeTextDocument((event) => {
|
||||
const document = event.document
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
scheduleDiagnostics(document)
|
||||
}
|
||||
}),
|
||||
vscode.workspace.onDidCloseTextDocument((document) => {
|
||||
const key = document.uri.toString()
|
||||
const timer = diagnosticTimers.get(key)
|
||||
if (timer !== undefined) {
|
||||
clearTimeout(timer)
|
||||
diagnosticTimers.delete(key)
|
||||
}
|
||||
diagnosticCollectionCdl.delete(document.uri)
|
||||
diagnosticCollectionDef.delete(document.uri)
|
||||
}),
|
||||
{
|
||||
dispose() {
|
||||
for (const timer of diagnosticTimers.values()) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
diagnosticTimers.clear()
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
for (const document of vscode.workspace.textDocuments) {
|
||||
if (document.languageId === "cdl" || document.languageId === "def") {
|
||||
updateDiagnostics(document)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 |
+33
-26
@@ -1,28 +1,35 @@
|
||||
{
|
||||
"comments": {
|
||||
// symbol used for single line comment. Remove this entry if your language does not support line comments
|
||||
"lineComment": "#"
|
||||
},
|
||||
// symbols used as brackets
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
// symbols that are auto closed when typing
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
],
|
||||
// symbols that can be used to surround a selection
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
]
|
||||
"comments": {
|
||||
// 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": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
],
|
||||
// symbols that are auto closed when typing
|
||||
"autoClosingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
],
|
||||
// symbols that can be used to surround a selection
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "nx-post-support",
|
||||
"version": "0.3.0",
|
||||
"version": "2026.9.501",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nx-post-support",
|
||||
"version": "0.3.0",
|
||||
"version": "2026.9.501",
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.96.0",
|
||||
"@vscode/vsce": "^3.2.1",
|
||||
|
||||
+144
-5
@@ -1,9 +1,19 @@
|
||||
{
|
||||
"name": "nx-post-support",
|
||||
"displayName": "NX Postprocessor Support",
|
||||
"description": "",
|
||||
"version": "0.4.9",
|
||||
"description": "VS Code extension for NX CAM postprocessor development with language support and remote Tcl debugging for CDL, TCL, and DEF files",
|
||||
"version": "2026.9.600",
|
||||
"publisher": "Christoph",
|
||||
"icon": "images/nx-1.png",
|
||||
"activationEvents": [
|
||||
"onLanguage:tcl",
|
||||
"onLanguage:cdl",
|
||||
"onLanguage:def",
|
||||
"onDebug"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"ms-python.python"
|
||||
],
|
||||
"serverInfo": {
|
||||
"name": "NX Postprocessor Support",
|
||||
"module": "nx-post-support"
|
||||
@@ -16,15 +26,112 @@
|
||||
"keywords": [
|
||||
"cdl",
|
||||
"NX CDL",
|
||||
"UDE"
|
||||
"UDE",
|
||||
"Postprocessor",
|
||||
"NX CAM",
|
||||
"Siemens NX",
|
||||
"debugger",
|
||||
"remote debugging"
|
||||
],
|
||||
"engines": {
|
||||
"vscode": "^1.96.0"
|
||||
},
|
||||
"categories": [
|
||||
"Programming Languages"
|
||||
"Programming Languages",
|
||||
"Debuggers"
|
||||
],
|
||||
"contributes": {
|
||||
"breakpoints": [
|
||||
{
|
||||
"language": "tcl"
|
||||
},
|
||||
{
|
||||
"language": "def"
|
||||
}
|
||||
],
|
||||
"debuggers": [
|
||||
{
|
||||
"type": "nx-tcl",
|
||||
"label": "NX Tcl Remote Debugger",
|
||||
"languages": [
|
||||
"tcl",
|
||||
"def"
|
||||
],
|
||||
"configurationAttributes": {
|
||||
"attach": {
|
||||
"required": [
|
||||
"host",
|
||||
"port"
|
||||
],
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string",
|
||||
"default": "127.0.0.1",
|
||||
"description": "Host on which the NX Tcl runtime is listening."
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"default": 4711,
|
||||
"description": "TCP port of the NX Tcl runtime."
|
||||
},
|
||||
"connectTimeout": {
|
||||
"type": "number",
|
||||
"default": 120000,
|
||||
"description": "How long VS Code retries until NX starts the runtime, in milliseconds."
|
||||
},
|
||||
"stopOnEntry": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Stop on the first traced Tcl command."
|
||||
},
|
||||
"breakOnError": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Stop when a traced Tcl command returns TCL_ERROR."
|
||||
},
|
||||
"localRoot": {
|
||||
"type": "string",
|
||||
"description": "Local source root opened in VS Code."
|
||||
},
|
||||
"remoteRoot": {
|
||||
"type": "string",
|
||||
"description": "Corresponding source root on the NX host when it differs from localRoot."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"initialConfigurations": [
|
||||
{
|
||||
"type": "nx-tcl",
|
||||
"request": "attach",
|
||||
"name": "Attach to NX Post Tcl",
|
||||
"host": "127.0.0.1",
|
||||
"port": 4711,
|
||||
"connectTimeout": 120000,
|
||||
"stopOnEntry": false,
|
||||
"breakOnError": true,
|
||||
"localRoot": "${workspaceFolder}"
|
||||
}
|
||||
],
|
||||
"configurationSnippets": [
|
||||
{
|
||||
"label": "NX Tcl: Attach to NX Post",
|
||||
"description": "Attach VS Code to an NX runtime embedded in NX Post.",
|
||||
"body": {
|
||||
"type": "nx-tcl",
|
||||
"request": "attach",
|
||||
"name": "Attach to NX Post Tcl",
|
||||
"host": "127.0.0.1",
|
||||
"port": 4711,
|
||||
"connectTimeout": 120000,
|
||||
"stopOnEntry": false,
|
||||
"breakOnError": true,
|
||||
"localRoot": "${workspaceFolder}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"id": "tcl",
|
||||
@@ -84,6 +191,31 @@
|
||||
"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.inlayHints.parameterNames": {
|
||||
"type": "string",
|
||||
"default": "all",
|
||||
"enum": [
|
||||
"all",
|
||||
"literals",
|
||||
"none"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Show parameter name hints for all arguments.",
|
||||
"Show parameter name hints only for literal arguments.",
|
||||
"Do not show parameter name hints."
|
||||
],
|
||||
"description": "Controls which TCL procedure arguments receive parameter name hints."
|
||||
},
|
||||
"nx-post-support.inlayHints.suppressWhenArgumentMatchesName": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Hide a parameter hint when a variable argument already has the same name, for example `output` in `my_proc $output`."
|
||||
},
|
||||
"nx-post-support.importStrategy": {
|
||||
"default": "useBundled",
|
||||
"description": "Defines where `NX Postprocessor Support` is imported from.",
|
||||
@@ -108,12 +240,19 @@
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"configurationDefaults": {
|
||||
"[tcl]": {
|
||||
"editor.inlayHints.maximumLength": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "node esbuild.js --production",
|
||||
"compile:debug": "node esbuild.js",
|
||||
"watch": "node esbuild.js --watch",
|
||||
"package": "node esbuild.js --production"
|
||||
"package": "node esbuild.js --production",
|
||||
"test:debugger": "node --test test/debugger.test.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/vscode": "^1.96.0",
|
||||
|
||||
@@ -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:
|
||||
|
||||
+396
-113
@@ -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(
|
||||
cls,
|
||||
these,
|
||||
slots,
|
||||
is_frozen,
|
||||
weakref_slot,
|
||||
_determine_whether_to_implement(
|
||||
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,
|
||||
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,12 +1910,17 @@ 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."
|
||||
raise TypeError(msg)
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
msg = "can't set attribute"
|
||||
args: ClassVar[tuple[str]] = [msg]
|
||||
def __init__(self):
|
||||
msg = "can't set attribute"
|
||||
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:
|
||||
self.key_validator(inst, attr, key)
|
||||
self.value_validator(inst, attr, value[key])
|
||||
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]]:
|
||||
@@ -95,7 +95,17 @@ def make_dict_unstructure_fn_from_attrs(
|
||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||
will be included.
|
||||
|
||||
.. versionadded:: 24.1.0
|
||||
.. versionadded:: 24.1.0
|
||||
.. versionchanged:: 25.2.0
|
||||
The `_cattrs_use_alias` parameter takes its value from the given converter
|
||||
by default.
|
||||
.. versionchanged:: 26.1.0
|
||||
`typing.Annotated[T, override()]` is now recognized and can be used to customize
|
||||
unstructuring.
|
||||
.. versionchanged:: 26.1.0
|
||||
When `_cattrs_omit_if_default` is true and the attribute has an attrs converter
|
||||
specified, the converter is applied to the default value before checking if it
|
||||
is equal to the attribute's value.
|
||||
"""
|
||||
|
||||
fn_name = "unstructure_" + cl.__name__
|
||||
@@ -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,16 +192,32 @@ 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}:")
|
||||
lines.append(f" res['{kn}'] = {invoke}")
|
||||
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:
|
||||
# No default or no override.
|
||||
@@ -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]]:
|
||||
@@ -235,13 +273,22 @@ def make_dict_unstructure_fn(
|
||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||
will be included.
|
||||
|
||||
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
||||
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
||||
.. 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]:
|
||||
@@ -314,7 +361,13 @@ def make_dict_structure_fn_from_attrs(
|
||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||
will be included.
|
||||
|
||||
.. versionadded:: 24.1.0
|
||||
.. versionadded:: 24.1.0
|
||||
.. versionchanged:: 25.2.0
|
||||
The `_cattrs_use_alias` parameter takes its value from the given converter
|
||||
by default.
|
||||
.. versionchanged:: 26.1.0
|
||||
`typing.Annotated[T, override()]` is now recognized and can be used to customize
|
||||
unstructuring.
|
||||
"""
|
||||
|
||||
cl_name = cl.__name__
|
||||
@@ -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]:
|
||||
@@ -706,14 +784,20 @@ def make_dict_structure_fn(
|
||||
:param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False`
|
||||
will be included.
|
||||
|
||||
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
||||
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
||||
.. versionchanged:: 23.2.0
|
||||
.. versionadded:: 23.2.0 *_cattrs_use_alias*
|
||||
.. versionadded:: 23.2.0 *_cattrs_include_init_false*
|
||||
.. versionchanged:: 23.2.0
|
||||
The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters
|
||||
take their values from the given converter by default.
|
||||
.. versionchanged:: 24.1.0
|
||||
.. versionchanged:: 24.1.0
|
||||
The `_cattrs_prefer_attrib_converters` parameter takes its value from the given
|
||||
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__")
|
||||
)
|
||||
]
|
||||
@@ -1 +1 @@
|
||||
pip
|
||||
uv
|
||||
@@ -1,4 +1,4 @@
|
||||
importlib_metadata-6.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
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,,
|
||||
@@ -6,15 +6,6 @@ importlib_metadata-6.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
|
||||
importlib_metadata-6.8.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
||||
importlib_metadata-6.8.0.dist-info/top_level.txt,sha256=CO3fD9yylANiXkrMo4qHLV_mqXL2sC5JFKgt1yWAT-A,19
|
||||
importlib_metadata/__init__.py,sha256=EiH0qTKP_6oa6pRGJgPrq0kvjnL3hJ18BJH8VaAYSBA,30749
|
||||
importlib_metadata/__pycache__/__init__.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_adapters.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_collections.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_compat.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_functools.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_itertools.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_meta.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_py39compat.cpython-311.pyc,,
|
||||
importlib_metadata/__pycache__/_text.cpython-311.pyc,,
|
||||
importlib_metadata/_adapters.py,sha256=i8S6Ib1OQjcILA-l4gkzktMZe18TaeUNI49PLRp6OBU,2454
|
||||
importlib_metadata/_collections.py,sha256=CJ0OTCHIjWA0ZIVS4voORAsn2R4R2cQBEtPsZEJpASY,743
|
||||
importlib_metadata/_compat.py,sha256=zhjcWMfA9SNExFVVVBozOYbuiok0A4tdMsNk9ZDZi-A,1554
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,17 +0,0 @@
|
||||
lsprotocol-2023.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
lsprotocol-2023.0.1.dist-info/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
|
||||
lsprotocol-2023.0.1.dist-info/METADATA,sha256=oh7M_V0nCX-lx8MCik5z0_J8Wyd7ApJtdl30wWs4Tb8,2237
|
||||
lsprotocol-2023.0.1.dist-info/RECORD,,
|
||||
lsprotocol-2023.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lsprotocol-2023.0.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
||||
lsprotocol/__init__.py,sha256=zoT6Do2JtGHGb7pOeKpahg4ocXIsSpyowjhOrhUhx8g,94
|
||||
lsprotocol/__pycache__/__init__.cpython-311.pyc,,
|
||||
lsprotocol/__pycache__/_hooks.cpython-311.pyc,,
|
||||
lsprotocol/__pycache__/converters.cpython-311.pyc,,
|
||||
lsprotocol/__pycache__/types.cpython-311.pyc,,
|
||||
lsprotocol/__pycache__/validators.cpython-311.pyc,,
|
||||
lsprotocol/_hooks.py,sha256=PCTq4Ve_dDd02DMcWQ8afu9gj_oyX0B4nDOomPghqYs,41570
|
||||
lsprotocol/converters.py,sha256=404tQOVoZL31R9CBrDe6Gx9Nok5cRph3glKlnSx00fo,433
|
||||
lsprotocol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lsprotocol/types.py,sha256=nZYiI5ZvHEBkRYjtPd8tpqe0n2NpmdaCZ2RLwHxoMLs,454735
|
||||
lsprotocol/validators.py,sha256=5UMUmWhk52_Ps66_KFydjNkMfLNUsPH3wmV0Buv645s,1420
|
||||
@@ -0,0 +1 @@
|
||||
uv
|
||||
+7
-7
@@ -1,15 +1,14 @@
|
||||
Metadata-Version: 2.1
|
||||
Metadata-Version: 2.4
|
||||
Name: lsprotocol
|
||||
Version: 2023.0.1
|
||||
Summary: Python implementation of the Language Server Protocol.
|
||||
Version: 2025.0.0
|
||||
Summary: Python types for Language Server Protocol.
|
||||
Author-email: Microsoft Corporation <lsprotocol-help@microsoft.com>
|
||||
Maintainer-email: Brett Cannon <brett@python.org>, Karthik Nadig <kanadig@microsoft.com>
|
||||
Requires-Python: >=3.7
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/markdown
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
@@ -17,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
License-File: LICENSE
|
||||
Requires-Dist: attrs>=21.3.0
|
||||
Requires-Dist: cattrs!=23.2.1
|
||||
Project-URL: Issues, https://github.com/microsoft/lsprotocol/issues
|
||||
@@ -24,7 +24,7 @@ Project-URL: Source, https://github.com/microsoft/lsprotocol
|
||||
|
||||
# Language Server Protocol Types implementation for Python
|
||||
|
||||
`lsprotocol` is a python implementation of object types used in the Language Server Protocol (LSP). This repository contains the code generator and the generated types for LSP.
|
||||
`lsprotocol` is a Python implementation of object types used in the Language Server Protocol (LSP). This repository contains the code generator and the generated types for LSP.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
lsprotocol-2025.0.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
||||
lsprotocol-2025.0.0.dist-info/METADATA,sha256=u3Lb5ZzZi4gH18WQWVPurP9kBvqHDY5U-Utafh8O7Bc,2184
|
||||
lsprotocol-2025.0.0.dist-info/RECORD,,
|
||||
lsprotocol-2025.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lsprotocol-2025.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||
lsprotocol-2025.0.0.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
|
||||
lsprotocol/__init__.py,sha256=zoT6Do2JtGHGb7pOeKpahg4ocXIsSpyowjhOrhUhx8g,94
|
||||
lsprotocol/_hooks.py,sha256=dp-HEi_z7CqTz0cgzCYfT_s32Sr1hcrLlu3ff42lBXI,44624
|
||||
lsprotocol/converters.py,sha256=404tQOVoZL31R9CBrDe6Gx9Nok5cRph3glKlnSx00fo,433
|
||||
lsprotocol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
lsprotocol/types.py,sha256=LJbn0uKWsPpveLZG2Wswr-1Gn2Tfimue3zV71xVA5SE,476733
|
||||
lsprotocol/validators.py,sha256=5UMUmWhk52_Ps66_KFydjNkMfLNUsPH3wmV0Buv645s,1420
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
import sys
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
from typing import Any, Optional, Sequence, Tuple, Union
|
||||
|
||||
import attrs
|
||||
import cattrs
|
||||
@@ -27,7 +27,7 @@ def _resolve_forward_references() -> None:
|
||||
items = list(filter(_filter, lsp_types.ALL_TYPES_MAP.items()))
|
||||
for _, value in items:
|
||||
if isinstance(value, type):
|
||||
attrs.resolve_types(value, lsp_types.ALL_TYPES_MAP, {}) # type: ignore
|
||||
attrs.resolve_types(value, lsp_types.ALL_TYPES_MAP, {})
|
||||
_resolved_forward_references = True
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
|
||||
def _inlay_hint_label_part_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[str, List[lsp_types.InlayHintLabelPart]]:
|
||||
) -> Union[str, Sequence[lsp_types.InlayHintLabelPart]]:
|
||||
if isinstance(object_, str):
|
||||
return object_
|
||||
|
||||
@@ -431,7 +431,7 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
|
||||
def _completion_list_hook(
|
||||
object_: Any, _: type
|
||||
) -> Optional[Union[lsp_types.CompletionList, List[lsp_types.CompletionItem]]]:
|
||||
) -> Optional[Union[lsp_types.CompletionList, Sequence[lsp_types.CompletionItem]]]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, list):
|
||||
@@ -446,8 +446,8 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
) -> Optional[
|
||||
Union[
|
||||
lsp_types.Location,
|
||||
List[lsp_types.Location],
|
||||
List[lsp_types.LocationLink],
|
||||
Sequence[lsp_types.Location],
|
||||
Sequence[lsp_types.LocationLink],
|
||||
]
|
||||
]:
|
||||
if object_ is None:
|
||||
@@ -470,7 +470,7 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
def _symbol_hook(
|
||||
object_: Any, _: type
|
||||
) -> Optional[
|
||||
Union[List[lsp_types.DocumentSymbol], List[lsp_types.SymbolInformation]]
|
||||
Union[Sequence[lsp_types.DocumentSymbol], Sequence[lsp_types.SymbolInformation]]
|
||||
]:
|
||||
if object_ is None:
|
||||
return None
|
||||
@@ -496,8 +496,8 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
Union[
|
||||
OptionalPrimitive,
|
||||
lsp_types.MarkupContent,
|
||||
lsp_types.MarkedString_Type1,
|
||||
List[Union[OptionalPrimitive, lsp_types.MarkedString_Type1]],
|
||||
lsp_types.MarkedStringWithLanguage,
|
||||
Sequence[Union[OptionalPrimitive, lsp_types.MarkedStringWithLanguage]],
|
||||
]
|
||||
]:
|
||||
if object_ is None:
|
||||
@@ -509,14 +509,14 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
(
|
||||
item
|
||||
if isinstance(item, (bool, int, str, float))
|
||||
else converter.structure(item, lsp_types.MarkedString_Type1)
|
||||
else converter.structure(item, lsp_types.MarkedStringWithLanguage)
|
||||
)
|
||||
for item in object_
|
||||
]
|
||||
if "kind" in object_:
|
||||
return converter.structure(object_, lsp_types.MarkupContent)
|
||||
else:
|
||||
return converter.structure(object_, lsp_types.MarkedString_Type1)
|
||||
return converter.structure(object_, lsp_types.MarkedStringWithLanguage)
|
||||
|
||||
def _document_edit_hook(
|
||||
object_: Any, _: type
|
||||
@@ -544,25 +544,25 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
|
||||
def _semantic_tokens_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[OptionalPrimitive, lsp_types.SemanticTokensOptionsFullType1]:
|
||||
) -> Union[OptionalPrimitive, lsp_types.SemanticTokensFullDelta]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(object_, lsp_types.SemanticTokensOptionsFullType1)
|
||||
return converter.structure(object_, lsp_types.SemanticTokensFullDelta)
|
||||
|
||||
def _semantic_tokens_capabilities_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
OptionalPrimitive,
|
||||
lsp_types.SemanticTokensClientCapabilitiesRequestsTypeFullType1,
|
||||
lsp_types.ClientSemanticTokensRequestFullDelta,
|
||||
]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(
|
||||
object_, lsp_types.SemanticTokensClientCapabilitiesRequestsTypeFullType1
|
||||
object_, lsp_types.ClientSemanticTokensRequestFullDelta
|
||||
)
|
||||
|
||||
def _code_action_kind_hook(
|
||||
@@ -622,29 +622,29 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
def _notebook_sync_option_selector_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType1,
|
||||
lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2,
|
||||
lsp_types.NotebookDocumentFilterWithNotebook,
|
||||
lsp_types.NotebookDocumentFilterWithCells,
|
||||
]:
|
||||
if "notebook" in object_:
|
||||
return converter.structure(
|
||||
object_, lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType1
|
||||
object_, lsp_types.NotebookDocumentFilterWithNotebook
|
||||
)
|
||||
else:
|
||||
return converter.structure(
|
||||
object_, lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2
|
||||
object_, lsp_types.NotebookDocumentFilterWithCells
|
||||
)
|
||||
|
||||
def _semantic_token_registration_options_hook(
|
||||
object_: Any, _: type
|
||||
) -> Optional[
|
||||
Union[OptionalPrimitive, lsp_types.SemanticTokensRegistrationOptionsFullType1]
|
||||
Union[OptionalPrimitive, lsp_types.ClientSemanticTokensRequestFullDelta]
|
||||
]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(
|
||||
object_, lsp_types.SemanticTokensRegistrationOptionsFullType1
|
||||
object_, lsp_types.ClientSemanticTokensRequestFullDelta
|
||||
)
|
||||
|
||||
def _inline_completion_provider_hook(
|
||||
@@ -659,7 +659,7 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
def _inline_completion_list_hook(
|
||||
object_: Any, _: type
|
||||
) -> Optional[
|
||||
Union[lsp_types.InlineCompletionList, List[lsp_types.InlineCompletionItem]]
|
||||
Union[lsp_types.InlineCompletionList, Sequence[lsp_types.InlineCompletionItem]]
|
||||
]:
|
||||
if object_ is None:
|
||||
return None
|
||||
@@ -682,7 +682,9 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
def _symbol_list_hook(
|
||||
object_: Any, _: type
|
||||
) -> Optional[
|
||||
Union[List[lsp_types.SymbolInformation], List[lsp_types.WorkspaceSymbol]]
|
||||
Union[
|
||||
Sequence[lsp_types.SymbolInformation], Sequence[lsp_types.WorkspaceSymbol]
|
||||
]
|
||||
]:
|
||||
if object_ is None:
|
||||
return None
|
||||
@@ -703,22 +705,71 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
converter.structure(item, lsp_types.SymbolInformation) for item in object_
|
||||
]
|
||||
|
||||
def _notebook_sync_registration_option_selector_hook(
|
||||
def _language_kind_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1,
|
||||
lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2,
|
||||
lsp_types.LanguageKind,
|
||||
OptionalPrimitive,
|
||||
]:
|
||||
if "notebook" in object_:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(object_, lsp_types.LanguageKind)
|
||||
|
||||
def _text_edit_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
lsp_types.TextEdit, lsp_types.AnnotatedTextEdit, lsp_types.SnippetTextEdit
|
||||
]:
|
||||
if "snippet" in object_:
|
||||
return converter.structure(object_, lsp_types.SnippetTextEdit)
|
||||
if "annotationId" in object_:
|
||||
return converter.structure(object_, lsp_types.AnnotatedTextEdit)
|
||||
return converter.structure(object_, lsp_types.TextEdit)
|
||||
|
||||
def _completion_item_kind_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[lsp_types.CompletionItemKind, OptionalPrimitive]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(object_, lsp_types.CompletionItemKind)
|
||||
|
||||
def _relative_pattern_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[OptionalPrimitive, lsp_types.RelativePattern]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(object_, lsp_types.RelativePattern)
|
||||
|
||||
def _workspace_folder_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[OptionalPrimitive, lsp_types.WorkspaceFolder]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if isinstance(object_, (bool, int, str, float)):
|
||||
return object_
|
||||
return converter.structure(object_, lsp_types.WorkspaceFolder)
|
||||
|
||||
def _text_document_content_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
OptionalPrimitive,
|
||||
lsp_types.TextDocumentContentRegistrationOptions,
|
||||
lsp_types.TextDocumentContentOptions,
|
||||
]:
|
||||
if object_ is None:
|
||||
return None
|
||||
if "id" in object_:
|
||||
return converter.structure(
|
||||
object_,
|
||||
lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1,
|
||||
object_, lsp_types.TextDocumentContentRegistrationOptions
|
||||
)
|
||||
else:
|
||||
return converter.structure(
|
||||
object_,
|
||||
lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2,
|
||||
)
|
||||
return converter.structure(object_, lsp_types.TextDocumentContentOptions)
|
||||
|
||||
structure_hooks = [
|
||||
(
|
||||
@@ -892,7 +943,7 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
_inlay_hint_provider_hook,
|
||||
),
|
||||
(
|
||||
Union[str, List[lsp_types.InlayHintLabelPart]],
|
||||
Union[str, Sequence[lsp_types.InlayHintLabelPart]],
|
||||
_inlay_hint_label_part_hook,
|
||||
),
|
||||
(
|
||||
@@ -912,22 +963,27 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
_code_action_hook,
|
||||
),
|
||||
(
|
||||
Optional[Union[List[lsp_types.CompletionItem], lsp_types.CompletionList]],
|
||||
Optional[
|
||||
Union[Sequence[lsp_types.CompletionItem], lsp_types.CompletionList]
|
||||
],
|
||||
_completion_list_hook,
|
||||
),
|
||||
(
|
||||
Optional[
|
||||
Union[
|
||||
lsp_types.Location,
|
||||
List[lsp_types.Location],
|
||||
List[lsp_types.LocationLink],
|
||||
Sequence[lsp_types.Location],
|
||||
Sequence[lsp_types.LocationLink],
|
||||
]
|
||||
],
|
||||
_location_hook,
|
||||
),
|
||||
(
|
||||
Optional[
|
||||
Union[List[lsp_types.SymbolInformation], List[lsp_types.DocumentSymbol]]
|
||||
Union[
|
||||
Sequence[lsp_types.SymbolInformation],
|
||||
Sequence[lsp_types.DocumentSymbol],
|
||||
]
|
||||
],
|
||||
_symbol_hook,
|
||||
),
|
||||
@@ -935,8 +991,8 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
Union[
|
||||
lsp_types.MarkupContent,
|
||||
str,
|
||||
lsp_types.MarkedString_Type1,
|
||||
List[Union[str, lsp_types.MarkedString_Type1]],
|
||||
lsp_types.MarkedStringWithLanguage,
|
||||
Sequence[Union[str, lsp_types.MarkedStringWithLanguage]],
|
||||
],
|
||||
_markup_content_hook,
|
||||
),
|
||||
@@ -950,14 +1006,14 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
_document_edit_hook,
|
||||
),
|
||||
(
|
||||
Optional[Union[bool, lsp_types.SemanticTokensOptionsFullType1]],
|
||||
Optional[Union[bool, lsp_types.SemanticTokensFullDelta]],
|
||||
_semantic_tokens_hook,
|
||||
),
|
||||
(
|
||||
Optional[
|
||||
Union[
|
||||
bool,
|
||||
lsp_types.SemanticTokensClientCapabilitiesRequestsTypeFullType1,
|
||||
lsp_types.ClientSemanticTokensRequestFullDelta,
|
||||
]
|
||||
],
|
||||
_semantic_tokens_capabilities_hook,
|
||||
@@ -1012,8 +1068,8 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
),
|
||||
(
|
||||
Union[
|
||||
lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType1,
|
||||
lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2,
|
||||
lsp_types.NotebookDocumentFilterWithNotebook,
|
||||
lsp_types.NotebookDocumentFilterWithCells,
|
||||
],
|
||||
_notebook_sync_option_selector_hook,
|
||||
),
|
||||
@@ -1027,7 +1083,7 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
_position_encoding_hook,
|
||||
),
|
||||
(
|
||||
Optional[Union[bool, lsp_types.SemanticTokensRegistrationOptionsFullType1]],
|
||||
Optional[Union[bool, lsp_types.ClientSemanticTokensRequestFullDelta]],
|
||||
_semantic_token_registration_options_hook,
|
||||
),
|
||||
(
|
||||
@@ -1037,7 +1093,8 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
(
|
||||
Optional[
|
||||
Union[
|
||||
lsp_types.InlineCompletionList, List[lsp_types.InlineCompletionItem]
|
||||
lsp_types.InlineCompletionList,
|
||||
Sequence[lsp_types.InlineCompletionItem],
|
||||
]
|
||||
],
|
||||
_inline_completion_list_hook,
|
||||
@@ -1049,17 +1106,63 @@ def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converte
|
||||
(
|
||||
Optional[
|
||||
Union[
|
||||
List[lsp_types.SymbolInformation], List[lsp_types.WorkspaceSymbol]
|
||||
Sequence[lsp_types.SymbolInformation],
|
||||
Sequence[lsp_types.WorkspaceSymbol],
|
||||
]
|
||||
],
|
||||
_symbol_list_hook,
|
||||
),
|
||||
(
|
||||
Union[lsp_types.LanguageKind, str],
|
||||
_language_kind_hook,
|
||||
),
|
||||
(
|
||||
Union[
|
||||
lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1,
|
||||
lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2,
|
||||
lsp_types.TextEdit,
|
||||
lsp_types.AnnotatedTextEdit,
|
||||
lsp_types.SnippetTextEdit,
|
||||
],
|
||||
_notebook_sync_registration_option_selector_hook,
|
||||
_text_edit_hook,
|
||||
),
|
||||
(
|
||||
Optional[Union[lsp_types.CompletionItemKind, int]],
|
||||
_completion_item_kind_hook,
|
||||
),
|
||||
(
|
||||
Union[lsp_types.CompletionItemKind, int],
|
||||
_completion_item_kind_hook,
|
||||
),
|
||||
(
|
||||
Optional[Union[str, lsp_types.RelativePattern]],
|
||||
_relative_pattern_hook,
|
||||
),
|
||||
(
|
||||
Union[str, lsp_types.RelativePattern],
|
||||
_relative_pattern_hook,
|
||||
),
|
||||
(
|
||||
Optional[Union[lsp_types.WorkspaceFolder, str]],
|
||||
_workspace_folder_hook,
|
||||
),
|
||||
(
|
||||
Union[lsp_types.WorkspaceFolder, str],
|
||||
_workspace_folder_hook,
|
||||
),
|
||||
(
|
||||
Optional[
|
||||
Union[
|
||||
lsp_types.TextDocumentContentOptions,
|
||||
lsp_types.TextDocumentContentRegistrationOptions,
|
||||
]
|
||||
],
|
||||
_text_document_content_hook,
|
||||
),
|
||||
(
|
||||
Union[
|
||||
lsp_types.TextDocumentContentOptions,
|
||||
lsp_types.TextDocumentContentRegistrationOptions,
|
||||
],
|
||||
_text_document_content_hook,
|
||||
),
|
||||
]
|
||||
for type_, hook in structure_hooks:
|
||||
@@ -1085,9 +1188,9 @@ def _register_required_structure_hooks(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
str,
|
||||
lsp_types.TextDocumentFilter_Type1,
|
||||
lsp_types.TextDocumentFilter_Type2,
|
||||
lsp_types.TextDocumentFilter_Type3,
|
||||
lsp_types.TextDocumentFilterLanguage,
|
||||
lsp_types.TextDocumentFilterScheme,
|
||||
lsp_types.TextDocumentFilterPattern,
|
||||
lsp_types.NotebookCellTextDocumentFilter,
|
||||
]:
|
||||
if isinstance(object_, str):
|
||||
@@ -1097,30 +1200,31 @@ def _register_required_structure_hooks(
|
||||
object_, lsp_types.NotebookCellTextDocumentFilter
|
||||
)
|
||||
elif "language" in object_:
|
||||
return converter.structure(object_, lsp_types.TextDocumentFilter_Type1)
|
||||
return converter.structure(object_, lsp_types.TextDocumentFilterLanguage)
|
||||
elif "scheme" in object_:
|
||||
return converter.structure(object_, lsp_types.TextDocumentFilter_Type2)
|
||||
return converter.structure(object_, lsp_types.TextDocumentFilterScheme)
|
||||
else:
|
||||
return converter.structure(object_, lsp_types.TextDocumentFilter_Type3)
|
||||
return converter.structure(object_, lsp_types.TextDocumentFilterPattern)
|
||||
|
||||
def _notebook_filter_hook(
|
||||
object_: Any, _: type
|
||||
) -> Union[
|
||||
str,
|
||||
lsp_types.NotebookDocumentFilter_Type1,
|
||||
lsp_types.NotebookDocumentFilter_Type2,
|
||||
lsp_types.NotebookDocumentFilter_Type3,
|
||||
lsp_types.NotebookDocumentFilterNotebookType,
|
||||
lsp_types.NotebookDocumentFilterScheme,
|
||||
lsp_types.NotebookDocumentFilterPattern,
|
||||
]:
|
||||
if isinstance(object_, str):
|
||||
return str(object_)
|
||||
elif "notebookType" in object_:
|
||||
return converter.structure(object_, lsp_types.NotebookDocumentFilter_Type1)
|
||||
return converter.structure(
|
||||
object_, lsp_types.NotebookDocumentFilterNotebookType
|
||||
)
|
||||
elif "scheme" in object_:
|
||||
return converter.structure(object_, lsp_types.NotebookDocumentFilter_Type2)
|
||||
return converter.structure(object_, lsp_types.NotebookDocumentFilterScheme)
|
||||
else:
|
||||
return converter.structure(object_, lsp_types.NotebookDocumentFilter_Type3)
|
||||
return converter.structure(object_, lsp_types.NotebookDocumentFilterPattern)
|
||||
|
||||
# TODO: Remove the ignore after this issue with attrs is addressed in either attrs or mypy
|
||||
NotebookSelectorItem = attrs.fields(
|
||||
lsp_types.NotebookCellTextDocumentFilter
|
||||
).notebook.type
|
||||
@@ -1133,9 +1237,9 @@ def _register_required_structure_hooks(
|
||||
(Optional[Union[bool, Any]], lambda object_, _type: object_),
|
||||
(
|
||||
Union[
|
||||
lsp_types.TextDocumentFilter_Type1,
|
||||
lsp_types.TextDocumentFilter_Type2,
|
||||
lsp_types.TextDocumentFilter_Type3,
|
||||
lsp_types.TextDocumentFilterLanguage,
|
||||
lsp_types.TextDocumentFilterScheme,
|
||||
lsp_types.TextDocumentFilterPattern,
|
||||
lsp_types.NotebookCellTextDocumentFilter,
|
||||
],
|
||||
_text_document_filter_hook,
|
||||
@@ -1144,20 +1248,26 @@ def _register_required_structure_hooks(
|
||||
(
|
||||
Union[
|
||||
str,
|
||||
lsp_types.NotebookDocumentFilter_Type1,
|
||||
lsp_types.NotebookDocumentFilter_Type2,
|
||||
lsp_types.NotebookDocumentFilter_Type3,
|
||||
lsp_types.NotebookDocumentFilterNotebookType,
|
||||
lsp_types.NotebookDocumentFilterScheme,
|
||||
lsp_types.NotebookDocumentFilterPattern,
|
||||
],
|
||||
_notebook_filter_hook,
|
||||
),
|
||||
(NotebookSelectorItem, _notebook_filter_hook),
|
||||
(
|
||||
Union[lsp_types.LSPObject, List["LSPAny"], str, int, float, bool, None],
|
||||
Union[lsp_types.LSPObject, Sequence["LSPAny"], str, int, float, bool, None],
|
||||
_lsp_object_hook,
|
||||
),
|
||||
(
|
||||
Union[
|
||||
lsp_types.LSPObject, List[lsp_types.LSPAny], str, int, float, bool, None
|
||||
lsp_types.LSPObject,
|
||||
Sequence[lsp_types.LSPAny],
|
||||
str,
|
||||
int,
|
||||
float,
|
||||
bool,
|
||||
None,
|
||||
],
|
||||
_lsp_object_hook,
|
||||
),
|
||||
@@ -1173,10 +1283,10 @@ def _register_required_structure_hooks(
|
||||
(
|
||||
Union[
|
||||
lsp_types.LSPObject,
|
||||
List[
|
||||
Sequence[
|
||||
Union[
|
||||
lsp_types.LSPObject,
|
||||
List["LSPAny"],
|
||||
Sequence["LSPAny"],
|
||||
str,
|
||||
int,
|
||||
float,
|
||||
@@ -1220,7 +1330,7 @@ def _register_custom_property_hooks(converter: cattrs.Converter) -> cattrs.Conve
|
||||
)
|
||||
for a in attrs.fields(cls)
|
||||
}
|
||||
return cattrs.gen.make_dict_unstructure_fn(cls, converter, **attributes)
|
||||
return cattrs.gen.make_dict_unstructure_fn(cls, converter, **attributes) # type: ignore
|
||||
|
||||
def _with_custom_structure(cls: type) -> Any:
|
||||
attributes = {
|
||||
@@ -1230,7 +1340,7 @@ def _register_custom_property_hooks(converter: cattrs.Converter) -> cattrs.Conve
|
||||
)
|
||||
for a in attrs.fields(cls)
|
||||
}
|
||||
return cattrs.gen.make_dict_structure_fn(cls, converter, **attributes)
|
||||
return cattrs.gen.make_dict_structure_fn(cls, converter, **attributes) # type: ignore
|
||||
|
||||
converter.register_unstructure_hook_factory(attrs.has, _with_custom_unstructure)
|
||||
converter.register_structure_hook_factory(attrs.has, _with_custom_structure)
|
||||
|
||||
+3310
-2203
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,41 +0,0 @@
|
||||
packaging-25.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
packaging-25.0.dist-info/METADATA,sha256=W2EaYJw4_vw9YWv0XSCuyY-31T8kXayp4sMPyFx6woI,3281
|
||||
packaging-25.0.dist-info/RECORD,,
|
||||
packaging-25.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
packaging-25.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||
packaging-25.0.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
||||
packaging-25.0.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
||||
packaging-25.0.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
||||
packaging/__init__.py,sha256=_0cDiPVf2S-bNfVmZguxxzmrIYWlyASxpqph4qsJWUc,494
|
||||
packaging/__pycache__/__init__.cpython-311.pyc,,
|
||||
packaging/__pycache__/_elffile.cpython-311.pyc,,
|
||||
packaging/__pycache__/_manylinux.cpython-311.pyc,,
|
||||
packaging/__pycache__/_musllinux.cpython-311.pyc,,
|
||||
packaging/__pycache__/_parser.cpython-311.pyc,,
|
||||
packaging/__pycache__/_structures.cpython-311.pyc,,
|
||||
packaging/__pycache__/_tokenizer.cpython-311.pyc,,
|
||||
packaging/__pycache__/markers.cpython-311.pyc,,
|
||||
packaging/__pycache__/metadata.cpython-311.pyc,,
|
||||
packaging/__pycache__/requirements.cpython-311.pyc,,
|
||||
packaging/__pycache__/specifiers.cpython-311.pyc,,
|
||||
packaging/__pycache__/tags.cpython-311.pyc,,
|
||||
packaging/__pycache__/utils.cpython-311.pyc,,
|
||||
packaging/__pycache__/version.cpython-311.pyc,,
|
||||
packaging/_elffile.py,sha256=UkrbDtW7aeq3qqoAfU16ojyHZ1xsTvGke_WqMTKAKd0,3286
|
||||
packaging/_manylinux.py,sha256=t4y_-dTOcfr36gLY-ztiOpxxJFGO2ikC11HgfysGxiM,9596
|
||||
packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694
|
||||
packaging/_parser.py,sha256=gYfnj0pRHflVc4RHZit13KNTyN9iiVcU2RUCGi22BwM,10221
|
||||
packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431
|
||||
packaging/_tokenizer.py,sha256=OYzt7qKxylOAJ-q0XyK1qAycyPRYLfMPdGQKRXkZWyI,5310
|
||||
packaging/licenses/__init__.py,sha256=VsK4o27CJXWfTi8r2ybJmsBoCdhpnBWuNrskaCVKP7U,5715
|
||||
packaging/licenses/__pycache__/__init__.cpython-311.pyc,,
|
||||
packaging/licenses/__pycache__/_spdx.cpython-311.pyc,,
|
||||
packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398
|
||||
packaging/markers.py,sha256=P0we27jm1xUzgGMJxBjtUFCIWeBxTsMeJTOJ6chZmAY,12049
|
||||
packaging/metadata.py,sha256=8IZErqQQnNm53dZZuYq4FGU4_dpyinMeH1QFBIWIkfE,34739
|
||||
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947
|
||||
packaging/specifiers.py,sha256=gtPu5DTc-F9baLq3FTGEK6dPhHGCuwwZetaY0PSV2gs,40055
|
||||
packaging/tags.py,sha256=41s97W9Zatrq2Ed7Rc3qeBDaHe8pKKvYq2mGjwahfXk,22745
|
||||
packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050
|
||||
packaging/version.py,sha256=olfyuk_DPbflNkJ4wBWetXQ17c74x3DB501degUv7DY,16676
|
||||
@@ -0,0 +1 @@
|
||||
uv
|
||||
+15
-8
@@ -1,25 +1,26 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: packaging
|
||||
Version: 25.0
|
||||
Version: 26.3
|
||||
Summary: Core utilities for Python packages
|
||||
Author-email: Donald Stufft <donald@stufft.io>
|
||||
Requires-Python: >=3.8
|
||||
Requires-Python: >=3.9
|
||||
Description-Content-Type: text/x-rst
|
||||
License-Expression: Apache-2.0 OR BSD-2-Clause
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
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 :: 3.15
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Programming Language :: Python :: Free Threading :: 4 - Resilient
|
||||
Classifier: Typing :: Typed
|
||||
License-File: LICENSE
|
||||
License-File: LICENSE.APACHE
|
||||
@@ -42,7 +43,7 @@ or benefit greatly from having a single shared implementation (eg: :pep:`425`).
|
||||
.. end-intro
|
||||
|
||||
The ``packaging`` project includes the following: version handling, specifiers,
|
||||
markers, requirements, tags, utilities.
|
||||
markers, requirements, tags, metadata, lockfiles, utilities.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
@@ -52,8 +53,14 @@ The `documentation`_ provides information and the API for the following:
|
||||
- Version Handling
|
||||
- Specifiers
|
||||
- Markers
|
||||
- Licenses
|
||||
- Requirements
|
||||
- Metadata
|
||||
- Tags
|
||||
- Lockfiles (pylock)
|
||||
- Direct URL helpers
|
||||
- Dependency groups
|
||||
- Errors
|
||||
- Utilities
|
||||
|
||||
Installation
|
||||
@@ -70,11 +77,11 @@ Discussion
|
||||
|
||||
If you run into bugs, you can file them in our `issue tracker`_.
|
||||
|
||||
You can also join ``#pypa`` on Freenode to ask questions or get involved.
|
||||
|
||||
You can also join discussions on `GitHub Discussions`_ to ask questions or get involved.
|
||||
|
||||
.. _`documentation`: https://packaging.pypa.io/
|
||||
.. _`issue tracker`: https://github.com/pypa/packaging/issues
|
||||
.. _`GitHub Discussions`: https://github.com/pypa/packaging/discussions
|
||||
|
||||
|
||||
Code of Conduct
|
||||
@@ -0,0 +1,31 @@
|
||||
packaging-26.3.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
||||
packaging-26.3.dist-info/METADATA,sha256=cP24n8TUqaBDv3NyuJcrzIg_3f80q1Xpz4DXOHU4R2M,3544
|
||||
packaging-26.3.dist-info/RECORD,,
|
||||
packaging-26.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
packaging-26.3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
||||
packaging-26.3.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
||||
packaging-26.3.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
||||
packaging-26.3.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
||||
packaging/__init__.py,sha256=bnWM3QAossrXTHObmtycnwbwVJMjBigLh4v-WdOSGbU,494
|
||||
packaging/_elffile.py,sha256=HwJfVMVz8ahXQMT72QjMHR-wwXfBQ3fr9fCdbNU2h4A,3236
|
||||
packaging/_manylinux.py,sha256=W26EMkxCU4q0zbFI4kMP26NvBEJ7Dv2lW-oIOjX6YTY,10000
|
||||
packaging/_musllinux.py,sha256=Ayj7gnsRcMd99MEH1Jvo3403JiEDSQph6Kytii-crf8,2774
|
||||
packaging/_parser.py,sha256=iKNIEVIJMnSX5FO2NeXbCZ_VkvkzXXEMcxOynNswxQw,12639
|
||||
packaging/_ranges.py,sha256=t7WV8uaSrrNpfFRbhcJMu8tFUvKtxtkFmqgtCHfoFrc,30850
|
||||
packaging/_structures.py,sha256=60jRbF78p8z5MKnNd6cAprgOadCJHV0DlmUmRBqFZcs,1109
|
||||
packaging/_tokenizer.py,sha256=zCXlfA1WTInhUHNHHW4cJkKZypRvsXwOHkr1TvdxPuY,5573
|
||||
packaging/dependency_groups.py,sha256=UwnMVkyjAi37WUgW6dWTuwnN80IIwfuMXsmvtsZwJ4w,11224
|
||||
packaging/direct_url.py,sha256=hDuqldBMu0kojenZ_XfulG6GLLYDEj5-RqaeFx-n0Gs,11645
|
||||
packaging/errors.py,sha256=6hfEYXAf8v_IF65-lFadJOMIieBP2xIKtyEXjG1nGIs,2680
|
||||
packaging/licenses/__init__.py,sha256=WQ0S7uc92-xTtOnuW_xk2cs6rLSUDo68uiYHV85WdAs,7859
|
||||
packaging/licenses/_spdx.py,sha256=WW7DXiyg68up_YND_wpRYlr1SHhiV4FfJLQffghhMxQ,51122
|
||||
packaging/markers.py,sha256=bL6GDTXZjStZnn8Oxz_qsSTgFXEbML6QReZ4ySlA2cw,20554
|
||||
packaging/metadata.py,sha256=k0FJ9CClRJ5vN2qmHW8moII-uCzHxqHIzeWqe-Q9D2M,42059
|
||||
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
packaging/pylock.py,sha256=G84Kn-Y-Qrh-jO0F2icrrxamiqcEs3GEBeJzWmESRnw,35232
|
||||
packaging/ranges.py,sha256=tHgc6RPl3_ngeQbyBYXFTDAo4ff_VjQYKOedUi3b-rY,83054
|
||||
packaging/requirements.py,sha256=RZR26yEH_jDmsTEAUaW9tEx9O3944Oh7raJoKCy6YvU,7040
|
||||
packaging/specifiers.py,sha256=ZhrTHd9F_7hh0mJnNPTj1mlmApuP23lYa7EFgl2NnVk,52798
|
||||
packaging/tags.py,sha256=DnF6CCp5Yg15zwMhtCDfFVk8SRb82gPCfLnYLgfvXW4,38302
|
||||
packaging/utils.py,sha256=oTdCzZmU7spa37AknRJkCMR4Am6x9Iy5evvy_p8o7MY,12215
|
||||
packaging/version.py,sha256=VYsvtQ_RmMZgpMCC-WuJGcVlkRtrS24H80trYMB0vpc,39040
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user