Compare commits

...
18 Commits
Author SHA1 Message Date
Esa Kataja e3e2bcb260 Rename xml parser functions and remove unused dependencies 2025-04-07 16:37:41 +03:00
Esa Kataja d602cbb897 Move sanitize_name to lib.py 2025-04-07 16:36:54 +03:00
Esa Kataja 6baf0f12ab Remove unused datetime import 2025-04-07 16:25:53 +03:00
Esa Kataja 43fe0869c2 Change gitignore to reflect new structure 2025-04-07 16:17:55 +03:00
Esa Kataja 5f7f496620 Refactor codebase to use a savefile and fetch data online 2025-04-07 16:16:15 +03:00
Esa Kataja f5b26d75bc Merge remote-tracking branch 'origin/main' into Onlinesync 2025-04-07 09:41:27 +03:00
Esa Kataja 5d3fd7fb35 Separate output psm file to blocks 2025-03-21 14:53:28 +02:00
Esa Kataja c897b1d7f9 Change psm template 2025-03-20 11:02:32 +02:00
Esa Kataja 4cd4b2a3e4 Merge branch main into Onlinesync 2025-03-20 09:29:41 +02:00
Esa Kataja 2034353958 Fix xml docstring 2025-03-20 09:24:32 +02:00
Esa Kataja 6fad3cfe76 Change gitignores 2025-03-20 09:19:50 +02:00
Esa Kataja a6bd04a302 Add model files 2025-03-19 15:32:04 +02:00
Esa Kataja 0b5334b097 Add httpx and pydantic 2025-03-19 15:29:50 +02:00
Esa Kataja c3224a7ed7 Fix psm output encoding to utf-8-bom 2025-03-17 12:16:41 +02:00
Esa Kataja b7f54b8873 Fix More typos in XML parser 2025-03-17 12:13:37 +02:00
Esa Kataja 71c78fc7fe Fix Alot of typos in xml and psm generation 2025-03-14 14:15:57 +02:00
Esa Kataja 472b7d9df7 Fix typos in xml generation 2025-03-14 13:06:04 +02:00
Esa Kataja 9ed4fbb7ef Change default psm output filename 2025-03-14 13:00:35 +02:00
11 changed files with 391 additions and 153 deletions
+4 -4
View File
@@ -10,7 +10,7 @@ wheels/
.venv .venv
# Project specific # Project specific
*.json *.bak
*.psm1 src/output/*
*.xml originals/*
*.bak .vscode
+2
View File
@@ -6,8 +6,10 @@ readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"click>=8.1.8", "click>=8.1.8",
"httpx>=0.28.1",
"loguru>=0.7.3", "loguru>=0.7.3",
"lxml>=5.3.1", "lxml>=5.3.1",
"pydantic>=2.10.6",
] ]
[dependency-groups] [dependency-groups]
-42
View File
@@ -1,42 +0,0 @@
import json
from pathlib import Path
import click
def _get_fields(json_data: dict) -> list[str]:
"""
Extract field names from a JSON data structure.
Args:
json_data (dict): The JSON data from which to extract field names.
Returns:
list[str]: A list of field names extracted from the JSON data.
"""
namelist = []
for attr in json_data["conceptCodes"]:
for name in attr["attributes"]:
if name["attributeName"] not in namelist:
namelist.append(name["attributeName"])
return namelist
def parse_json(json_file: Path) -> list[str]:
"""
Parse a JSON file and extract field names.
Args:
json_file (Path): The path to the JSON file to parse.
Returns:
list[str]: A list of field names extracted from the JSON file.
Raises:
click.FileError: If the file is not a valid JSON file.
"""
try:
with open(json_file, "r") as f:
json_data = json.load(f)
except json.JSONDecodeError:
raise click.FileError(json_file, "Not a valid JSON file.")
return _get_fields(json_data)
+9
View File
@@ -0,0 +1,9 @@
def sanitize_name(name: str) -> str:
"""
Sanitize a name by removing all non-letter characters except hyphens and Scandinavian letters.
"""
# Replace Scandinavian letters
name = name.replace('ä', 'a').replace('ö', 'o')
name = name.replace('Ä', 'A').replace('Ö', 'O')
# Remove all non-letter characters except hyphens
return ''.join(c for c in name if c.isalpha() or c == '-')
+113 -14
View File
@@ -1,23 +1,122 @@
import click import click
from pathlib import Path from pathlib import Path
import json
from datetime import datetime
from pydantic import HttpUrl
import httpx
from urllib.parse import urlparse, urlencode
from json_parser import parse_json from models.savefile import SaveFile, CodeItem
from psm_parser import write_psm from psm_parser import write_psm
from xml_parser import get_xml_data, add_connection, write_xml_data from xml_parser import write_xml_data
def _strip_params(url: HttpUrl) -> str:
parsed_url = urlparse(str(url))
return f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
@click.command def get_attributes(url: str) -> list[str]:
@click.option('-j', '--json', type=Path, required=True, help='Path to the JSON file') """
@click.option('-x', '--xml', type=Path, required=True, help='Path to the XML file. If the file does not exist, a default configuration will be generated.') Get attributes from a URL.
@click.option('-n', '--name', type=str, required=True, help='Name of the connection')
@click.option('-d', '--description', type=str, required=True, help='Description of the connection') Args:
@click.option('-p', '--pretty', is_flag=True, default=False, help='Enable pretty output') url (str): The URL to get attributes from.
def main(json: Path, xml: Path, name: str, description: str, pretty: bool):
xml_data = get_xml_data(xml) Returns:
field_names = parse_json(json) list[str]: A list of attributes.
root = add_connection(xml_data, name, field_names, description=description) """
write_xml_data(root, xml, pretty=pretty) query_params = {"pageSize": 500}
write_psm(name, field_names)
new_url = f"{url}?{urlencode(query_params, doseq=True)}"
response = httpx.get(new_url)
if response.status_code != 200:
click.echo(f"Failed to get attributes from {url}")
return []
conseptCodes = response.json()["conceptCodes"]
retval = []
for attr in conseptCodes:
for name in attr["attributes"]:
if name["attributeName"] not in retval:
retval.append(name["attributeName"])
return retval
def write_json(json_file: Path, data: SaveFile):
with open(json_file, "w") as f:
f.write(data.model_dump_json())
@click.group
def main():
click.echo("Varha CodeServer Connector")
pass
@main.command
@click.argument('json_file', type=Path, required=True)
@click.option('--url', '-u', type=str, required=True, help="URL of the connection")
@click.option('--name', '-n', type=str, required=True, help="Name of the connection")
def add(json_file: Path, url: str, name: str):
"""
Add a new connection to the JSON file.
Args:
json_file (Path): The path to the JSON file.
url (str): The URL of the connection.
name (str): The name of the connection.
"""
server_data = SaveFile()
if json_file.exists():
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
# Check if name already exists
existing_names = [code.name for code in server_data.conceptCodes]
if name in existing_names:
click.echo(f"Name {name} already exists.")
return
# Get attributes from the URL
attributes = get_attributes(_strip_params(url))
server_data.conceptCodes.append(CodeItem(name=name, url=_strip_params(url), attributes=attributes))
server_data.updated_at = datetime.now()
write_json(json_file, server_data)
@main.command
@click.argument('json_file', type=Path, required=True)
def listUrls(json_file: Path):
#TODO: Format the output
if not json_file.exists():
click.echo(f"File {json_file} does not exist.")
return
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
click.echo(server_data.model_dump_json(indent=4))
@main.command
@click.argument('json_file', type=Path, required=True)
@click.option('--name', '-n', type=str, required=True, help="Name of the connection")
def remove(json_file: Path, name: str):
if not json_file.exists():
click.echo(f"File {json_file} does not exist.")
return
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
server_data.conceptCodes = [code for code in server_data.conceptCodes if code.name != name]
server_data.updated_at = datetime.now()
write_json(json_file, server_data)
@main.command
@click.argument('json_file', type=Path, required=True)
def write_output(json_file: Path):
if not json_file.exists():
click.echo(f"File {json_file} does not exist.")
return
server_data = None
with open(json_file, "r") as f:
server_data = SaveFile(**json.load(f))
write_psm(server_data)
write_xml_data(server_data)
if __name__ == "__main__": if __name__ == "__main__":
+33
View File
@@ -0,0 +1,33 @@
from datetime import datetime
from pydantic import BaseModel, HttpUrl
from enum import Enum
class LinkType(Enum):
get = "GET"
post = "POST"
put = "PUT"
delete = "DELETE"
class Attributes(BaseModel):
attributeName: str
attributeValue: list[str]
class Links(BaseModel):
href: HttpUrl
rel: str
type: LinkType
class CodeServerItem(BaseModel):
attributes: list[Attributes]
beginDate: datetime
classificationId: str
classificationName: str
conceptCodeId: str
createDate: datetime
expirationDate: datetime
lastModifiedBy: str
lastModifiedDate: datetime
links: list[Links]
status: str
versionId: str
versionName: str
+12
View File
@@ -0,0 +1,12 @@
from pydantic import BaseModel, HttpUrl, Field
from datetime import datetime
class DataLocation(BaseModel):
href: HttpUrl
name: str
description: str = "Default description"
class LocationList(BaseModel):
location: DataLocation
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
+14
View File
@@ -0,0 +1,14 @@
from pydantic import BaseModel, Field
from datetime import datetime
class CodeItem(BaseModel):
name: str
url: str
attributes: list[str]
checked_at: datetime = Field(default_factory=datetime.now)
class SaveFile(BaseModel):
version: int = 1
conceptCodes: list[CodeItem] = Field(default_factory=list)
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
+26 -43
View File
@@ -1,8 +1,7 @@
import shutil
from pathlib import Path from pathlib import Path
from datetime import datetime
from xml_parser import sanitize_name from lib import sanitize_name
from models.savefile import SaveFile
get_all_code = """ get_all_code = """
function Get-AllCodeServerPages!NAME! { function Get-AllCodeServerPages!NAME! {
@@ -10,17 +9,12 @@ function Get-AllCodeServerPages!NAME! {
param ( param (
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$Resource, [string]$Resource,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$RootProperty, [string]$RootProperty,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$CacheKeyProperty = "conceptCodeId", [string]$CacheKeyProperty = "conceptCodeId",
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[ScriptBlock]$Converter = ${function:ConvertFrom-CodeServerObject!NAME!}, [ScriptBlock]$Converter = ${function:ConvertFrom-CodeServerObject!NAME!},
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[int]$PageSize = 500 [int]$PageSize = 500
) )
@@ -32,7 +26,6 @@ function Get-AllCodeServerPages!NAME! {
$cacheKey = $Resource.GetHashCode() $cacheKey = $Resource.GetHashCode()
$cache[$cacheKey] = @{} $cache[$cacheKey] = @{}
} }
process { process {
$currentPage = 0; $currentPage = 0;
$Global:debugOut = @() $Global:debugOut = @()
@@ -56,16 +49,12 @@ function Get-AllCodeServerPages!NAME! {
$Global:cache[$cacheKey].Add($key, $codeObject.attributes) $Global:cache[$cacheKey].Add($key, $codeObject.attributes)
} }
} }
} }
} }
write-output $cache[$cacheKey].Values
write-output $cache[$cacheKey].Values
} }
end { end {
} }
} }
""" """
@@ -76,42 +65,33 @@ function Get-CodeServerObject!NAME! {
param ( param (
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$conceptCodeId, [string]$conceptCodeId,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$Resource, [string]$Resource,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$RootProperty, [string]$RootProperty,
[Parameter(Mandatory=$false)] [Parameter(Mandatory=$false)]
[string]$CacheKeyProperty = "conceptCodeId" [string]$CacheKeyProperty = "conceptCodeId"
) )
begin { begin {
if ($null -eq $Global:cache) { if ($null -eq $Global:cache) {
$params = @{ $params = @{
Resource = $Resource; Resource = $Resource;
RootProperty = $RootProperty; RootProperty = $RootProperty;
} }
if ($PSBoundParameters.ContainsKey("CacheKeyProperty")) { if ($PSBoundParameters.ContainsKey("CacheKeyProperty")) {
$params.Add("CacheKeyProperty", $CacheKeyProperty) $params.Add("CacheKeyProperty", $CacheKeyProperty)
} }
[void](Get-AllCodeServerPages!NAME! @params)
[void](Get-AllCodeServerPagesRoles @params)
} }
$cacheKey = $Resource.GetHashCode() $cacheKey = $Resource.GetHashCode()
} }
process { process {
Write-Debug $cacheKey Write-Debug $cacheKey
Write-Output $cache[$cacheKey][$conceptCodeId] Write-Output $cache[$cacheKey][$conceptCodeId]
} }
end { end {
} }
} }
""" """
@@ -119,6 +99,7 @@ function Get-CodeServerObject!NAME! {
convert_from = """ convert_from = """
Function ConvertFrom-CodeServerObject!NAME!([PSCustomObject[]]$codeServerObject!NAME!) { Function ConvertFrom-CodeServerObject!NAME!([PSCustomObject[]]$codeServerObject!NAME!) {
$map = @{ $map = @{
"conceptCodeId" = "conceptCodeId";
!MAPS! !MAPS!
} }
@@ -145,35 +126,37 @@ class CodeServer!NAME! {
!VARIABLES! !VARIABLES!
} }
[string]${conceptCodeId}
!STRINGS! !STRINGS!
} }
Export-ModuleMember ConvertFrom-CodeServerObject!NAME! Export-ModuleMember ConvertFrom-CodeServerObject!NAME!, Get-AllCodeServerPages!NAME!,Get-CodeServerObject!NAME!
Export-AllCodeServerPages!NAME!
Export-CodeServerObject!NAME!
""" """
def define_maps(items: list[str]) -> str: def define_maps(items: list[str]) -> str:
return "\n".join([f"\t'{sanitize_name(item)}' = '{item}'" for item in items]) return "\n".join([f"\t\"{sanitize_name(item)}\" = \"{item}\";" for item in items])
def define_variables(items: list[str]) -> str: def define_variables(items: list[str]) -> str:
return "\n".join([f"\t$this.{{{sanitize_name(item)}}} = $dic['{item}']" for item in items]) return "\n".join([f"\t$this.{{{sanitize_name(item)}}} = $dic[\"{item}\"]" for item in items])
def define_strings(items: list[str]) -> str: def define_strings(items: list[str]) -> str:
return "\n".join(["\t" + "[string]$" + "{" + sanitize_name(item) + "}" for item in items]) return "\n".join(["\t" + "[string]$" + "{" + sanitize_name(item) + "}" for item in items])
def write_psm(name, items: list[str], filename: Path = Path("Connector.psm1")) -> str: def write_psm(savefile: SaveFile) -> None:
if filename.exists():
backup_filename = str(filename) + f".{datetime.now().strftime('%Y-%m-%d.%H%M%S')}.bak"
shutil.copy2(filename, backup_filename)
output = get_all_code.replace("!NAME!", name) filename = Path("output/CodeServerConnector.psm1")
output += get_code.replace("!NAME!", name) if not filename.parent.exists():
output += convert_from.replace("!NAME!", name).replace("!MAPS!", define_maps(items)) filename.parent.mkdir(parents=True, exist_ok=True)
output += class_definition.replace("!NAME!", name).replace("!VARIABLES!", define_variables(items)).replace("!STRINGS!", define_strings(items))
with open(filename, "a") as f: with open(filename, "w", encoding="utf-8-sig") as f:
f.write(output) f.write("# ------- BEGIN CODE BLOCK -------\n")
pass for code in savefile.conceptCodes:
f.write(get_all_code.replace("!NAME!", code.name))
f.write(get_code.replace("!NAME!", code.name))
f.write(convert_from.replace("!NAME!", code.name).replace("!MAPS!", define_maps(code.attributes)))
f.write(class_definition.replace("!NAME!", code.name).replace("!VARIABLES!", define_variables(code.attributes)).replace("!STRINGS!", define_strings(code.attributes)))
f.write("# ------- END CODE BLOCK -------\n")
return
+25 -50
View File
@@ -1,28 +1,21 @@
import lxml.etree as ET import lxml.etree as ET
from pathlib import Path from pathlib import Path
import click
from datetime import datetime
def write_xml_data(root: ET.Element, file_path: Path, pretty: bool = True) -> None: from models.savefile import SaveFile
if file_path.exists(): from lib import sanitize_name
file_path.rename(str(file_path) + f".{datetime.now().strftime('%Y-%m-%d.%H%M%S')}.bak")
xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=pretty)
if pretty:
# Parse the XML string to ensure proper indentation
parser = ET.XMLParser(remove_blank_text=True, strip_cdata=False)
root = ET.fromstring(xml_str, parser=parser)
xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=pretty)
file_path.write_bytes(xml_str)
def sanitize_name(name: str) -> str: def write_xml_data(savefile: SaveFile) -> None:
""" filename = Path("output/CodeServerConnector.xml")
Sanitize a name by removing all non-letter characters except hyphens and Scandinavian letters. if not filename.parent.exists():
""" filename.parent.mkdir(parents=True, exist_ok=True)
# Replace Scandinavian letters
name = name.replace('ä', 'a').replace('ö', 'o') root = _generate_xml_root()
name = name.replace('Ä', 'A').replace('Ö', 'O') for code in savefile.conceptCodes:
# Remove all non-letter characters except hyphens root = add_connection(root, code.name, code.attributes)
return ''.join(c for c in name if c.isalpha() or c == '-')
xml_str = ET.tostring(root, encoding="utf-8", xml_declaration=True, pretty_print=True).decode("utf-8-sig")
with open(filename, "w", encoding="utf-8-sig") as f:
f.write(xml_str)
def add_connection(root: ET.Element, name: str, attr_list: list[str], description: str = "Default description") -> ET.Element: def add_connection(root: ET.Element, name: str, attr_list: list[str], description: str = "Default description") -> ET.Element:
""" """
@@ -31,6 +24,7 @@ def add_connection(root: ET.Element, name: str, attr_list: list[str], descriptio
Args: Args:
root (ET.Element): The root element of the XML configuration. root (ET.Element): The root element of the XML configuration.
name (str): The name of the connection parameter. name (str): The name of the connection parameter.
attr_list (list[str]): List of attributes for the connection parameter.
description (str, optional): The description of the connection parameter. Defaults to "Default description". description (str, optional): The description of the connection parameter. Defaults to "Default description".
Returns: Returns:
@@ -46,8 +40,8 @@ def add_connection(root: ET.Element, name: str, attr_list: list[str], descriptio
predefined_commands = initialization.find("PredefinedCommands") predefined_commands = initialization.find("PredefinedCommands")
if predefined_commands is None: if predefined_commands is None:
predefined_commands = ET.SubElement(initialization, "PredefinedCommands") predefined_commands = ET.SubElement(initialization, "PredefinedCommands")
ET.SubElement(predefined_commands, "Command", Name=f"Get-AllCodeServerPages{name}", Description=f"Get all pages for {name}") ET.SubElement(predefined_commands, "Command", Name=f"Get-AllCodeServerPages{name}")
ET.SubElement(predefined_commands, "Command", Name=f"Get-CodeServerPage{name}", Description=f"Get a specific page for {name}") ET.SubElement(predefined_commands, "Command", Name=f"Get-CodeServerObject{name}")
schema = root.find("Schema") schema = root.find("Schema")
if schema is not None: if schema is not None:
@@ -55,29 +49,29 @@ def add_connection(root: ET.Element, name: str, attr_list: list[str], descriptio
new_class = ET.SubElement(schema, "Class", Name=f"CodeServerObject{name}") new_class = ET.SubElement(schema, "Class", Name=f"CodeServerObject{name}")
properties = ET.SubElement(new_class, "Properties") properties = ET.SubElement(new_class, "Properties")
property = ET.SubElement(properties, "Property", Name="conceptCodeId", DataType="String", IsMultivalue="false", IsAutoFill="false", IsUniqueKey="true") property = ET.SubElement(properties, "Property", Name="conceptCodeId", DataType="String", IsMultivalue="false", IsAutoFill="false", IsUniqueKey="true")
return_binding = ET.SubElement(property, "ReturnBinding") return_binding = ET.SubElement(property, "ReturnBindings")
ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-AllCodeServerPages{name}", Path="conceptCodeId") ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-AllCodeServerPages{name}", Path="conceptCodeId")
ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-CodeServerPage{name}", Path="conceptCodeId") ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-CodeServerObject{name}", Path="conceptCodeId")
mapping = ET.SubElement(property, "CommandMappings") mapping = ET.SubElement(property, "CommandMappings")
ET.SubElement(mapping, "Map", ToCommand=f"Get-CodeServerPages{name}", Parameter="conceptCodeId") ET.SubElement(mapping, "Map", ToCommand=f"Get-CodeServerObject{name}", Parameter="conceptCodeId")
for attr in attr_list: for attr in attr_list:
property = ET.SubElement(properties, "Property", Name=sanitize_name(attr), DataType="String", IsMultivalue="false", IsAutoFill="false") property = ET.SubElement(properties, "Property", Name=sanitize_name(attr), DataType="String", IsMultivalue="false", IsAutoFill="false")
return_binding = ET.SubElement(property, "ReturnBinding") return_binding = ET.SubElement(property, "ReturnBindings")
ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-AllCodeServerPages{name}", Path=sanitize_name(attr)) ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-AllCodeServerPages{name}", Path=sanitize_name(attr))
ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-CodeServerPage{name}", Path=sanitize_name(attr)) ET.SubElement(return_binding, "Bind", CommandResultOf=f"Get-CodeServerObject{name}", Path=sanitize_name(attr))
read_configuration = ET.SubElement(new_class, "ReadConfiguration") read_configuration = ET.SubElement(new_class, "ReadConfiguration")
listing_command = ET.SubElement(read_configuration, "ListingCommand", Command=f"Get-AllCodeServerPages{name}") listing_command = ET.SubElement(read_configuration, "ListingCommand", Command=f"Get-AllCodeServerPages{name}")
ET.SubElement(listing_command, "SetParameter", Param="Resource", Source="ConnectionParameter", Value=f"VarhaUrl{name}") ET.SubElement(listing_command, "SetParameter", Param="Resource", Source="ConnectionParameter", Value=f"VarhaUrl{name}")
ET.SubElement(listing_command, "SetParameter", Param="RootProperty", Source="FixedValue", Value="conceptCodes") ET.SubElement(listing_command, "SetParameter", Param="RootProperty", Source="FixedValue", Value="conceptCodes")
cmd_squence = ET.SubElement(read_configuration, "CommandSequence") cmd_squence = ET.SubElement(read_configuration, "CommandSequence")
item = ET.SubElement(cmd_squence, "Item", Command=f"Get-CodeServerPage{name}", Order="1") item = ET.SubElement(cmd_squence, "Item", Command=f"Get-CodeServerObject{name}", Order="1")
ET.SubElement(item, "SetParameter", Param="Resource", Source="ConnectionParameter", Value=f"VarhaUrl{name}") ET.SubElement(item, "SetParameter", Param="Resource", Source="ConnectionParameter", Value=f"VarhaUrl{name}")
ET.SubElement(item, "SetParameter", Param="RootProperty", Source="FixedValue", Value="conceptCodes") ET.SubElement(item, "SetParameter", Param="RootProperty", Source="FixedValue", Value="conceptCodes")
return root return root
def _generate_xml_configuration() -> ET.Element: def _generate_xml_root() -> ET.Element:
""" """
Generate a default XML configuration for a PowerShell connector. Generate a default XML configuration for a PowerShell connector.
@@ -100,23 +94,4 @@ def _generate_xml_configuration() -> ET.Element:
ET.SubElement(command_sequence, "Item", Command="Initialize-CodeServer", Order="1") ET.SubElement(command_sequence, "Item", Command="Initialize-CodeServer", Order="1")
ET.SubElement(environment_initialization, "Disconnect") ET.SubElement(environment_initialization, "Disconnect")
ET.SubElement(root, "Schema") ET.SubElement(root, "Schema")
return root return root
def get_xml_data(xml_file: Path) -> ET.Element:
"""
Get XML data from a file. If the file does not exist, generate a default configuration.
Args:
xml_file (Path): The path to the XML file.
Returns:
ET.Element: The root element of the XML configuration.
"""
if not xml_file.exists():
return _generate_xml_configuration()
try:
parser = ET.XMLParser(strip_cdata=False)
tree = ET.parse(xml_file, parser=parser)
return tree
except ET.XMLSyntaxError as e:
raise click.FileError(xml_file, f"Not a valid XML file. {e}")
Generated
+153
View File
@@ -2,6 +2,38 @@ version = 1
revision = 1 revision = 1
requires-python = ">=3.12" requires-python = ">=3.12"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
]
[[package]]
name = "anyio"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
]
[[package]]
name = "certifi"
version = "2025.1.31"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.8" version = "8.1.8"
@@ -23,14 +55,62 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
] ]
[[package]]
name = "h11"
version = "0.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
]
[[package]]
name = "httpcore"
version = "1.0.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
]
[[package]] [[package]]
name = "int" name = "int"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "click" }, { name = "click" },
{ name = "httpx" },
{ name = "loguru" }, { name = "loguru" },
{ name = "lxml" }, { name = "lxml" },
{ name = "pydantic" },
] ]
[package.dev-dependencies] [package.dev-dependencies]
@@ -41,8 +121,10 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "click", specifier = ">=8.1.8" }, { name = "click", specifier = ">=8.1.8" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "loguru", specifier = ">=0.7.3" }, { name = "loguru", specifier = ">=0.7.3" },
{ name = "lxml", specifier = ">=5.3.1" }, { name = "lxml", specifier = ">=5.3.1" },
{ name = "pydantic", specifier = ">=2.10.6" },
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
@@ -124,6 +206,59 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
] ]
[[package]]
name = "pydantic"
version = "2.10.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696 },
]
[[package]]
name = "pydantic-core"
version = "2.27.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
{ url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 },
{ url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 },
{ url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 },
{ url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 },
{ url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 },
{ url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 },
{ url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 },
{ url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 },
{ url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 },
{ url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 },
{ url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 },
]
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.19.1" version = "2.19.1"
@@ -146,6 +281,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 }, { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
] ]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
]
[[package]] [[package]]
name = "win32-setctime" name = "win32-setctime"
version = "1.2.0" version = "1.2.0"