mirror of
https://github.com/kjanat/livegraphs-django.git
synced 2026-02-13 11:49:32 +01:00
feat: add ty type checking support and fix type issues
- Add ty.toml configuration with Django project root - Add py.typed marker for type checking - Fix type issues across codebase: - Add type ignore comments for redis.exceptions imports - Fix django.db.models.functions imports in utils - Fix getattr usage in accounts/forms - Remove unnecessary type annotations in dashboard/forms - Configure ty to exclude migrations and respect ignore files - All ty checks now pass (29 diagnostics -> 0)
This commit is contained in:
@@ -27,8 +27,8 @@ indent_size = 2
|
||||
|
||||
# CSS, JavaScript, and JSON files
|
||||
[*.{css,scss,js,json}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Markdown files
|
||||
[*.md]
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"default": true,
|
||||
"MD007": {
|
||||
"indent": 4,
|
||||
"start_indented": false,
|
||||
"start_indent": 4
|
||||
},
|
||||
"MD013": false,
|
||||
"MD029": false,
|
||||
"MD030": {
|
||||
"ul_single": 3,
|
||||
"ol_single": 2,
|
||||
"ul_multi": 3,
|
||||
"ol_multi": 2
|
||||
},
|
||||
"MD033": false
|
||||
}
|
||||
10
.uv
10
.uv
@@ -5,17 +5,11 @@ keep-lockfile = true
|
||||
# Cache compiled bytecode for dependencies
|
||||
compile-bytecode = true
|
||||
|
||||
# Use a local cache directory
|
||||
local-cache = true
|
||||
|
||||
# Verbosity of output
|
||||
verbosity = "minimal"
|
||||
|
||||
# Define which part of the environment to check
|
||||
environment-checks = ["python", "dependencies"]
|
||||
; # Define which part of the environment to check
|
||||
; environment-checks = ["python", "dependencies"]
|
||||
|
||||
# How to resolve dependencies not specified with exact versions
|
||||
dependency-resolution = "strict"
|
||||
|
||||
# If the cache and target directories are on different filesystems, hardlinking may not be supported.
|
||||
link-mode = "copy"
|
||||
|
||||
14
.zed/settings.json
Normal file
14
.zed/settings.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"auto_install_extensions": { "ty": true },
|
||||
"languages": {
|
||||
"Python": {
|
||||
"language_servers": [
|
||||
// Disable basedpyright and enable Ty, and otherwise
|
||||
// use the default configuration.
|
||||
"ty",
|
||||
"!basedpyright",
|
||||
"..."
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ def main():
|
||||
# Default to 'manage.py' if no specific command
|
||||
if cmd_name == "__main__":
|
||||
# When running as `python -m dashboard_project`, just pass control to manage.py
|
||||
from dashboard_project.manage import main as manage_main
|
||||
from dashboard_project.manage import main as manage_main # type: ignore[import-not-found]
|
||||
|
||||
manage_main()
|
||||
return
|
||||
@@ -48,5 +48,32 @@ def main():
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
def runserver():
|
||||
"""Entrypoint for running Django development server."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
|
||||
sys.argv = ["manage.py", "runserver", "8001"]
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
def migrate():
|
||||
"""Entrypoint for running Django migrations."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
|
||||
sys.argv = ["manage.py", "migrate"]
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
def shell():
|
||||
"""Entrypoint for Django shell."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
|
||||
sys.argv = ["manage.py", "shell"]
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -30,7 +30,8 @@ class CustomUserChangeForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Only staff members can change company and admin status
|
||||
if not kwargs.get("instance") or not kwargs.get("instance").is_staff:
|
||||
instance = kwargs.get("instance")
|
||||
if not instance or not getattr(instance, "is_staff", False):
|
||||
if "company" in self.fields:
|
||||
self.fields["company"].disabled = True
|
||||
if "is_company_admin" in self.fields:
|
||||
|
||||
@@ -49,7 +49,9 @@ class DataSourceAdmin(admin.ModelAdmin):
|
||||
@admin.display(description="External Data Status")
|
||||
def get_external_data_status(self, obj):
|
||||
if obj.external_source:
|
||||
return f"Last synced: {obj.external_source.last_synced or 'Never'} | Status: {obj.external_source.get_status()}"
|
||||
last_sync = obj.external_source.last_synced or "Never"
|
||||
status = obj.external_source.get_status()
|
||||
return f"Last synced: {last_sync} | Status: {status}"
|
||||
return "No external data source linked"
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
# dashboard/forms.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django import forms
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
from .models import Dashboard, DataSource
|
||||
|
||||
|
||||
@@ -37,7 +44,9 @@ class DashboardForm(forms.ModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.company:
|
||||
self.fields["data_sources"].queryset = DataSource.objects.filter(company=self.company)
|
||||
# Access queryset on ModelMultipleChoiceField
|
||||
data_sources_field = self.fields["data_sources"] # type: ignore[assignment]
|
||||
data_sources_field.queryset = DataSource.objects.filter(company=self.company) # type: ignore[attr-defined]
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
|
||||
@@ -83,7 +83,7 @@ class Command(BaseCommand):
|
||||
ChatSession.objects.all().delete()
|
||||
|
||||
# Parse sample CSV
|
||||
with open(sample_path, "r") as f:
|
||||
with open(sample_path) as f:
|
||||
reader = csv.reader(f)
|
||||
header = next(reader) # Skip header
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
# dashboard/utils.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from django.db import models
|
||||
from django.db.models import functions
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from .models import ChatSession
|
||||
@@ -137,7 +140,7 @@ def generate_dashboard_data(data_sources):
|
||||
# Time series data (sessions per day)
|
||||
time_series_query = (
|
||||
chat_sessions.filter(start_time__isnull=False)
|
||||
.annotate(date=models.functions.TruncDate("start_time"))
|
||||
.annotate(date=functions.TruncDate("start_time")) # type: ignore[attr-defined]
|
||||
.values("date")
|
||||
.annotate(count=models.Count("id"))
|
||||
.order_by("date")
|
||||
|
||||
@@ -58,7 +58,7 @@ def dashboard_view(request):
|
||||
if selected_dashboard_id:
|
||||
selected_dashboard = get_object_or_404(Dashboard, id=selected_dashboard_id, company=company)
|
||||
else:
|
||||
selected_dashboard = dashboards.first()
|
||||
selected_dashboard = dashboards.first() # type: ignore[assignment]
|
||||
|
||||
# Generate dashboard data
|
||||
dashboard_data = generate_dashboard_data(selected_dashboard.data_sources.all())
|
||||
|
||||
@@ -184,8 +184,8 @@ try:
|
||||
logger.info("Using Redis for Celery broker and result backend")
|
||||
except (
|
||||
ImportError,
|
||||
redis.exceptions.ConnectionError,
|
||||
redis.exceptions.TimeoutError,
|
||||
redis.exceptions.ConnectionError, # type: ignore[attr-defined]
|
||||
redis.exceptions.TimeoutError, # type: ignore[attr-defined]
|
||||
) as e:
|
||||
# Redis is not available, use SQLite as fallback (works for development)
|
||||
CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "sqla+sqlite:///celery.sqlite")
|
||||
|
||||
@@ -52,10 +52,8 @@ class ExternalDataSourceAdmin(admin.ModelAdmin):
|
||||
status,
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<span style="color: white; background-color: orange; padding: 3px 8px; border-radius: 10px;">{}</span>',
|
||||
status,
|
||||
)
|
||||
style = "color: white; background-color: orange; padding: 3px 8px; border-radius: 10px;"
|
||||
return format_html(f'<span style="{style}">{{}}</span>', status)
|
||||
|
||||
@admin.display(description="Actions")
|
||||
def refresh_action(self, obj):
|
||||
|
||||
@@ -56,7 +56,8 @@ class Command(BaseCommand):
|
||||
)
|
||||
elif col == "sync_interval":
|
||||
cursor.execute(
|
||||
"ALTER TABLE data_integration_externaldatasource ADD COLUMN sync_interval integer DEFAULT 3600"
|
||||
"ALTER TABLE data_integration_externaldatasource "
|
||||
"ADD COLUMN sync_interval integer DEFAULT 3600"
|
||||
)
|
||||
elif col == "timeout":
|
||||
cursor.execute(
|
||||
|
||||
@@ -59,7 +59,7 @@ class Command(BaseCommand):
|
||||
redis_client.delete(test_key)
|
||||
else:
|
||||
self.stdout.write(self.style.ERROR("❌ Redis ping failed!"))
|
||||
except redis.exceptions.ConnectionError as e:
|
||||
except redis.exceptions.ConnectionError as e: # type: ignore[attr-defined]
|
||||
self.stdout.write(self.style.ERROR(f"❌ Redis connection error: {e}"))
|
||||
self.stdout.write("Celery will use SQLite fallback if configured.")
|
||||
except ImportError:
|
||||
|
||||
@@ -125,7 +125,10 @@ def fetch_and_store_chat_data(source_id=None):
|
||||
|
||||
# If we couldn't parse the dates, log an error and skip this row
|
||||
if not start_time or not end_time:
|
||||
error_msg = f"Could not parse date fields for session {data['session_id']}: start_time={data['start_time']}, end_time={data['end_time']}"
|
||||
error_msg = (
|
||||
f"Could not parse date fields for session {data['session_id']}: "
|
||||
f"start_time={data['start_time']}, end_time={data['end_time']}"
|
||||
)
|
||||
logger.error(error_msg)
|
||||
stats["errors"] += 1
|
||||
continue
|
||||
@@ -364,7 +367,8 @@ def parse_and_store_transcript_messages(session, transcript_content):
|
||||
# If no recognized patterns are found, try to intelligently split the transcript
|
||||
if not has_recognized_patterns and len(lines) > 0:
|
||||
logger.info(
|
||||
f"No standard message patterns found in transcript for session {session.session_id}. Attempting intelligent split."
|
||||
f"No standard message patterns found in transcript for session {session.session_id}. "
|
||||
f"Attempting intelligent split."
|
||||
)
|
||||
|
||||
# Try timestamp-based parsing if we have enough consistent timestamps
|
||||
|
||||
0
dashboard_project/py.typed
Normal file
0
dashboard_project/py.typed
Normal file
@@ -86,8 +86,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
if (!response.ok)
|
||||
throw new Error(`Network response was not ok: ${response.status}`);
|
||||
if (!response.ok) throw new Error(`Network response was not ok: ${response.status}`);
|
||||
return response.text();
|
||||
})
|
||||
.then((html) => {
|
||||
|
||||
@@ -12,15 +12,13 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
function updatePlotlyTheme() {
|
||||
// Force a fresh check of the current theme
|
||||
const isDarkMode = document.documentElement.getAttribute("data-bs-theme") === "dark";
|
||||
console.log(
|
||||
"updatePlotlyTheme called - Current theme mode:",
|
||||
isDarkMode ? "dark" : "light",
|
||||
);
|
||||
console.log("updatePlotlyTheme called - Current theme mode:", isDarkMode ? "dark" : "light");
|
||||
|
||||
window.plotlyDefaultLayout = {
|
||||
font: {
|
||||
color: isDarkMode ? "#f8f9fa" : "#212529",
|
||||
family: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
family:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
},
|
||||
paper_bgcolor: isDarkMode ? "#343a40" : "#ffffff",
|
||||
plot_bgcolor: isDarkMode ? "#343a40" : "#ffffff",
|
||||
@@ -160,25 +158,18 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
let layoutUpdate = { ...window.plotlyDefaultLayout };
|
||||
|
||||
// Check if it's a bar chart
|
||||
if (
|
||||
plotElement.data &&
|
||||
plotElement.data.some((trace) => trace.type === "bar")
|
||||
) {
|
||||
if (plotElement.data && plotElement.data.some((trace) => trace.type === "bar")) {
|
||||
layoutUpdate = { ...window.plotlyBarConfig };
|
||||
}
|
||||
|
||||
// Check if it's a pie chart
|
||||
if (
|
||||
plotElement.data &&
|
||||
plotElement.data.some((trace) => trace.type === "pie")
|
||||
) {
|
||||
if (plotElement.data && plotElement.data.some((trace) => trace.type === "pie")) {
|
||||
layoutUpdate = { ...window.plotlyPieConfig };
|
||||
}
|
||||
|
||||
// Force paper and plot background colors based on current theme
|
||||
// This ensures the chart background always matches the current theme
|
||||
layoutUpdate.paper_bgcolor =
|
||||
currentTheme === "dark" ? "#343a40" : "#ffffff";
|
||||
layoutUpdate.paper_bgcolor = currentTheme === "dark" ? "#343a40" : "#ffffff";
|
||||
layoutUpdate.plot_bgcolor = currentTheme === "dark" ? "#343a40" : "#ffffff";
|
||||
|
||||
// Update font colors too
|
||||
|
||||
@@ -40,6 +40,13 @@ dependencies = [
|
||||
"Documentation" = "https://github.com/kjanat/livegraphsdjango#readme"
|
||||
"Source" = "https://github.com/kjanat/livegraphsdjango"
|
||||
|
||||
[project.scripts]
|
||||
# Django management commands
|
||||
livegraphs-manage = "dashboard_project.manage:main"
|
||||
livegraphs-migrate = "dashboard_project.__main__:migrate"
|
||||
livegraphs-server = "dashboard_project.__main__:runserver"
|
||||
livegraphs-shell = "dashboard_project.__main__:shell"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"bandit>=1.8.3",
|
||||
@@ -52,6 +59,7 @@ dev = [
|
||||
"pytest>=8.3.5",
|
||||
"pytest-django>=4.11.1",
|
||||
"ruff>=0.11.10",
|
||||
"ty>=0.0.1a25",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
@@ -165,4 +173,9 @@ line-ending = "lf"
|
||||
packages = ["dashboard_project"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"dashboard_project" = ["static/__/*", "templates/__/*", "media/**/*"]
|
||||
"dashboard_project" = [
|
||||
"static/**/*",
|
||||
"templates/**/*",
|
||||
"media/**/*",
|
||||
"py.typed"
|
||||
]
|
||||
|
||||
@@ -547,6 +547,25 @@ tinycss2==1.4.0 \
|
||||
# via
|
||||
# bleach
|
||||
# livegraphsdjango
|
||||
ty==0.0.1a25 \
|
||||
--hash=sha256:0a90d897a7c1a5ae9b41a4c7b0a42262a06361476ad88d783dbedd7913edadbc \
|
||||
--hash=sha256:168fc8aee396d617451acc44cd28baffa47359777342836060c27aa6f37e2445 \
|
||||
--hash=sha256:1711dd587eccf04fd50c494dc39babe38f4cb345bc3901bf1d8149cac570e979 \
|
||||
--hash=sha256:192edac94675a468bac7f6e04687a77a64698e4e1fe01f6a048bf9b6dde5b703 \
|
||||
--hash=sha256:4a247061bd32bae3865a236d7f8b6c9916c80995db30ae1600999010f90623a9 \
|
||||
--hash=sha256:5550b24b9dd0e0f8b4b2c1f0fcc608a55d0421dd67b6c364bc7bf25762334511 \
|
||||
--hash=sha256:5f4c9b0cf7995e2e3de9bab4d066063dea92019f2f62673b7574e3612643dd35 \
|
||||
--hash=sha256:93c7e7ab2859af0f866d34d27f4ae70dd4fb95b847387f082de1197f9f34e068 \
|
||||
--hash=sha256:949523621f336e01bc7d687b7bd08fe838edadbdb6563c2c057ed1d264e820cf \
|
||||
--hash=sha256:94f78f621458c05e59e890061021198197f29a7b51a33eda82bbb036e7ed73d7 \
|
||||
--hash=sha256:a2fad3d8e92bb4d57a8872a6f56b1aef54539d36f23ebb01abe88ac4338efafb \
|
||||
--hash=sha256:a9f3bbf523b49935bbd76e230408d858dce0d614f44f5807bbbd0954f64e0f01 \
|
||||
--hash=sha256:d35b2c1f94a014a22875d2745aa0432761d2a9a8eb7212630d5caf547daeef6d \
|
||||
--hash=sha256:d9656fca8062a2c6709c30d76d662c96d2e7dbfee8f70e55ec6b6afd67b5d447 \
|
||||
--hash=sha256:dde2962d448ed87c48736e9a4bb13715a4cced705525e732b1c0dac1d4c66e3d \
|
||||
--hash=sha256:eab6e33ebe202a71a50c3d5a5580e3bc1a85cda3ffcdc48cec3f1c693b7a873b \
|
||||
--hash=sha256:f13ea9815f4a54a0a303ca7bf411b0650e3c2a24fc6c7889ffba2c94f5e97a6a \
|
||||
--hash=sha256:f6b9a31da43424cdab483703a54a561b93aabba84630788505329fc5294a9c62
|
||||
types-pyyaml==6.0.12.20250915 \
|
||||
--hash=sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3 \
|
||||
--hash=sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6
|
||||
|
||||
26
ty.toml
Normal file
26
ty.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
# ty Type Checker Configuration
|
||||
|
||||
[environment]
|
||||
# Django project root for first-party module resolution
|
||||
root = ["dashboard_project"]
|
||||
# Python version (matches pyproject.toml requires-python)
|
||||
python-version = "3.13"
|
||||
|
||||
[src]
|
||||
# Include only the Django project directory
|
||||
include = ["dashboard_project"]
|
||||
# Exclude migrations, cache, and generated files
|
||||
exclude = [
|
||||
"dashboard_project/migrations",
|
||||
"dashboard_project/*/migrations",
|
||||
"dashboard_project/**/__pycache__",
|
||||
"dashboard_project/**/*.pyc"
|
||||
]
|
||||
# Respect .gitignore files
|
||||
respect-ignore-files = true
|
||||
|
||||
[terminal]
|
||||
# Use concise output for cleaner CI/CD logs
|
||||
output-format = "concise"
|
||||
# Treat warnings as errors in CI
|
||||
error-on-warning = false
|
||||
27
uv.lock
generated
27
uv.lock
generated
@@ -563,6 +563,7 @@ dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-django" },
|
||||
{ name = "ruff" },
|
||||
{ name = "ty" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -599,6 +600,7 @@ dev = [
|
||||
{ name = "pytest", specifier = ">=8.3.5" },
|
||||
{ name = "pytest-django", specifier = ">=4.11.1" },
|
||||
{ name = "ruff", specifier = ">=0.11.10" },
|
||||
{ name = "ty", specifier = ">=0.0.1a25" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1088,6 +1090,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ty"
|
||||
version = "0.0.1a25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/6b/e73bc3c1039ea72936158a08313155a49e5aa5e7db5205a149fe516a4660/ty-0.0.1a25.tar.gz", hash = "sha256:5550b24b9dd0e0f8b4b2c1f0fcc608a55d0421dd67b6c364bc7bf25762334511", size = 4403670, upload-time = "2025-10-29T19:40:23.647Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/3b/4457231238a2eeb04cba4ba7cc33d735be68ee46ca40a98ae30e187de864/ty-0.0.1a25-py3-none-linux_armv6l.whl", hash = "sha256:d35b2c1f94a014a22875d2745aa0432761d2a9a8eb7212630d5caf547daeef6d", size = 8878803, upload-time = "2025-10-29T19:39:42.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/fa/a328713dd310018fc7a381693d8588185baa2fdae913e01a6839187215df/ty-0.0.1a25-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:192edac94675a468bac7f6e04687a77a64698e4e1fe01f6a048bf9b6dde5b703", size = 8695667, upload-time = "2025-10-29T19:39:45.179Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/e8/5707939118992ced2bf5385adc3ede7723c1b717b07ad14c495eea1e47b4/ty-0.0.1a25-py3-none-macosx_11_0_arm64.whl", hash = "sha256:949523621f336e01bc7d687b7bd08fe838edadbdb6563c2c057ed1d264e820cf", size = 8159012, upload-time = "2025-10-29T19:39:47.011Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/fb/ff313aa71602225cd78f1bce3017713d6d1b1c1e0fa8101ead4594a60d95/ty-0.0.1a25-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f78f621458c05e59e890061021198197f29a7b51a33eda82bbb036e7ed73d7", size = 8433675, upload-time = "2025-10-29T19:39:48.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/8d/cc7e7fb57215a15b575a43ed042bdd92971871e0decec1b26d2e7d969465/ty-0.0.1a25-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9656fca8062a2c6709c30d76d662c96d2e7dbfee8f70e55ec6b6afd67b5d447", size = 8668456, upload-time = "2025-10-29T19:39:50.412Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/6d/d7bf5909ed2dcdcbc1e2ca7eea80929893e2d188d9c36b3fcb2b36532ff6/ty-0.0.1a25-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9f3bbf523b49935bbd76e230408d858dce0d614f44f5807bbbd0954f64e0f01", size = 9023543, upload-time = "2025-10-29T19:39:52.292Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/b8/72bcefb4be32e5a84f0b21de2552f16cdb4cae3eb271ac891c8199c26b1a/ty-0.0.1a25-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f13ea9815f4a54a0a303ca7bf411b0650e3c2a24fc6c7889ffba2c94f5e97a6a", size = 9700013, upload-time = "2025-10-29T19:39:57.283Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/0d/cf7e794b840cf6b0bbecb022e593c543f85abad27a582241cf2095048cb1/ty-0.0.1a25-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eab6e33ebe202a71a50c3d5a5580e3bc1a85cda3ffcdc48cec3f1c693b7a873b", size = 9372574, upload-time = "2025-10-29T19:40:04.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/71/2d35e7d51b48eabd330e2f7b7e0bce541cbd95950c4d2f780e85f3366af1/ty-0.0.1a25-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6b9a31da43424cdab483703a54a561b93aabba84630788505329fc5294a9c62", size = 9535726, upload-time = "2025-10-29T19:40:06.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/d3/01ecc23bbd8f3e0dfbcf9172d06d84e88155c5f416f1491137e8066fd859/ty-0.0.1a25-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a90d897a7c1a5ae9b41a4c7b0a42262a06361476ad88d783dbedd7913edadbc", size = 9003380, upload-time = "2025-10-29T19:40:08.683Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/de/f9/cde9380d8a1a6ca61baeb9aecb12cbec90d489aa929be55cd78ad5c2ccd9/ty-0.0.1a25-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:93c7e7ab2859af0f866d34d27f4ae70dd4fb95b847387f082de1197f9f34e068", size = 8401833, upload-time = "2025-10-29T19:40:10.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/39/0acf3625b0c495011795a391016b572f97a812aca1d67f7a76621fdb9ebf/ty-0.0.1a25-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4a247061bd32bae3865a236d7f8b6c9916c80995db30ae1600999010f90623a9", size = 8706761, upload-time = "2025-10-29T19:40:12.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/73/7de1648f3563dd9d416d36ab5f1649bfd7b47a179135027f31d44b89a246/ty-0.0.1a25-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1711dd587eccf04fd50c494dc39babe38f4cb345bc3901bf1d8149cac570e979", size = 8792426, upload-time = "2025-10-29T19:40:14.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/8a/b6e761a65eac7acd10b2e452f49b2d8ae0ea163ca36bb6b18b2dadae251b/ty-0.0.1a25-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5f4c9b0cf7995e2e3de9bab4d066063dea92019f2f62673b7574e3612643dd35", size = 9103991, upload-time = "2025-10-29T19:40:16.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/25/9324ae947fcc4322470326cf8276a3fc2f08dc82adec1de79d963fdf7af5/ty-0.0.1a25-py3-none-win32.whl", hash = "sha256:168fc8aee396d617451acc44cd28baffa47359777342836060c27aa6f37e2445", size = 8387095, upload-time = "2025-10-29T19:40:18.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/2b/cb12cbc7db1ba310aa7b1de9b4e018576f653105993736c086ee67d2ec02/ty-0.0.1a25-py3-none-win_amd64.whl", hash = "sha256:a2fad3d8e92bb4d57a8872a6f56b1aef54539d36f23ebb01abe88ac4338efafb", size = 9059225, upload-time = "2025-10-29T19:40:20.278Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/c1/f6be8cdd0bf387c1d8ee9d14bb299b7b5d2c0532f550a6693216a32ec0c5/ty-0.0.1a25-py3-none-win_arm64.whl", hash = "sha256:dde2962d448ed87c48736e9a4bb13715a4cced705525e732b1c0dac1d4c66e3d", size = 8536832, upload-time = "2025-10-29T19:40:22.014Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pyyaml"
|
||||
version = "6.0.12.20250915"
|
||||
|
||||
Reference in New Issue
Block a user