Merge remote-tracking branch 'origin/main' into features/move_and_safe_delete

This commit is contained in:
Dominik Jain committed 2026-04-01 19:08:17 +02:00
commit fac745fc7c
74 files changed
+1656 -140

No files matched your search

+1 -1
View File
@@ -566,7 +566,7 @@ jobs:
df -h
- name: Test with pytest
shell: bash
run: uv run poe test
run: uv run poe test -q --tb=short
- name: Type-checking with mypy
shell: bash
run: uv run poe type-check
+60 -18
View File
@@ -2,38 +2,80 @@
Serena comes with built-in tools for monitoring and managing the current session:
* the **web-based dashboard** (enabled by default)
* the **Serena Dashboard** (enabled by default)
The dashboard provides detailed information on your Serena session, the current configuration and provides access to logs.
Some settings (e.g. the current set of active programming languages) can also be directly modified through the dashboard.
The dashboard is supported on all platforms.
By default, it will be accessible at `http://localhost:24282/dashboard/index.html`,
but a higher port may be used if the default port is unavailable/multiple instances are running.
**We recommend always enabling the dashboard**. If you don't want the browser to open automatically,
you can disable it while still keeping the dashboard running in the background (see below).
* the **GUI tool** (disabled by default)
* the **GUI Log Viewer** (disabled by default)
The GUI tool is a native application window which displays logs.
It furthermore allows you to shut down the agent and to access the dashboard's URL (if it is running).
The GUI tool is a legacy native application which displays Serena's live logs.
This is mainly supported on Windows, but it may also work on Linux; macOS is unsupported.
Both can be configured in Serena's [configuration](050_configuration) file (`serena_config.yml`).
If enabled, they will automatically be opened as soon as the Serena agent/MCP server is started.
For the dashboard, this can be disabled if desired (see below).
## Disabling Automatic Browser Opening
## The Serena Dashboard
If you prefer not to have the dashboard open automatically (e.g., to avoid focus stealing), you can disable it
by setting `web_dashboard_open_on_launch: False` in your `serena_config.yml` or by passing `--open-web-dashboard False`
to `start-mcp-server` CLI command.
The dashboard is a web application, which is opened via a native application wrapper on Windows and macOS,
where it will appear in the system tray. **Click on the tray icon** to open the application window.
When automatic opening is disabled, you can still access the dashboard by:
On Linux, native app support is difficult to provide, so the dashboard will be opened in your default web browser instead.
By default, the dashboard can be accessed at `http://localhost:24282/dashboard/index.html`,
but a higher port may be used if the default port is unavailable/multiple instances are running.
### Features
The dashboard provides ...
* a detailed overview of
* the current Serena status and configuration (e.g. active tools, active programming languages, enabled modes and contexts, etc.)
* ongoing and past tool calls (and statistics on tool usage)
* access to Serena's live logs
* the ability to modify settings (e.g. the set of active programming languages) on the fly
* the ability to edit
* configuration files (global `serena_config.yml` and project-specific `project.yml`)
* memories of the current project
* the ability to shut down the Serena MCP server.
### Configuring the Dashboard
You can configure settings for the dashboard in the [global configuration file](global-config).
In particular, you can configure
* whether the dashboard is enabled at all.
We highly recommend keeping it enabled, as it provides valuable insights into Serena's operation and allows you to adjust configuration conveniently.
* whether the dashboard window is opened automatically when Serena is started (see below).
* network settings (for cases where you might want to access the Dashboard non-locally).
#### Dashboard Opening Behaviour
When Serena is started, the Dashboard window is opened by default in order to make users aware of its existence.
If you prefer not to have this happen (e.g., to avoid focus stealing), you can disable prevent it from opening
by setting `web_dashboard_open_on_launch: False` in the [global configuration file](global-config) or by passing `--open-web-dashboard False`
to the `start-mcp-server` CLI command.
On Windows and macOS, you can conveniently open the dashboard at any time by clicking on the tray icon, so automatic
opening is not a requirement to be able to access the dashboard on these platforms.
On Linux, access to the dashboard is more cumbersome without native app support.
Yet you may still access it by
* asking the LLM to "open the Serena dashboard", which will open the dashboard in your default browser
(the tool `open_dashboard` is enabled for this purpose, provided that the dashboard is active,
not opened by default and the GUI tool, which can provide the URL, is not enabled)
* navigating directly to the URL (see above)
* navigating directly to the URL (see above).
## The GUI Log Viewer
The Serena GUI Log Viewer is a legacy application which provides access to Serena's live logs.
Via its menu, it furthermore allows you to
* shut down the agent
* access the dashboard's URL (if it is running).
The tool is mainly supported on Windows, but it may work on some Linux systems as well (depending on your desktop environment).
To enable the tool, set `gui_log_window` to `true` in Serena's [global configuration file](global-config).
+11
View File
@@ -57,6 +57,17 @@
final.setuptools
];
});
proxy-tools = prev.proxy-tools.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
final.setuptools
];
});
pywebview = prev.pywebview.overrideAttrs (old: {
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
final.setuptools
final.setuptools-scm
];
});
};
python = pkgs.python311;
+7 -1
View File
@@ -36,6 +36,8 @@ dependencies = [
"tiktoken==0.12.0",
"anthropic==0.59.0",
"beautifulsoup4==4.14.2",
"pywebview",
"pystray==0.19.5",
# Transitive deps pinned for security (dependabot alerts).
# Exact pins because uvx installs from git, ignoring the lock file.
"urllib3==2.6.3",
@@ -45,8 +47,12 @@ dependencies = [
"filelock==3.25.2",
"cryptography==46.0.6",
"regex==2026.2.28",
"pythonnet==3.1.0-rc0 ; sys_platform == 'win32'",
]
[tool.uv.sources]
pywebview = { git = "https://github.com/r0x0r/pywebview", rev = "345596c1f4fd59fd26c7aa02d4176c5362bcbabe" }
[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
@@ -88,7 +94,7 @@ dev = [
"nbconvert==7.17.0",
"wheel==0.46.3",
"pyasn1==0.6.3",
"PyJWT==2.10.1",
"PyJWT==2.12.0",
]
agno = ["agno==2.5.10", "sqlalchemy==2.0.41"] # agno bumped for session state overwrite CVE
google = ["google-genai==1.27.0"]
Binary file not shown.
Binary file not shown.
+47 -7
View File
@@ -2,6 +2,7 @@
The Serena Model Context Protocol (MCP) Server
"""
import multiprocessing
import os
import platform
import subprocess
@@ -11,6 +12,7 @@ from contextlib import contextmanager
from logging import Logger
from typing import TYPE_CHECKING, Optional, TypeVar
import webview
from sensai.util import logging
from sensai.util.logging import LogTime
from sensai.util.string import dict_string
@@ -28,7 +30,7 @@ from serena.config.serena_config import (
SerenaPaths,
ToolInclusionDefinition,
)
from serena.dashboard import SerenaDashboardAPI
from serena.dashboard import SerenaDashboardAPI, SerenaDashboardViewer
from serena.ls_manager import LanguageServerManager
from serena.project import MemoriesManager, Project
from serena.prompt_factory import SerenaPromptFactory
@@ -375,8 +377,7 @@ class SerenaAgent:
dashboard_url = f"http://{dashboard_host}:{port}/dashboard/index.html"
self._dashboard_url = dashboard_url
log.info("Serena web dashboard started at %s", dashboard_url)
if self.serena_config.web_dashboard_open_on_launch:
self.open_dashboard()
self._start_dashboard_viewer(minimized=not self.serena_config.web_dashboard_open_on_launch)
# inform the GUI window (if any)
if self._gui_log_viewer is not None:
self._gui_log_viewer.set_dashboard_url(dashboard_url)
@@ -519,11 +520,46 @@ class SerenaAgent:
"""
return self._dashboard_url
@staticmethod
def _start_dashboard_viewer_process_function(url: str, minimized: bool) -> None:
"""
Main function of the subprocess for starting the dashboard viewer
"""
try:
SerenaDashboardViewer(url, start_minimized=minimized).run()
except webview.errors.WebViewException as e:
log.warning(f"Could not open Serena Dashboard viewer. Cause:\n{e}")
# Fall back to opening the browser window if the window was supposed to be shown directly
if not minimized:
SerenaAgent._open_dashboard_in_browser(url)
def _start_dashboard_viewer(self, minimized: bool) -> None:
"""
Starts the dashboard viewer (in a separate process) or, if the current platform does not support it,
opens the dashboard in the default web browser.
:param minimized: whether the dashboard viewer should be started minimized (if supported on the current platform).
If the viewer is not supported on the current platform, then this controls whether to open the browser window.
"""
if not system_has_usable_display():
log.info("Not starting the Serena dashboard viewer because no usable display was detected.")
return
url = self.get_dashboard_url()
assert url is not None
if SerenaDashboardViewer.is_current_platform_supported():
process = multiprocessing.Process(target=self._start_dashboard_viewer_process_function, args=(url, minimized), daemon=True)
process.start()
else:
log.info("Not starting Serena dashboard viewer because the current platform does not support it; using browser-based fallback")
if not minimized:
self._open_dashboard_in_browser(url)
def open_dashboard(self) -> bool:
"""
Opens the Serena web dashboard in the default web browser.
Opens the Serena dashboard (for on-demand usage as triggered by the user, e.g. via a tool)
:return: a message indicating success or failure
:return: True if the dashboard was opened, False if it could not be opened
"""
if self._dashboard_url is None:
raise Exception("Dashboard is not running.")
@@ -532,15 +568,19 @@ class SerenaAgent:
log.warning("Not opening the Serena web dashboard because no usable display was detected.")
return False
self._open_dashboard_in_browser(self._dashboard_url)
return True
@staticmethod
def _open_dashboard_in_browser(url: str) -> None:
# Use a subprocess to avoid any output from webbrowser.open being written to stdout
subprocess.Popen(
[sys.executable, "-c", f"import webbrowser; webbrowser.open({self._dashboard_url!r})"],
[sys.executable, "-c", f"import webbrowser; webbrowser.open({url!r})"],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
start_new_session=True, # Detach from parent process
)
return True
def get_exposed_tool_instances(self) -> list["Tool"]:
"""
+16
View File
@@ -406,6 +406,22 @@ class TopLevelCommands(AutoRegisteringGroup):
run_kwargs["port"] = port
server.run(**run_kwargs)
@staticmethod
@click.command(
"dashboard-viewer",
help="Open the Serena dashboard viewer for a given URL.",
context_settings={"max_content_width": _MAX_CONTENT_WIDTH},
)
@click.argument("url", type=str)
@click.option("--width", type=int, default=1400, show_default=True, help="Window width.")
@click.option("--height", type=int, default=900, show_default=True, help="Window height.")
@click.option("--minimized", is_flag=True, default=False, help="Whether to start minimized/in tray.")
def dashboard_viewer(url: str, width: int, height: int, minimized: bool) -> None:
from serena.dashboard import SerenaDashboardViewer
viewer = SerenaDashboardViewer(url, start_minimized=minimized, width=width, height=height)
viewer.run()
class ModeCommands(AutoRegisteringGroup):
"""Group for 'mode' subcommands."""
+199
View File
@@ -1,11 +1,14 @@
import os
import socket
import sys
import threading
from collections.abc import Callable
from pathlib import Path
from typing import TYPE_CHECKING, Any, Self
import webview
from flask import Flask, Response, redirect, request, send_from_directory
from PIL import Image
from pydantic import BaseModel
from sensai.util import logging
@@ -676,3 +679,199 @@ class SerenaDashboardAPI:
thread = threading.Thread(target=lambda: self.run(host=host, port=port), daemon=True)
thread.start()
return thread, port
class SerenaDashboardViewer:
"""
Minimal pywebview wrapper with optional system tray.
"""
def __init__(
self,
url: str,
*,
start_minimized: bool = False,
width: int = 1400,
height: int = 900,
):
self.url = url
# Use system tray to allow hiding to tray and intercepting close to hide instead of quit
self.tray = True
self.title = "Serena Dashboard"
self.width = width
self.height = height
self.start_minimized = start_minimized
self.window: webview.Window
self._tray_icon: Any
self._quitting = False
self._app_icon_path: str | None = None
@staticmethod
def is_current_platform_supported() -> bool:
"""
:return: whether the current platform supports the dashboard viewer
"""
# The dashboard viewer (with system tray) is technically supported only on Windows and macOS.
# Linux support is problematic; see https://github.com/oraios/serena/pull/1117#issuecomment-4128753943
supported_platforms = [
"win32",
# NOTE: Disabling macOS support for now, because the tray behaviour is suboptimal (too many icons when
# subagents are spawned, etc.)
# "darwin"
]
return sys.platform in supported_platforms
def run(self) -> None:
# set app id (avoid app being lumped together with other Python-based apps in Windows taskbar)
if sys.platform == "win32":
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("oraios.serena")
dashboard_path = Path(SERENA_DASHBOARD_DIR)
# .ico is Windows-only; macOS expects a PNG for the window/dock icon.
icon_filename = "serena.ico" if sys.platform == "win32" else "serena-icon-1024-mac.png"
icon_path = str(dashboard_path / icon_filename)
self._app_icon_path = icon_path
# Create hidden to avoid flash; show/restore/minimize in start callback.
window = webview.create_window(
self.title,
self.url,
width=self.width,
height=self.height,
hidden=self.start_minimized,
)
assert window is not None
self.window = window
if self.tray:
self.window.events.closing += self._on_closing
self._start_tray()
def _start_callback() -> None:
if self.start_minimized:
self._hide_window()
else:
self._show_window()
webview.start(_start_callback, icon=icon_path)
def _show_window(self) -> None:
if not self.window:
return
if sys.platform == "darwin":
from PyObjCTools.AppHelper import callAfter
callAfter(self._show_window_on_macos)
else:
self.window.show()
self.window.restore()
def _hide_window(self) -> None:
if not self.window:
return
if sys.platform == "darwin":
from PyObjCTools.AppHelper import callAfter
callAfter(self._hide_window_on_macos)
else:
self.window.hide()
def _show_window_on_macos(self) -> None:
from AppKit import (
NSApplication,
NSApplicationActivationPolicyRegular,
)
from PyObjCTools.AppHelper import callLater
ns_app = NSApplication.sharedApplication()
ns_app.setActivationPolicy_(NSApplicationActivationPolicyRegular)
self._set_macos_app_icon(ns_app)
ns_app.unhide_(None)
ns_app.activateIgnoringOtherApps_(True)
# Give the status item menu a beat to close before restoring the window.
callLater(0.1, self._restore_window_on_macos)
def _restore_window_on_macos(self) -> None:
self.window.show()
self.window.restore()
def _hide_window_on_macos(self) -> None:
from AppKit import (
NSApplication,
NSApplicationActivationPolicyAccessory,
)
self.window.hide()
NSApplication.sharedApplication().setActivationPolicy_(NSApplicationActivationPolicyAccessory)
def _set_macos_app_icon(self, ns_app: Any) -> None:
if not self._app_icon_path:
return
from AppKit import NSImage
ns_image = NSImage.alloc().initByReferencingFile_(self._app_icon_path)
if ns_image is not None:
ns_app.setApplicationIconImage_(ns_image)
def _on_closing(self) -> bool:
"""Intercept window close: hide window instead of quitting (macOS standard behavior)."""
if self._quitting:
return True
self._hide_window()
return False # prevent the window from actually closing
def _start_tray(self) -> None:
# import pystray locally, because the import fails when there is no display!
import pystray
from pystray import MenuItem as Item
from pystray._base import Icon as TrayIcon
dashboard_path = Path(SERENA_DASHBOARD_DIR)
# macOS menu bar icons are displayed at 16pt; 32px covers Retina (@2x).
# Windows/Linux tray icons are larger, so 48px is the better fit there.
icon_filename = "serena-icon-tray-mac.png" if sys.platform == "darwin" else "serena-icon-48.png"
icon_img = Image.open(dashboard_path / icon_filename)
def show(_icon: TrayIcon, _item: Item) -> None:
self._show_window()
def hide(_icon: TrayIcon, _item: Item) -> None:
self._hide_window()
def quit_app(_icon: TrayIcon, _item: Item) -> None:
self._quitting = True
try:
_icon.stop()
finally:
if self.window:
self.window.destroy()
menu = pystray.Menu(
Item("Open", show, default=True),
Item("Hide", hide),
Item("Quit", quit_app),
)
kwargs: dict[str, Any] = {}
if sys.platform == "darwin":
# Passing darwin_nsapplication integrates pystray with the NSApplication
# run loop that webview.start() is about to enter. sharedApplication()
# is idempotent; pywebview will reuse the same singleton.
from AppKit import NSApplication
kwargs["darwin_nsapplication"] = NSApplication.sharedApplication()
self._tray_icon = pystray.Icon("dashboard_viewer", icon_img, self.title, menu, **kwargs)
# On Windows/Linux, run_detached spawns pystray's own internal thread and
# returns immediately. On macOS it hooks into the NSApplication run loop
# that webview.start() is about to enter (run_detached is always called
# before webview.start() on macOS — see run()).
self._tray_icon.run_detached()
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

+11 -13
View File
@@ -29,28 +29,26 @@ line_ending: native
# we have to live with this limitation for now.
gui_log_window: False
# whether to start the Serena web dashboard, which provides access to Serena's configuration
# and state as well as the current session logs.
# The web dashboard is supported on all platforms.
# We strongly recommend to always enable this option, since the dashboard provides important information
# about the current state of Serena.
# If you don't want the browser window to pop up automatically, set `web_dashboard_open_on_launch` to False
# (see option below).
# whether to start the Serena Dashboard, which provides detailed information on your Serena session,
# the current configuration and furthermore allows some settings to be conveniently modified on the fly.
# We strongly recommend to always enable this option!
# If you want to prevent the Dashboard window from being opened on launch,
# set `web_dashboard_open_on_launch` to false (see below).
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
web_dashboard: True
# the address the web dashboard will listen on (bind address).
web_dashboard_listen_address: 127.0.0.1
# whether to open a browser window with the web dashboard when Serena starts (provided that web_dashboard
# is enabled). See also the web_dashboard option.
# If set to false, you can still open the dashboard manually by
# whether to open the Dashboard window/browser tab when Serena starts (provided that web_dashboard is enabled).
# If set to false, you can still open the dashboard manually by clicking on the Serena icon in your system
# tray on Windows and macOS. On Linux, there is no system tray support, so you can only open the dashboard by
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
# may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
# See also: https://oraios.github.io/serena/02-usage/060_dashboard.html
web_dashboard_open_on_launch: True
# the address the web dashboard will listen on (bind address).
web_dashboard_listen_address: 127.0.0.1
# address where JetBrains plugin servers are running (only relevant when using the JetBrains language backend)
jetbrains_plugin_server_address: 127.0.0.1
@@ -1013,9 +1013,18 @@ class ALLanguageServer(SolidLanguageServer):
original_name = symbol["name"]
normalized_name = self._extract_al_display_name(original_name)
# Store original name if it was normalized (for hover injection)
if symbol.get("kind") in (SymbolKind.Function, SymbolKind.Method) and "(" in normalized_name:
normalized_name = normalized_name.split("(", 1)[0].strip()
if symbol.get("kind") == SymbolKind.Method and normalized_name.lower().startswith("action "):
normalized_name = normalized_name.split(None, 1)[-1].strip()
if symbol.get("kind") == SymbolKind.Field and ":" in normalized_name:
normalized_name = normalized_name.split(":", 1)[0].strip()
# Store original name if it was normalized for an AL object declaration
# Only store if we have valid position data to avoid false matches at (0, 0)
if original_name != normalized_name:
if original_name != normalized_name and self._AL_OBJECT_NAME_PATTERN.match(original_name):
sel_range = symbol.get("selectionRange")
if sel_range:
start = sel_range.get("start") # type: ignore
@@ -1026,6 +1035,11 @@ class ALLanguageServer(SolidLanguageServer):
return normalized_name
@override
def _document_symbols_cache_fingerprint(self) -> int:
normalize_symbol_name_version = 1
return normalize_symbol_name_version
@override
def request_hover(
self, relative_file_path: str, line: int, column: int, file_buffer: LSPFileBuffer | None = None
@@ -1,9 +1,12 @@
import logging
import os
import pathlib
from collections.abc import Hashable
from typing import cast
from solidlsp.ls import SolidLanguageServer
from overrides import override
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
@@ -34,6 +37,15 @@ class DartLanguageServer(SolidLanguageServer):
config, repository_root_path, ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), "dart", solidlsp_settings
)
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
normalize_symbol_name_version = 1
return normalize_symbol_name_version
@override
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
return symbol["name"].rsplit(".", 1)[-1]
@classmethod
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
dart_settings = solidlsp_settings.get_ls_specific_settings(Language.DART)
@@ -4,11 +4,12 @@ import pathlib
import stat
import subprocess
import threading
from collections.abc import Hashable
from typing import Any, cast
from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_utils import FileUtils, PlatformId, PlatformUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
@@ -215,6 +216,15 @@ class ElixirTools(SolidLanguageServer):
# Set generous timeout for Expert which can be slow to initialize and respond
self.set_request_timeout(180.0)
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
normalize_symbol_name_version = 1
return normalize_symbol_name_version
@override
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
return symbol["name"].removeprefix("defp ").removeprefix("def ").split("(", 1)[0].strip()
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
+16 -8
View File
@@ -1,12 +1,15 @@
import hashlib
import json
import logging
import os
import pathlib
import subprocess
from collections.abc import Hashable
from typing import Any, cast
from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -165,15 +168,13 @@ class Gopls(SolidLanguageServer):
_CACHE_CONTEXT_ENV_KEYS = ("GOFLAGS", "GOOS", "GOARCH", "CGO_ENABLED")
@override
def _document_symbols_cache_fingerprint(self) -> str | None:
def _document_symbols_cache_fingerprint(self) -> Hashable:
"""
Compute a deterministic fingerprint of the Go build context.
The fingerprint includes gopls_settings and selected env vars that affect symbol discovery.
"""
import hashlib
import json
normalize_symbol_name_version = 1
gopls_settings_raw = self._custom_settings.settings.get("gopls_settings")
gopls_settings: dict | None
@@ -190,11 +191,14 @@ class Gopls(SolidLanguageServer):
if value:
env_subset[key] = value
# Return None only when BOTH settings and env subset are effectively empty.
# Version processed symbols even when the build context itself is empty.
if gopls_settings is None and not env_subset:
return None
return normalize_symbol_name_version
fingerprint_data: dict[str, object] = {"env": env_subset}
fingerprint_data: dict[str, object] = {
"env": env_subset,
"normalize_symbol_name_version": normalize_symbol_name_version,
}
if gopls_settings is not None:
fingerprint_data["gopls_settings"] = gopls_settings
@@ -202,6 +206,10 @@ class Gopls(SolidLanguageServer):
return hashlib.sha256(canonical_json.encode("utf-8")).hexdigest()[:16]
@override
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
return symbol["name"].rsplit(".", 1)[-1]
def _start_server(self) -> None:
"""Start gopls server process"""
+23 -1
View File
@@ -11,12 +11,14 @@ import os
import pathlib
import platform
import shutil
from collections.abc import Hashable
from pathlib import Path
from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import FileUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -169,6 +171,26 @@ class LuaLanguageServer(SolidLanguageServer):
)
self.request_id = 0
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
normalize_symbol_name_version = 1
return normalize_symbol_name_version
@override
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
original_name = symbol["name"]
if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method):
return original_name
if "." in original_name:
return original_name.rsplit(".", 1)[-1]
if ":" in original_name:
return original_name.rsplit(":", 1)[-1]
return original_name
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
@@ -14,12 +14,14 @@ import platform
import shutil
import tempfile
import threading
from collections.abc import Hashable
from pathlib import Path
from overrides import override
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import FileUtils
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
@@ -199,6 +201,30 @@ class PowerShellLanguageServer(SolidLanguageServer):
)
self.server_ready = threading.Event()
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
normalize_symbol_name_version = 1
return normalize_symbol_name_version
@override
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
original_name = symbol["name"]
symbol_kind = symbol.get("kind")
# normalize class declarations
if symbol_kind == SymbolKind.Class:
return original_name.removeprefix("class ").split("{", 1)[0].strip()
# normalize method signatures
if symbol_kind == SymbolKind.Method:
return original_name.split("(", 1)[0].rsplit(None, 1)[-1]
# normalize function declarations
if symbol_kind == SymbolKind.Function:
return original_name.removeprefix("function ").split("(", 1)[0].strip()
return original_name
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
+20 -1
View File
@@ -3,12 +3,14 @@ import os
import pathlib
import subprocess
import time
from collections.abc import Hashable
from overrides import override
from solidlsp import ls_types
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls import RawDocumentSymbol, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_types import SymbolKind
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings
@@ -56,6 +58,23 @@ class SourceKitLSP(SolidLanguageServer):
self._did_sleep_before_requesting_references = False
self._initialization_timestamp: float | None = None
@override
def _document_symbols_cache_fingerprint(self) -> Hashable:
normalize_symbol_name_version = 1
return normalize_symbol_name_version
@override
def _normalize_symbol_name(self, symbol: RawDocumentSymbol, relative_file_path: str) -> str:
original_name = symbol["name"]
if symbol.get("kind") not in (SymbolKind.Function, SymbolKind.Method, SymbolKind.Constructor):
return original_name
if "(" not in original_name:
return original_name
return original_name.split("(", 1)[0].strip()
@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
+1
View File
@@ -42,6 +42,7 @@ _LANGUAGE_REPO_ALIASES: dict[Language, Language] = {
Language.PHP_PHPACTOR: Language.PHP,
Language.PYTHON_JEDI: Language.PYTHON,
Language.RUBY_SOLARGRAPH: Language.RUBY,
Language.PYTHON_TY: Language.PYTHON,
}
@@ -15,6 +15,10 @@ type DemoStruct struct {
Field int
}
func (d *DemoStruct) Value() int {
return d.Field
}
func UsingHelper() {
Helper()
}
@@ -105,5 +105,11 @@ function Main {
Write-Output "Script completed successfully"
}
class PersonFormatter {
[string] FormatName([string]$Name) {
return "Formatted: $Name"
}
}
# Run main function
Main @args
+2 -2
View File
@@ -194,7 +194,7 @@ class TestSerenaAgent:
pytest.param(Language.PHP, "helperFunction", "Function", "helper.php", marks=pytest.mark.php),
pytest.param(Language.CLOJURE, "greet", "Function", clj.CORE_PATH, marks=pytest.mark.clojure),
pytest.param(Language.CSHARP, "Calculator", "Class", "Program.cs", marks=pytest.mark.csharp),
pytest.param(Language.POWERSHELL, "function Greet-User ()", "Function", "main.ps1", marks=pytest.mark.powershell),
pytest.param(Language.POWERSHELL, "Greet-User", "Function", "main.ps1", marks=pytest.mark.powershell),
pytest.param(Language.CPP_CCLS, "add", "Function", "b.cpp", marks=pytest.mark.cpp),
pytest.param(Language.LEAN4, "add", "Method", "Helper.lean", marks=pytest.mark.lean4),
],
@@ -295,7 +295,7 @@ class TestSerenaAgent:
marks=pytest.mark.clojure,
),
pytest.param(Language.CSHARP, "Calculator", "Program.cs", "Program.cs", marks=pytest.mark.csharp),
pytest.param(Language.POWERSHELL, "function Greet-User ()", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
pytest.param(Language.POWERSHELL, "Greet-User", "main.ps1", "main.ps1", marks=pytest.mark.powershell),
pytest.param(Language.CPP_CCLS, "add", "b.cpp", "a.cpp", marks=pytest.mark.cpp),
pytest.param(Language.LEAN4, "add", "Helper.lean", "Main.lean", marks=pytest.mark.lean4),
],
+7 -7
View File
@@ -5,7 +5,7 @@ import pytest
from serena.jetbrains.jetbrains_types import SymbolDTO, SymbolDTOKey
from serena.project import Project
from serena.symbol import LanguageServerSymbol, LanguageServerSymbolRetriever, NamePathComponent, NamePathMatcher
from solidlsp.ls_config import Language
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
class TestSymbolNameMatching:
@@ -235,7 +235,7 @@ class TestSymbolNameMatching:
@pytest.mark.python
class TestLanguageServerSymbolRetriever:
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_info(self, project_with_ls: Project):
symbol_retriever = LanguageServerSymbolRetriever(project_with_ls)
create_user_method_symbol = symbol_retriever.find("UserService/create_user", within_relative_path="test_repo/services.py")[0]
@@ -280,7 +280,7 @@ def _make_mock_symbols(count: int, *, relative_path: str = "test_repo/services.p
class TestHoverBudget:
"""Tests for symbol_info_budget time budget behavior."""
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_budget_not_exceeded_all_lookups_performed(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""With a large budget, all hover lookups are performed."""
# Create symbol retriever with a mock agent that has large budget
@@ -309,7 +309,7 @@ class TestHoverBudget:
assert all(info is not None for info in result.values())
assert len(result) == 3
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_budget_exceeded_partial_info(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""With a small budget, hover lookups stop and remaining symbols get None info."""
project_with_ls.serena_config.symbol_info_budget = 0.1
@@ -353,7 +353,7 @@ class TestHoverBudget:
assert result_list[3] is None
assert result_list[4] is None
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_budget_zero_means_unlimited(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""With budget=0, all hover lookups proceed (no early stopping)."""
project_with_ls.serena_config.symbol_info_budget = 0.0
@@ -380,7 +380,7 @@ class TestHoverBudget:
assert call_count == 5
assert all(info is not None for info in result.values())
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_project_budget_overrides_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""Project-level budget overrides global budget."""
# Create symbol retriever with global budget 10.0 but project budget 0.05
@@ -418,7 +418,7 @@ class TestHoverBudget:
# So 2 calls succeed (proving project budget 0.05 overrode global 10.0)
assert call_count == 2
@pytest.mark.parametrize("project_with_ls", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project_with_ls", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_project_null_inherits_global(self, project_with_ls: Project, monkeypatch: pytest.MonkeyPatch):
"""When project budget is None, global budget is used."""
# Create symbol retriever with project budget=None (inherit global)
+37
View File
@@ -6,8 +6,10 @@ from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer
from solidlsp.language_servers.al_language_server import ALLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from test.conftest import language_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
pytestmark = [pytest.mark.al, pytest.mark.skipif(not language_tests_enabled(Language.AL), reason="AL tests are disabled")]
@@ -556,3 +558,38 @@ class TestALPathNormalization:
f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
)
break
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
is_al_display_symbol = (
s["name"].endswith((".Codeunit", ".Page", ".Table", ".TableExt", ".Enum", ".Interface"))
or (s["name"].startswith('"') and s["name"].endswith('"'))
or s["name"].startswith(
(
"Enum Name ",
"Area ",
"Group ",
"Field ",
"Part ",
"SystemPart ",
"Repeater ",
"ActionRef ",
"Key ",
"FieldGroup ",
)
)
or ":" in s["name"]
)
if not is_al_display_symbol and has_malformed_name(
s,
whitespace_allowed=s["kind"] in {SymbolKind.Class, SymbolKind.Struct, SymbolKind.Interface, SymbolKind.Enum},
):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -13,6 +13,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(platform.system() == "Windows", reason="ansible-language-server does not support native Windows operation")
@@ -51,3 +52,17 @@ class TestAnsibleLanguageServerBasics:
assert len(result) > 0, "Expected non-empty completion list"
labels = [item["completionText"] for item in result if "completionText" in item]
assert labels, f"Expected completions with completionText, got: {result[:3]}"
@pytest.mark.xfail(reason="Seems like ansible LS lacks basic functionality at the moment, textDocument/documentSymbol doesn't work")
@pytest.mark.parametrize("language_server", [Language.ANSIBLE], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -9,6 +9,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.bash
@@ -119,3 +120,16 @@ class TestBashLanguageServerBasics:
# Verify total counts match expectations
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.sh, found {len(main_functions)}"
assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_functions)}"
@pytest.mark.parametrize("language_server", [Language.BASH], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+21 -1
View File
@@ -3,8 +3,9 @@ import pytest
from serena.project import Project
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import UnifiedSymbolInformation
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
from test.conftest import language_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from . import CORE_PATH, UTILS_PATH
@@ -187,6 +188,8 @@ class TestLanguageServerBasics:
assert found_relevant_references, f"Should have found calculate-area referencing multiply, but got: {result}"
@pytest.mark.skipif(not language_tests_enabled(Language.CLOJURE), reason="Clojure tests are disabled")
@pytest.mark.clojure
class TestProjectBasics:
@pytest.mark.parametrize("project", [Language.CLOJURE], indirect=True)
def test_retrieve_content_around_line(self, project: Project):
@@ -221,3 +224,20 @@ class TestProjectBasics:
assert result is not None, "Should find require statements"
utils_matches = [match for match in result if match.source_file_path and "utils.clj" in match.source_file_path]
assert len(utils_matches) > 0, "Should find require statement in utils.clj"
@pytest.mark.parametrize("language_server", [Language.CLOJURE], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
# clojure-lsp exposes namespace and dependency/container entries in addition to real vars/functions.
# Those qualified names are not the target of this regression check.
if s["kind"] in {SymbolKind.Namespace, SymbolKind.Struct}:
continue
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+56
View File
@@ -0,0 +1,56 @@
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind, UnifiedSymbolInformation
PYTHON_BACKEND_LANGUAGES = [Language.PYTHON, Language.PYTHON_TY]
def has_malformed_name(
symbol: UnifiedSymbolInformation,
whitespace_allowed: bool = False,
period_allowed: bool = False,
colon_allowed: bool = False,
brace_allowed: bool = False,
parenthesis_allowed: bool = False,
comma_allowed: bool = False,
) -> bool:
forbidden_chars: list[str] = []
if not whitespace_allowed:
forbidden_chars.append(" ")
if not period_allowed:
forbidden_chars.append(".")
if not colon_allowed:
forbidden_chars.append(":")
if not brace_allowed:
forbidden_chars.append("{")
if not parenthesis_allowed:
forbidden_chars.append("(")
if not comma_allowed:
forbidden_chars.append(",")
return any(separator in symbol["name"] for separator in forbidden_chars)
def request_all_symbols(language_server: SolidLanguageServer) -> list[UnifiedSymbolInformation]:
result: list[UnifiedSymbolInformation] = []
def visit(symbol: UnifiedSymbolInformation) -> None:
result.append(symbol)
for child in symbol.get("children", []):
visit(child)
symbols = language_server.request_full_symbol_tree()
for symbol in symbols:
visit(symbol)
return result
def format_symbol_for_assert(symbol: UnifiedSymbolInformation) -> str:
relative_path = symbol.get("location", {}).get("relativePath", "<unknown>")
try:
kind = SymbolKind(symbol["kind"]).name
except ValueError:
kind = str(symbol["kind"])
return f"{symbol['name']} [{kind}] ({relative_path})"
+14
View File
@@ -15,6 +15,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
def _ccls_available() -> bool:
@@ -142,3 +143,16 @@ int use_add() {
# Clean up the new file
if os.path.exists(new_file_abs_path):
os.remove(new_file_abs_path)
@pytest.mark.parametrize("language_server", _cpp_servers, indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+15
View File
@@ -15,8 +15,10 @@ from solidlsp.language_servers.csharp_language_server import (
find_solution_or_project_file,
)
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.csharp
@@ -359,3 +361,16 @@ class TestCSharpSolutionProjectOpening:
# Verify the file actually exists
assert os.path.exists(result)
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.Namespace):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.dart
@@ -361,3 +362,16 @@ class TestDartLanguageServer:
if main_dart_refs:
for ref in main_dart_refs:
assert "range" in ref or "location" in ref
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+28
View File
@@ -11,6 +11,8 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from . import EXPERT_UNAVAILABLE, EXPERT_UNAVAILABLE_REASON
@@ -110,3 +112,29 @@ class TestElixirBasic:
for _ in range(3):
symbols = language_server.request_document_symbols("lib/services.ex").get_all_symbols_and_roots()
assert symbols is not None
@pytest.mark.parametrize("language_server", [Language.ELIXIR], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if s["kind"] in {SymbolKind.Module, SymbolKind.Namespace, SymbolKind.Struct}:
continue
allow_test_style_name = s["name"].startswith(('test "', 'describe "'))
allow_struct_name = s["name"].startswith("%")
allow_type_name = s["name"].startswith("@type ")
if has_malformed_name(
s,
whitespace_allowed=allow_test_style_name or allow_type_name,
period_allowed=allow_struct_name,
colon_allowed=allow_test_style_name,
brace_allowed=allow_test_style_name or allow_struct_name,
comma_allowed=allow_test_style_name,
):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+17
View File
@@ -1,12 +1,16 @@
import os
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(shutil.which("elm") is None and not is_ci, reason="Elm compiler is not available")
@pytest.mark.elm
class TestElmLanguageServer:
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
@@ -52,3 +56,16 @@ class TestElmLanguageServer:
# Verify that at least one reference is in Main.elm (where formatMessage is used)
assert any("Main.elm" in ref.get("relativePath", "") for ref in refs), "Expected to find usage of formatMessage in Main.elm"
@pytest.mark.parametrize("language_server", [Language.ELM], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -9,6 +9,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
from . import ERLANG_LS_UNAVAILABLE, ERLANG_LS_UNAVAILABLE_REASON
@@ -41,3 +42,16 @@ class TestErlangLanguageServerBasics:
pytest.skip("Erlang language server not fully initialized")
else:
raise
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -11,6 +11,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Mark all tests in this module as fortran tests
pytestmark = pytest.mark.fortran
@@ -272,3 +273,16 @@ class TestFortranLanguageServer:
_refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
# refs might be empty if Point3D isn't used elsewhere, but the call should not fail
# The important thing is that it doesn't error due to wrong character position
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Currently, most F# tests fail, there seems to be a regression or instability.
@@ -183,3 +184,16 @@ class TestFSharpLanguageServer:
# This is a successful test - FsAutoComplete is working with F# files
assert True, "F# language server can handle files successfully"
@pytest.mark.parametrize("language_server", [Language.FSHARP], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+29
View File
@@ -3,9 +3,11 @@ from pathlib import Path
import pytest
from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.go
@@ -17,6 +19,20 @@ class TestGoLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "Helper"), "Helper function not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "DemoStruct"), "DemoStruct not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
def test_find_symbol_matches_go_method_by_bare_name(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree(within_relative_path="main.go")
assert SymbolUtils.symbol_tree_contains_name(symbols, "Value"), "Expected Go method name to be normalized to bare name"
assert not SymbolUtils.symbol_tree_contains_name(symbols, "(*DemoStruct).Value"), (
"Expected receiver-qualified Go method name to be normalized away"
)
bare_name_matches = [match for root in symbols for match in LanguageServerSymbol(root).find("Value")]
assert bare_name_matches, "Expected a Go method to match by bare name"
assert all(match.name == "Value" for match in bare_name_matches)
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
def test_find_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
file_path = os.path.join("main.go")
@@ -192,3 +208,16 @@ class TestGoBuildTags:
# A cache miss should repopulate and mark caches modified.
_assert_caches_modified(ls_foo)
@pytest.mark.parametrize("language_server", [Language.GO], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -8,6 +8,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language, LanguageServerConfig
from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.groovy
@@ -103,3 +104,16 @@ class TestGroovyLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "ModelUser"), "ModelUser missing from overview"
def test_bare_symbol_names(self) -> None:
assert self.language_server is not None
all_symbols = request_all_symbols(self.language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -19,6 +19,8 @@ import pytest
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.haskell
@@ -217,3 +219,24 @@ class TestHaskellLanguageServer:
assert any("Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths), (
f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
)
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if s["kind"] == SymbolKind.Module:
continue
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
diagnostics = [
{
"formatted": format_symbol_for_assert(sym),
"name": sym["name"],
"kind": SymbolKind(sym["kind"]).name,
"detail": sym.get("detail"),
}
for sym in malformed_symbols
]
pytest.fail(f"Found malformed symbols: {diagnostics}", pytrace=False)
+15
View File
@@ -12,7 +12,9 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_exceptions import SolidLSPException
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
@@ -158,3 +160,16 @@ class TestHlslHover:
hover_info = language_server.request_hover("common.hlsl", 3, 7)
assert hover_info is not None, "Hover should return information for VertexInput"
assert "contents" in hover_info, "Hover should have contents"
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.File):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -6,6 +6,7 @@ from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import language_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
pytestmark = [pytest.mark.java, pytest.mark.skipif(not language_tests_enabled(Language.JAVA), reason="Java tests disabled")]
@@ -50,3 +51,16 @@ class TestJavaLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+18
View File
@@ -1,9 +1,14 @@
import shutil
import pytest
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(shutil.which("julia") is None and not is_ci, reason="Julia is not available")
@pytest.mark.julia
class TestJuliaLanguageServer:
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
@@ -48,3 +53,16 @@ class TestJuliaLanguageServer:
reference_paths = [ref["relativePath"] for ref in references]
# The reference might be in either file (definition or usage)
assert "main.jl" in reference_paths or "lib/helper.jl" in reference_paths
@pytest.mark.parametrize("language_server", [Language.JULIA], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+15
View File
@@ -4,8 +4,10 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Kotlin LSP (IntelliJ-based, pre-alpha v261) crashes on JVM restart under CI resource constraints
@@ -55,3 +57,16 @@ class TestKotlinLanguageServer:
assert SymbolUtils.symbol_tree_contains_name(symbols, "Main"), "Main missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Utils"), "Utils missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Model"), "Model missing from overview"
@pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s, period_allowed=s["kind"] == SymbolKind.File):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+19
View File
@@ -10,6 +10,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.lua
@@ -253,3 +254,21 @@ class TestLuaLanguageServer:
# The test file should have some content that references calculator
symbol_list = test_symbols[0] if isinstance(test_symbols, tuple) else test_symbols
assert len(symbol_list) > 0, "test_calculator.lua should have symbols"
@pytest.mark.parametrize("language_server", [Language.LUA], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(
s,
whitespace_allowed=s["name"] == " ",
period_allowed="." in s["name"],
colon_allowed=":" in s["name"],
):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -9,6 +9,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.luau
@@ -151,3 +152,16 @@ class TestLuauLanguageServer:
assert definition["uri"].endswith("init.luau"), f"Definition should be in init.luau, got: {definition['uri']}"
# createConfig is defined at line 8 (0-indexed): `local function createConfig(...)`
assert definition["range"]["start"]["line"] == 8, f"Definition should be at line 8, got line {definition['range']['start']['line']}"
@pytest.mark.parametrize("language_server", [Language.LUAU], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -11,6 +11,7 @@ from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.markdown
@@ -93,3 +94,16 @@ class TestMarkdownLanguageServerBasics:
for symbol in all_symbols:
assert symbol["kind"] == SymbolKind.Namespace, f"Nested heading '{symbol['name']}' should be remapped to Namespace"
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s, whitespace_allowed=True, parenthesis_allowed=True):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -16,6 +16,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Skip all tests if MATLAB is not available
pytestmark = pytest.mark.matlab
@@ -115,3 +116,16 @@ class TestMatlabLanguageServerReferences:
# Should find references in both main.m and Calculator.m
assert references is not None
@pytest.mark.parametrize("language_server", [Language.MATLAB], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+32
View File
@@ -10,7 +10,9 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Skip all Nix tests on Windows as Nix doesn't support Windows
pytestmark = pytest.mark.skipif(platform.system() == "Windows", reason="Nix and nil are not available on Windows")
@@ -218,3 +220,33 @@ class TestNixLanguageServer:
root = symbols[0]
assert isinstance(root, dict), "Root should be a dict"
assert "name" in root, "Root should have a name"
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
# nixd can surface anonymous expression nodes using synthetic display labels
# like "(anonymous lambda)", "(dynamic string)", "{anonymous}", and
# "(dynamic attribute name)". It also emits literal value nodes such as
# strings and arrays. These do not correspond to named declarations, so
# there is no bare identifier we could or should normalize them to in the
# Nix wrapper. This test is only meant to enforce bare names for symbols
# that actually have declaration names.
if s["kind"] in {SymbolKind.String, SymbolKind.Array}:
continue
if s["name"] in {"(anonymous lambda)", "(dynamic string)", "{anonymous}", "(dynamic attribute name)"}:
continue
if s["kind"] in {SymbolKind.Field, SymbolKind.Property} and "." in s["name"]:
continue
if has_malformed_name(
s,
whitespace_allowed=s["name"] == "(anonymous lambda)",
parenthesis_allowed=s["name"] == "(anonymous lambda)",
):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+20 -2
View File
@@ -1,12 +1,16 @@
import os
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(shutil.which("opam") is None and not is_ci, reason="OPAM is not available")
@pytest.mark.ocaml
class TestOCamlLanguageServer:
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
@@ -49,8 +53,6 @@ class TestOCamlLanguageServer:
def test_reason_file_patterns(self) -> None:
"""Test that OCaml language configuration recognizes Reason file extensions"""
from solidlsp.ls_config import Language
ocaml_lang = Language.OCAML
file_matcher = ocaml_lang.get_source_fn_matcher()
@@ -150,3 +152,19 @@ class TestOCamlLanguageServer:
file_path = os.path.join("bin", "main.ml")
symbols, _roots = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
assert len(symbols) > 0, "Should find symbols in main.ml that use opened modules"
@pytest.mark.parametrize("language_server", [Language.OCAML], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
# happens for lambdas a la let () = ...
if s["name"] == "()":
continue
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+20 -1
View File
@@ -10,12 +10,15 @@ Uses genericptr/pascal-language-server which returns SymbolInformation[] format:
- Method names don't include parent class prefix; uses containerName instead
"""
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.conftest import language_tests_enabled
from test.conftest import is_ci, language_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
pytestmark = [
pytest.mark.pascal,
@@ -24,6 +27,7 @@ pytestmark = [
@pytest.mark.pascal
@pytest.mark.skipif(shutil.which("fpc") is None and not is_ci, reason="Free Pascal compiler is not available")
class TestPascalLanguageServerBasics:
"""Test basic functionality of the Pascal language server."""
@@ -193,3 +197,18 @@ class TestPascalLanguageServerBasics:
# Should contain the doc comment
assert "Calculates the sum" in value, f"Hover should include doc comment. Got: {value[:500]}"
@pytest.mark.parametrize("language_server", [Language.PASCAL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if s["kind"] == SymbolKind.Package:
continue
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -5,6 +5,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.perl
@@ -74,3 +75,16 @@ class TestPerlLanguageServer:
main_pl_lines = sorted([ref["range"]["start"]["line"] for ref in main_pl_refs])
assert 17 in main_pl_lines, f"Expected reference at line 18 (0-indexed 17), found: {main_pl_lines}"
assert 20 in main_pl_lines, f"Expected reference at line 21 (0-indexed 20), found: {main_pl_lines}"
@pytest.mark.parametrize("language_server", [Language.PERL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+23 -1
View File
@@ -1,3 +1,4 @@
import subprocess
from pathlib import Path
import pytest
@@ -5,10 +6,18 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import is_ci, is_windows, language_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
def _php_supports_phpactor() -> bool:
completed_process = subprocess.run(["php", "-m"], capture_output=True, text=True, check=False)
installed_extensions = {line.strip().lower() for line in completed_process.stdout.splitlines()}
return "openssl" in installed_extensions
_php_servers: list[Language] = [Language.PHP]
if language_tests_enabled(Language.PHP_PHPACTOR):
if not (is_windows and is_ci): # TODO: Phpactor tests are flaky in Windows CI and can even cause hangs #1040
if not (is_windows and is_ci) and _php_supports_phpactor():
_php_servers.append(Language.PHP_PHPACTOR)
@@ -256,3 +265,16 @@ class TestPhpLanguageServers:
dog_root = next((s for s in symbols if s.get("name") == "Dog"), None)
if dog_root is not None:
assert SymbolUtils.symbol_tree_contains_name([dog_root], "greet"), "greet should be nested under Dog in symbol tree"
@pytest.mark.parametrize("language_server", _php_servers, indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -9,6 +9,8 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.powershell
@@ -130,6 +132,14 @@ class TestPowerShellLanguageServerBasics:
assert len(main_functions) >= 3, f"Should find at least 3 functions in main.ps1, found {len(main_functions)}"
assert len(utils_functions) >= 8, f"Should find at least 8 functions in utils.ps1, found {len(utils_functions)}"
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
def test_powershell_class_method_symbols_use_bare_method_name(self, language_server: SolidLanguageServer) -> None:
"""Test whether PSES already reports class methods with bare names."""
symbols = language_server.request_full_symbol_tree(within_relative_path="main.ps1")
assert SymbolUtils.symbol_tree_contains_name(symbols, "PersonFormatter"), "Should find PersonFormatter class in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "FormatName"), "Expected PowerShell method to be exposed with bare name"
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
def test_powershell_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
"""Test finding references to a function within the same file."""
@@ -172,3 +182,16 @@ class TestPowerShellLanguageServerBasics:
assert any("utils.ps1" in loc.get("uri", "") for loc in definition_locations), (
f"Should find definition in utils.ps1, got {definition_locations}"
)
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s, colon_allowed=s["name"].startswith("$")):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+20 -7
View File
@@ -12,14 +12,14 @@ import pytest
from serena.project import Project
from serena.util.text_utils import LineType
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES, format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.python
class TestPythonLanguageServerBasics:
"""Test basic functionality of the language server."""
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_references_user_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_references on the User class."""
# Get references to the User class in models.py
@@ -34,7 +34,7 @@ class TestPythonLanguageServerBasics:
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert len(references) > 1, "User class should be referenced in multiple files (using selectionRange if present)"
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_references_item_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_references on the Item class."""
# Get references to the Item class in models.py
@@ -50,7 +50,7 @@ class TestPythonLanguageServerBasics:
services_references = [ref for ref in references if "services.py" in ref["uri"]]
assert len(services_references) > 0, "At least one reference should be in services.py (using selectionRange if present)"
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_references_function_parameter(self, language_server: SolidLanguageServer) -> None:
"""Test request_references on a function parameter."""
# Get references to the id parameter in get_user method
@@ -65,7 +65,7 @@ class TestPythonLanguageServerBasics:
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert len(references) > 0, "id parameter should be referenced within the method (using selectionRange if present)"
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_references_create_user_method(self, language_server: SolidLanguageServer) -> None:
# Get references to the create_user method in UserService
file_path = os.path.join("test_repo", "services.py")
@@ -81,7 +81,7 @@ class TestPythonLanguageServerBasics:
class TestProjectBasics:
@pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_retrieve_content_around_line(self, project: Project) -> None:
"""Test retrieve_content_around_line functionality with various scenarios."""
file_path = os.path.join("test_repo", "models.py")
@@ -188,7 +188,7 @@ class TestProjectBasics:
else:
assert line.match_type == LineType.AFTER_MATCH
@pytest.mark.parametrize("project", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("project", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_search_files_for_pattern(self, project: Project) -> None:
"""Test search_files_for_pattern with various patterns and glob filters."""
# Test 1: Search for class definitions across all files
@@ -233,3 +233,16 @@ class TestProjectBasics:
no_match_pattern = r"def\s+this_method_does_not_exist\s*\([^)]*\):"
matches = project.search_source_files_for_pattern(no_match_pattern)
assert len(matches) == 0
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -6,6 +6,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import start_ls_context
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
# This mark will be applied to all tests in this module
pytestmark = pytest.mark.python
@@ -19,7 +20,7 @@ def ls_with_ignored_dirs() -> Generator[SolidLanguageServer, None, None]:
yield ls
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
"""Tests that request_full_symbol_tree ignores the configured directory."""
root = ls_with_ignored_dirs.request_full_symbol_tree()[0]
@@ -28,7 +29,7 @@ def test_symbol_tree_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
assert children_names == {"test_repo", "examples"}
@pytest.mark.parametrize("ls_with_ignored_dirs", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("ls_with_ignored_dirs", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
"""Tests that find_references ignores the configured directory."""
# Location of Item, which is referenced in scripts
@@ -42,7 +43,7 @@ def test_find_references_ignores_dir(ls_with_ignored_dirs: SolidLanguageServer):
assert not any("scripts" in ref["relativePath"] for ref in references)
@pytest.mark.parametrize("repo_path", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("repo_path", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
"""Tests that refs and symbols with glob patterns are ignored."""
ignored_paths = ["*ipts", "custom_t*"]
+24 -53
View File
@@ -12,8 +12,8 @@ import pytest
from serena.symbol import LanguageServerSymbol
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import PYTHON_BACKEND_LANGUAGES
pytestmark = pytest.mark.python
@@ -21,7 +21,7 @@ pytestmark = pytest.mark.python
class TestLanguageServerSymbols:
"""Test the language server's symbol-related functionality."""
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a function."""
# Test for a position inside the create_user method
@@ -36,7 +36,7 @@ class TestLanguageServerSymbols:
if "body" in containing_symbol:
assert containing_symbol["body"].get_text().strip().startswith("def create_user(self")
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_references_to_variables(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a variable."""
file_path = os.path.join("test_repo", "variables.py")
@@ -51,7 +51,7 @@ class TestLanguageServerSymbols:
assert "dataclass_instance" in ref_names
assert "second_dataclass" in ref_names
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_containing_symbol_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a class."""
# Test for a position inside the UserService class but outside any method
@@ -64,7 +64,7 @@ class TestLanguageServerSymbols:
assert containing_symbol["name"] == "UserService"
assert containing_symbol["kind"] == SymbolKind.Class
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_containing_symbol_nested(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol with nested scopes."""
# Test for a position inside a method which is inside a class
@@ -90,7 +90,7 @@ class TestLanguageServerSymbols:
assert parent_symbol["name"] == "UserService"
assert parent_symbol["kind"] == SymbolKind.Class
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_containing_symbol_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol for a position with no containing symbol."""
# Test for a position outside any function/class (e.g., in imports)
@@ -101,7 +101,7 @@ class TestLanguageServerSymbols:
# Should return None or an empty dictionary
assert containing_symbol is None or containing_symbol == {}
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_referencing_symbols_function(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a function."""
# Test referencing symbols for create_user function
@@ -125,7 +125,7 @@ class TestLanguageServerSymbols:
assert "start" in symbol["location"]["range"]
assert "end" in symbol["location"]["range"]
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_referencing_symbols_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a class."""
# Test referencing symbols for User class
@@ -146,7 +146,7 @@ class TestLanguageServerSymbols:
]
assert len(services_references) > 0, "No referencing symbols from services.py for User (selectionRange)"
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_referencing_symbols_parameter(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a function parameter."""
# Test referencing symbols for id parameter in get_user
@@ -167,7 +167,7 @@ class TestLanguageServerSymbols:
]
assert len(method_refs) > 0, "No referencing symbols within method body for get_user (selectionRange)"
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_referencing_symbols_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_referencing_symbols for a position with no symbol."""
# For positions with no symbol, the method might throw an error or return None/empty list
@@ -185,7 +185,7 @@ class TestLanguageServerSymbols:
pass
# Tests for request_defining_symbol
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_defining_symbol_variable(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a variable usage."""
# Test finding the definition of a symbol in the create_user method
@@ -204,7 +204,7 @@ class TestLanguageServerSymbols:
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
assert "services.py" in defining_symbol["location"]["uri"]
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_defining_symbol_imported_class(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for an imported class."""
# Test finding the definition of the 'User' class used in the UserService.create_user method
@@ -216,7 +216,7 @@ class TestLanguageServerSymbols:
assert defining_symbol is not None
assert defining_symbol.get("name") == "User"
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_defining_symbol_method_call(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a method call."""
# Create an example file path for a file that calls UserService.create_user
@@ -241,7 +241,7 @@ class TestLanguageServerSymbols:
warnings.warn("Could not verify method call definition - file structure may differ from expected")
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_defining_symbol_none(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a position with no symbol."""
# Test for a position with no symbol (e.g., whitespace or comment)
@@ -252,7 +252,7 @@ class TestLanguageServerSymbols:
# Should return None for positions with no symbol
assert defining_symbol is None
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_containing_symbol_variable(self, language_server: SolidLanguageServer) -> None:
"""Test request_containing_symbol where the symbol is a variable."""
# Test for a position inside a variable definition
@@ -265,7 +265,7 @@ class TestLanguageServerSymbols:
assert containing_symbol["name"] == "user_var_str"
assert containing_symbol["kind"] == SymbolKind.Variable
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_defining_symbol_nested_function(self, language_server: SolidLanguageServer) -> None:
"""Test request_defining_symbol for a nested function or closure."""
# Use the existing nested.py file which contains nested classes and methods
@@ -290,15 +290,8 @@ class TestLanguageServerSymbols:
# Test 3: Find definition of a method-local function
defining_symbol = language_server.request_defining_symbol(file_path, 9, 15) # Position inside func_within_func
# This is challenging for many language servers and may fail
try:
assert defining_symbol is not None
assert defining_symbol.get("name") == "func_within_func"
except (AssertionError, TypeError, KeyError):
# This is expected to potentially fail in many implementations
import warnings
warnings.warn("Could not resolve nested class method definition - implementation limitation")
assert defining_symbol is not None
assert defining_symbol.get("name") == "func_within_func"
# Test 2: Find definition of the nested class
defining_symbol = language_server.request_defining_symbol(file_path, 15, 18) # Position of NestedClass
@@ -314,9 +307,9 @@ class TestLanguageServerSymbols:
# This is challenging for many language servers and may fail
assert defining_symbol is not None
assert defining_symbol.get("name") == "func_within_func"
assert defining_symbol.get("kind") == SymbolKind.Function.value
assert defining_symbol.get("kind") in [SymbolKind.Function.value, SymbolKind.Method.value]
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_symbol_methods_integration(self, language_server: SolidLanguageServer) -> None:
"""Test the integration between different symbol-related methods."""
# This test demonstrates using the various symbol methods together
@@ -365,7 +358,7 @@ class TestLanguageServerSymbols:
warnings.warn("Could not verify container hierarchy - implementation detail")
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_symbol_tree_structure(self, language_server: SolidLanguageServer) -> None:
"""Test that the symbol tree structure is correctly built."""
# Get all symbols in the test file
@@ -395,7 +388,7 @@ class TestLanguageServerSymbols:
).get_all_symbols_and_roots()
assert user_management_roots == user_management_node["children"]
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_symbol_tree_structure_subdir(self, language_server: SolidLanguageServer) -> None:
"""Test that the symbol tree structure is correctly built."""
# Get all symbols in the test file
@@ -418,7 +411,7 @@ class TestLanguageServerSymbols:
).get_all_symbols_and_roots()
assert user_management_roots == user_management_node["children"]
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
"""Test that request_dir_overview returns correct symbol information for files in a directory."""
# Get overview of the examples directory
@@ -442,7 +435,7 @@ class TestLanguageServerSymbols:
retrieved_symbols = {symbol["name"] for symbol in services_symbols if "name" in symbol}
assert expected_symbols.issubset(retrieved_symbols)
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
@pytest.mark.parametrize("language_server", PYTHON_BACKEND_LANGUAGES, indirect=True)
def test_request_document_overview(self, language_server: SolidLanguageServer) -> None:
"""Test that request_document_overview returns correct symbol information for a file."""
# Get overview of the user_management.py file
@@ -451,25 +444,3 @@ class TestLanguageServerSymbols:
# Verify that we have entries for both files
symbol_names = {LanguageServerSymbol(s_info).name for s_info in overview}
assert {"UserStats", "UserManager", "process_user_data", "main"}.issubset(symbol_names)
@pytest.mark.parametrize("language_server", [Language.PYTHON], indirect=True)
def test_containing_symbol_of_var_is_file(self, language_server: SolidLanguageServer) -> None:
"""Test that the containing symbol of a variable is the file itself."""
# Get the containing symbol of a variable in a file
file_path = os.path.join("test_repo", "services.py")
# import of typing
references_to_typing = [
ref.symbol
for ref in language_server.request_referencing_symbols(file_path, 4, 6, include_imports=False, include_file_symbols=True)
]
assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File}
# now include bodies
references_to_typing = [
ref.symbol
for ref in language_server.request_referencing_symbols(
file_path, 4, 6, include_imports=False, include_file_symbols=True, include_body=True
)
]
assert {ref["kind"] for ref in references_to_typing} == {SymbolKind.File}
assert references_to_typing[0]["body"]
+17 -2
View File
@@ -3,14 +3,18 @@ Basic tests for R Language Server integration
"""
import os
import shutil
from pathlib import Path
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(shutil.which("R") is None and not is_ci, reason="R is not available")
@pytest.mark.r
class TestRLanguageServer:
"""Test basic functionality of the R language server."""
@@ -82,8 +86,6 @@ class TestRLanguageServer:
def test_file_matching(self):
"""Test that R files are properly matched."""
from solidlsp.ls_config import Language
matcher = Language.R.get_source_fn_matcher()
assert matcher.is_relevant_filename("script.R")
@@ -95,3 +97,16 @@ class TestRLanguageServer:
"""Test R language enum value."""
assert Language.R == "r"
assert str(Language.R) == "r"
@pytest.mark.parametrize("language_server", [Language.R], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+20
View File
@@ -1,14 +1,19 @@
"""Tests for Rego language server (Regal) functionality."""
import os
import shutil
import pytest
from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from solidlsp.ls_utils import SymbolUtils
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(shutil.which("regal") is None and not is_ci, reason="Regal is not available")
@pytest.mark.rego
class TestRegoLanguageServer:
"""Test Regal language server functionality for Rego."""
@@ -132,3 +137,18 @@ class TestRegoLanguageServer:
assert "validate_user_input" in symbol_names, "validate_user_input rule not found"
assert "has_valid_credentials" in symbol_names, "has_valid_credentials function not found"
assert "validate_request" in symbol_names, "validate_request rule not found"
@pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if s["kind"] == SymbolKind.Package:
continue
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+31 -2
View File
@@ -6,6 +6,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.ruby
@@ -37,8 +38,36 @@ class TestRubyLanguageServer:
str(repo_path / "main.rb"), 16, 17
) # add method at line 17 (0-indexed 16), position 17
assert len(definition_location_list) == 1
definition_location = definition_location_list[0]
unique_locations = {
(
location["relativePath"],
location["range"]["start"]["line"],
location["range"]["start"]["character"],
location["range"]["end"]["line"],
location["range"]["end"]["character"],
): location
for location in definition_location_list
}
assert len(unique_locations) == 1
definition_location = next(iter(unique_locations.values()))
print(f"Found definition: {definition_location}")
assert definition_location["uri"].endswith("lib.rb")
assert definition_location["range"]["start"]["line"] == 1 # add method on line 2 (0-indexed 1)
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(
s,
whitespace_allowed=s["name"] == "<< self",
period_allowed=s["name"].startswith("self."),
):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -5,6 +5,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.rust
@@ -55,3 +56,16 @@ class TestRustLanguageServer:
symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "main"), "main missing from overview"
assert SymbolUtils.symbol_tree_contains_name(symbols, "add"), "add missing from overview"
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -13,6 +13,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
def _find_identifier_position(file_path: Path, symbol_name: str) -> Optional[tuple[int, int]]:
@@ -163,3 +164,16 @@ class TestSolidityLanguageServerBasics:
ref_files = {ref.get("uri", "") for ref in references}
assert any("Token.sol" in uri for uri in ref_files), "IERC20.transfer references should include Token.sol"
@pytest.mark.parametrize("language_server", [Language.SOLIDITY], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -15,6 +15,7 @@ from serena.util.text_utils import LineType
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
# Skip Swift tests on Windows due to complex GitHub Actions configuration
WINDOWS_SKIP = platform.system() == "Windows"
@@ -224,3 +225,16 @@ class TestSwiftProjectBasics:
# Should find Status enum
status_matches = [m for m in matches if "Status" in str(m)]
assert len(status_matches) > 0, "Should find Status enum"
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -5,6 +5,7 @@ This module tests Language.SYSTEMVERILOG using verible-verilog-ls.
Tests are skipped if the language server is not available.
"""
import shutil
from typing import Any
import pytest
@@ -12,6 +13,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
@@ -262,3 +264,17 @@ class TestSystemVerilogRename:
for uri, file_edits in changes.items():
for edit in file_edits:
assert edit["newText"] == "my_counter", f"Expected 'my_counter', got {edit['newText']}"
@pytest.mark.skipif(shutil.which("verible-verilog-ls") is None, reason="verible-verilog-ls is not available")
@pytest.mark.parametrize("language_server", [Language.SYSTEMVERILOG], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -5,12 +5,18 @@ These tests validate the functionality of the language server APIs
like request_references using the test repository.
"""
import shutil
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.conftest import is_ci
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.skipif(shutil.which("terraform") is None and not is_ci, reason="Terraform CLI is not available")
@pytest.mark.terraform
class TestLanguageServerBasics:
"""Test basic functionality of the Terraform language server."""
@@ -51,3 +57,18 @@ class TestLanguageServerBasics:
sel_start = var_symbol["selectionRange"]["start"]
references = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert len(references) >= 1, "variable should be referenced at least once"
@pytest.mark.parametrize("language_server", [Language.TERRAFORM], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if s["kind"] == SymbolKind.Class:
continue
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -11,6 +11,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.toml
@@ -233,3 +234,16 @@ class TestTomlLanguageServerBasics:
strict_symbol = next((s for s in pyproject_symbols if s.get("name") == "strict"), None)
if strict_symbol:
assert strict_symbol.get("kind") == 17, "'strict' should have kind 17 (boolean)"
@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
@@ -5,6 +5,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.typescript
@@ -31,3 +32,16 @@ class TestTypescriptLanguageServer:
assert any("index.ts" in ref.get("relativePath", "") for ref in refs), (
"index.ts should reference helperFunction (tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.TYPESCRIPT], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+23
View File
@@ -5,6 +5,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.vue
@@ -360,3 +361,25 @@ class TestVueEdgeCases:
f"Composable '{expected_composable}' should appear exactly once in directory overview. "
f"Found {len(matching_files)} matches. All files: {list(composables_overview.keys())}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
is_callback = s["name"].endswith(" callback")
is_vue_selector = "." in s["name"] or ":" in s["name"]
is_vue_block = s["name"] in {"script setup", "style scoped"}
if has_malformed_name(
s,
whitespace_allowed=is_callback or is_vue_selector or is_vue_block,
period_allowed=is_vue_selector,
colon_allowed=is_vue_selector,
parenthesis_allowed=is_callback or is_vue_selector,
):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -11,6 +11,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.yaml
@@ -175,3 +176,16 @@ class TestYAMLLanguageServerBasics:
app_port = next((s for s in port_symbols if s["range"]["start"]["line"] == 4), None)
assert app_port is not None, "Should find 'port' under 'app'"
assert app_port["range"]["start"]["character"] == 2, "'port' should be indented 2 spaces"
@pytest.mark.parametrize("language_server", [Language.YAML], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
+14
View File
@@ -13,6 +13,7 @@ import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_types import SymbolKind
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
@pytest.mark.zig
@@ -341,3 +342,16 @@ class TestZigLanguageServer:
root = symbols[0]
assert isinstance(root, dict), "Root should be a dict"
assert "name" in root, "Root should have a name"
@pytest.mark.parametrize("language_server", [Language.ZIG], indirect=True)
def test_bare_symbol_names(self, language_server) -> None:
all_symbols = request_all_symbols(language_server)
malformed_symbols = []
for s in all_symbols:
if has_malformed_name(s, whitespace_allowed=True):
malformed_symbols.append(s)
if malformed_symbols:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(sym) for sym in malformed_symbols]}",
pytrace=False,
)
Generated
+290 -4
View File
@@ -216,6 +216,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
]
[[package]]
name = "bottle"
version = "0.13.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/71/cca6167c06d00c81375fd668719df245864076d284f7cb46a694cbeb5454/bottle-0.13.4.tar.gz", hash = "sha256:787e78327e12b227938de02248333d788cfe45987edca735f8f88e03472c3f47", size = 98717, upload-time = "2025-06-15T10:08:59.439Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/f6/b55ec74cfe68c6584163faa311503c20b0da4c09883a41e8e00d6726c954/bottle-0.13.4-py2.py3-none-any.whl", hash = "sha256:045684fbd2764eac9cdeb824861d1551d113e8b683d8d26e296898d3dd99a12e", size = 103807, upload-time = "2025-06-15T10:08:57.691Z" },
]
[[package]]
name = "cachecontrol"
version = "0.14.4"
@@ -414,6 +423,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "clr-loader"
version = "0.2.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/18/24/c12faf3f61614b3131b5c98d3bf0d376b49c7feaa73edca559aeb2aee080/clr_loader-0.2.10.tar.gz", hash = "sha256:81f114afbc5005bafc5efe5af1341d400e22137e275b042a8979f3feb9fc9446", size = 83605, upload-time = "2026-01-03T23:13:06.984Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/61/cf819f8e8bb4d4c74661acf2498ba8d4a296714be3478d21eaabf64f5b9b/clr_loader-0.2.10-py3-none-any.whl", hash = "sha256:ebbbf9d511a7fe95fa28a95a4e04cd195b097881dfe66158dc2c281d3536f282", size = 56483, upload-time = "2026-01-03T23:13:05.439Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
@@ -1730,6 +1751,93 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
]
[[package]]
name = "pillow"
version = "12.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" },
{ url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" },
{ url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" },
{ url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" },
{ url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" },
{ url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" },
{ url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" },
{ url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" },
{ url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" },
{ url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" },
{ url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" },
{ url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
{ url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
{ url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
{ url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
{ url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
{ url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
{ url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
{ url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
{ url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
{ url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
{ url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
{ url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
{ url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
{ url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
{ url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
{ url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
{ url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
{ url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
{ url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
{ url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
{ url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
{ url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
{ url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
{ url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
{ url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
{ url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
{ url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
{ url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
{ url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
{ url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
{ url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
{ url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
{ url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
{ url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
{ url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
{ url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" },
{ url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" },
{ url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" },
{ url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" },
{ url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" },
{ url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" },
{ url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" },
]
[[package]]
name = "platformdirs"
version = "4.9.4"
@@ -1773,6 +1881,12 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
]
[[package]]
name = "proxy-tools"
version = "0.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd03895caca7857ef51e4c409e0ca6b37ee6e9f7daa50b642/proxy_tools-0.1.0.tar.gz", hash = "sha256:ccb3751f529c047e2d8a58440d86b205303cf0fe8146f784d1cbcd94f0a28010", size = 2978, upload-time = "2014-05-05T21:02:24.606Z" }
[[package]]
name = "psutil"
version = "7.0.0"
@@ -2065,11 +2179,11 @@ wheels = [
[[package]]
name = "pyjwt"
version = "2.10.1"
version = "2.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a8/10/e8192be5f38f3e8e7e046716de4cae33d56fd5ae08927a823bb916be36c1/pyjwt-2.12.0.tar.gz", hash = "sha256:2f62390b667cd8257de560b850bb5a883102a388829274147f1d724453f8fb02", size = 102511, upload-time = "2026-03-12T17:15:30.831Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
{ url = "https://files.pythonhosted.org/packages/15/70/70f895f404d363d291dcf62c12c85fdd47619ad9674ac0f53364d035925a/pyjwt-2.12.0-py3-none-any.whl", hash = "sha256:9bb459d1bdd0387967d287f5656bf7ec2b9a26645d1961628cda1764e087fd6e", size = 29700, upload-time = "2026-03-12T17:15:29.257Z" },
]
[package.optional-dependencies]
@@ -2077,6 +2191,104 @@ crypto = [
{ name = "cryptography" },
]
[[package]]
name = "pyobjc-core"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" },
{ url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" },
{ url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" },
{ url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" },
{ url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" },
{ url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" },
]
[[package]]
name = "pyobjc-framework-cocoa"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" },
{ url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" },
{ url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" },
{ url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" },
{ url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" },
{ url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" },
]
[[package]]
name = "pyobjc-framework-quartz"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" },
{ url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" },
{ url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" },
{ url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" },
{ url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" },
{ url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" },
]
[[package]]
name = "pyobjc-framework-security"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" },
{ url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" },
{ url = "https://files.pythonhosted.org/packages/32/48/b294ed75247c5cfa00d51925a10237337d24f54961d49a179b20a4307642/pyobjc_framework_security-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afc36661cc6eb98cd794bed1d6668791e96557d6f72d9ac70aa49022d26af1d4", size = 41284, upload-time = "2025-11-14T10:03:01.722Z" },
{ url = "https://files.pythonhosted.org/packages/ef/57/0d3ef78779cf5c3bba878b2f824137e50978ad4a21dabe65d8b5ae0fc0d1/pyobjc_framework_security-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9510c98ab56921d1d416437372605cc1c1f6c1ad8d3061ee56b17bf423dd5427", size = 42162, upload-time = "2025-11-14T10:03:05.337Z" },
{ url = "https://files.pythonhosted.org/packages/66/4d/63c15f9449c191e7448a05ff8af4a82c39a51bb627bc96dc9697586c0f79/pyobjc_framework_security-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6319a34508fd87ab6ca3cda6f54e707196197a65b792b292705af967e225438a", size = 41348, upload-time = "2025-11-14T10:03:08.926Z" },
{ url = "https://files.pythonhosted.org/packages/1a/d8/5aaa2a8124ed04a9d6ca7053dc0fa64e42be51497ed8263a24b744a95598/pyobjc_framework_security-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:03d166371cefdef24908825148eb848f99ee2c0b865870a09dcbb94334dd3e0a", size = 42908, upload-time = "2025-11-14T10:03:13.01Z" },
]
[[package]]
name = "pyobjc-framework-uniformtypeidentifiers"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" },
]
[[package]]
name = "pyobjc-framework-webkit"
version = "12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyobjc-core" },
{ name = "pyobjc-framework-cocoa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" },
{ url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" },
{ url = "https://files.pythonhosted.org/packages/7a/3d/80d36280164c69220ce99372f7736a028617c207e42cb587716009eecb88/pyobjc_framework_webkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da0c428c9d9891c93e0de51c9f272bfeb96d34356cdf3136cb4ad56ce32ec2d", size = 50096, upload-time = "2025-11-14T10:07:10.027Z" },
{ url = "https://files.pythonhosted.org/packages/8a/7a/03c29c46866e266b0c705811c55c22625c349b0a80f5cf4776454b13dc4c/pyobjc_framework_webkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a29e334d5a7dd4a4f0b5647481b6ccf8a107b92e67b2b3c6b368c899f571965", size = 50572, upload-time = "2025-11-14T10:07:14.232Z" },
{ url = "https://files.pythonhosted.org/packages/3b/ac/924878f239c167ffe3bfc643aee4d6dd5b357e25f6b28db227e40e9e6df3/pyobjc_framework_webkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:99d0d28542a266a95ee2585f51765c0331794bca461aaf4d1f5091489d475179", size = 50210, upload-time = "2025-11-14T10:07:18.926Z" },
{ url = "https://files.pythonhosted.org/packages/2d/86/637cda4983dc0936b73a385f3906256953ac434537b812814cb0b6d231a2/pyobjc_framework_webkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1aaa3bf12c7b68e1a36c0b294d2728e06f2cc220775e6dc4541d5046290e4dc8", size = 50680, upload-time = "2025-11-14T10:07:23.331Z" },
]
[[package]]
name = "pyright"
version = "1.1.403"
@@ -2090,6 +2302,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/49/b6/b04e5c2f41a5ccad74a1a4759da41adb20b4bc9d59a5e08d29ba60084d07/pyright-1.1.403-py3-none-any.whl", hash = "sha256:c0eeca5aa76cbef3fcc271259bbd785753c7ad7bcac99a9162b4c4c7daed23b3", size = 5684504, upload-time = "2025-07-09T07:15:50.958Z" },
]
[[package]]
name = "pystray"
version = "0.19.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pillow" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
{ name = "python-xlib", marker = "sys_platform == 'linux'" },
{ name = "six" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/5c/64/927a4b9024196a4799eba0180e0ca31568426f258a4a5c90f87a97f51d28/pystray-0.19.5-py2.py3-none-any.whl", hash = "sha256:a0c2229d02cf87207297c22d86ffc57c86c227517b038c0d3c59df79295ac617", size = 49068, upload-time = "2023-09-17T13:44:26.872Z" },
]
[[package]]
name = "pytest"
version = "8.4.1"
@@ -2161,6 +2387,48 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
]
[[package]]
name = "python-xlib"
version = "0.33"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "six" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" },
]
[[package]]
name = "pythonnet"
version = "3.1.0rc0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "clr-loader" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a2/59/4295652a9a93a2bd6755ecce2dc32e3d3c704e7b8ef301946ee65c818a59/pythonnet-3.1.0rc0.tar.gz", hash = "sha256:5f0f505e100233d70864b00dc5f5dd760603cfd94ac64da5c8b0e15ed95af939", size = 241025, upload-time = "2026-02-25T10:46:53.632Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/3d/a378aa74085fc6f72923d5e39fc30efbc97541cfd49b3fe0a35f20d09ff7/pythonnet-3.1.0rc0-py3-none-any.whl", hash = "sha256:286ccf23d08e1cc2b5200bb91d0ee649436d2e8386fec36a85193c66d0ff8b5b", size = 293150, upload-time = "2026-02-25T10:46:48.535Z" },
]
[[package]]
name = "pywebview"
version = "6.2.dev50+g345596c1f"
source = { git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe#345596c1f4fd59fd26c7aa02d4176c5362bcbabe" }
dependencies = [
{ name = "bottle" },
{ name = "proxy-tools" },
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin'" },
{ name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" },
{ name = "pythonnet", marker = "sys_platform == 'win32'" },
{ name = "qtpy", marker = "sys_platform == 'openbsd6'" },
{ name = "typing-extensions" },
]
[[package]]
name = "pywin32"
version = "311"
@@ -2273,6 +2541,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
]
[[package]]
name = "qtpy"
version = "2.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" },
]
[[package]]
name = "referencing"
version = "0.37.0"
@@ -2645,8 +2925,11 @@ dependencies = [
{ name = "psutil" },
{ name = "pydantic" },
{ name = "pyright" },
{ name = "pystray" },
{ name = "python-dotenv" },
{ name = "python-multipart" },
{ name = "pythonnet", marker = "sys_platform == 'win32'" },
{ name = "pywebview" },
{ name = "pyyaml" },
{ name = "regex" },
{ name = "requests" },
@@ -2720,13 +3003,16 @@ requires-dist = [
{ name = "pyasn1", marker = "extra == 'dev'", specifier = "==0.6.3" },
{ name = "pydantic", specifier = "==2.12.5" },
{ name = "pyinstrument", marker = "extra == 'dev'", specifier = "==5.1.1" },
{ name = "pyjwt", marker = "extra == 'dev'", specifier = "==2.10.1" },
{ name = "pyjwt", marker = "extra == 'dev'", specifier = "==2.12.0" },
{ name = "pyright", specifier = "==1.1.403" },
{ name = "pystray", specifier = "==0.19.5" },
{ name = "pytest", marker = "extra == 'dev'", specifier = "==8.4.1" },
{ name = "pytest-timeout", marker = "extra == 'dev'", specifier = "==2.4.0" },
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = "==3.8.0" },
{ name = "python-dotenv", specifier = "==1.2.1" },
{ name = "python-multipart", specifier = "==0.0.22" },
{ name = "pythonnet", marker = "sys_platform == 'win32'", specifier = "==3.1.0rc0" },
{ name = "pywebview", git = "https://github.com/r0x0r/pywebview?rev=345596c1f4fd59fd26c7aa02d4176c5362bcbabe" },
{ name = "pyyaml", specifier = "==6.0.2" },
{ name = "regex", specifier = "==2026.2.28" },
{ name = "requests", specifier = "==2.33.0" },