diff --git a/eval/main.go b/eval/main.go index 7d8cadb..85eaa89 100644 --- a/eval/main.go +++ b/eval/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "time" @@ -79,7 +80,9 @@ func runEval(browser playwright.Browser, eval *evalConfigYaml) []evalResult { continue } - locatrOutput, err := playWrightLocatr.GetLocatr(step.UserRequest) + ctx := context.Background() + + locatrOutput, err := playWrightLocatr.GetLocatr(ctx, step.UserRequest) if err != nil { logger.Logger.Error("Error getting locator for step", "stepName", step.Name, "error", err) results = append(results, evalResult{ @@ -88,7 +91,7 @@ func runEval(browser playwright.Browser, eval *evalConfigYaml) []evalResult { Passed: false, GeneratedLocatrs: nil, ExpectedLocatrs: step.ExpectedLocatrs, - Error: err.Error(), + Error: fmt.Sprintf("%v", err), }) continue } diff --git a/eval/utils.go b/eval/utils.go index e8c3fec..6bdde2a 100644 --- a/eval/utils.go +++ b/eval/utils.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/csv" "fmt" "os" @@ -31,6 +32,7 @@ func getAllYamlFiles(folder string) ([]string, error) { "folder", folder, "fileCount", len(res)) return res, nil } + func contains(locatrs []string, loc string) bool { for _, l := range locatrs { if l == loc { @@ -48,6 +50,7 @@ func compareSlices(yamlLocatrs []string, locatrs []string) bool { } return false } + func readYamlFile(filePath string) (*evalConfigYaml, error) { yamlFile, err := os.ReadFile(filePath) if err != nil { @@ -81,7 +84,7 @@ func getLocatrFromYamlConfig(evalConfig *evalConfigYaml, page playwright.Page) * reRankClient := reranker.NewCohereClient(os.Getenv("COHERE_API_KEY")) locatrOptions.ReRankClient = reRankClient } - return playwrightLocatr.NewPlaywrightLocatr(page, locatrOptions) + return playwrightLocatr.NewPlaywrightLocatr(context.Background(), page, locatrOptions) } func writeEvalResultToCsv(results []evalResult, fileName string) { diff --git a/examples/appium/main.go b/examples/appium/main.go index 78c995c..9a45daf 100644 --- a/examples/appium/main.go +++ b/examples/appium/main.go @@ -1,6 +1,8 @@ package main import ( + "context" + "errors" "fmt" "log/slog" "os" @@ -9,11 +11,50 @@ import ( appiumLocatr "github.com/vertexcover-io/locatr/golang/appium" "github.com/vertexcover-io/locatr/golang/llm" "github.com/vertexcover-io/locatr/golang/logger" + "github.com/vertexcover-io/locatr/golang/tracing" ) +type Config struct { + Tracing struct { + Endpoint string + ServiceName string + Insecure bool + } +} + func main() { + ctx := context.Background() + logger.Level.Set(slog.LevelDebug) + cfg := Config{} + cfg.Tracing.Endpoint = "localhost:4317" + cfg.Tracing.ServiceName = "locator" + cfg.Tracing.Insecure = true + + shutdown, err := tracing.SetupOtelSDK( + context.Background(), + tracing.WithEndpoint(cfg.Tracing.Endpoint), + tracing.WithSVCName(cfg.Tracing.ServiceName), + tracing.WithInsecure(cfg.Tracing.Insecure), + ) + if err != nil { + logger.Logger.Error( + "Failed to setup Open Telemetry SDK", + slog.String("error", err.Error()), + ) + os.Exit(1) + } + defer func() { + if sErr := shutdown(context.Background()); sErr != nil { + err = errors.Join(err, sErr) + logger.Logger.Error( + "Error while shutting down Open Telemetry service", + slog.String("error", err.Error()), + ) + } + }() + llmClient, _ := llm.NewLlmClient( llm.OpenAI, // (openai | anthropic), os.Getenv("OPENAI_MODEL"), @@ -23,15 +64,16 @@ func main() { LlmClient: llmClient, } aLocatr, err := appiumLocatr.NewAppiumLocatr( + ctx, "http://localhost:4723", - "640daa1b-afdc-45a3-83fd-d0c37cffb3de", bLocatr, + "b97d1a76-3ad5-44a5-840d-286294d94bfc", bLocatr, ) if err != nil { fmt.Println("failed creating appium locatr locatr", err) return } - desc := "This input element is designed for password entry, indicated by its type attribute set to \"password,\" which obscures the text entered for privacy. It requires user input, as denoted by the \"required\" attribute, ensuring that users do not submit the form without filling out this field. The placeholder text prompts users to \"Enter your password,\" guiding them on the expected input. This input is commonly used within forms where sensitive data is collected, such as registration or login forms." - l, err := aLocatr.GetLocatrStr(desc) + desc := "This EditText element for 'Email' serves as the second interactive entry point for user input in a linear sequence, positioned directly below an ImageView-containing LinearLayout which implies a visual hierarchy. It follows a specific order that leads to it being the first editable text input provided in the context, indicating its primary role in user authentication workflows." + l, err := aLocatr.GetLocatrStr(ctx, desc) if err != nil { fmt.Println("error getting locatr", err) } diff --git a/examples/cdp/main.go b/examples/cdp/main.go index 3462c81..46f6c17 100644 --- a/examples/cdp/main.go +++ b/examples/cdp/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log" "os" @@ -11,6 +12,8 @@ import ( ) func main() { + ctx := context.Background() + reRankClient := reranker.NewCohereClient(os.Getenv("COHERE_API_KEY")) options := locatr.BaseLocatrOptions{ @@ -22,7 +25,7 @@ func main() { connectionOpts := cdpLocatr.CdpConnectionOptions{ Port: 9222, } - connection, err := cdpLocatr.CreateCdpConnection(connectionOpts) + connection, err := cdpLocatr.CreateCdpConnection(ctx, connectionOpts) if err != nil { fmt.Println(err) return @@ -36,7 +39,7 @@ func main() { return } - newsItem, err := cdpLocatr.GetLocatrStr("First news link") + newsItem, err := cdpLocatr.GetLocatrStr(ctx, "First news link") if err != nil { log.Fatalf("could not get locator: %v", err) return diff --git a/examples/default-llm-client/main.go b/examples/default-llm-client/main.go index efd72fb..f7a1f6a 100644 --- a/examples/default-llm-client/main.go +++ b/examples/default-llm-client/main.go @@ -6,6 +6,7 @@ Example on how to use locatr without passing the llm client. */ import ( + "context" "log" "time" @@ -15,6 +16,8 @@ import ( ) func main() { + ctx := context.Background() + pw, err := playwright.Run() if err != nil { log.Fatalf("could not start playwright: %v", err) @@ -44,9 +47,9 @@ func main() { UseCache: true, } // llm client is created by default by reading the environment variables. - playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(page, options) + playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(ctx, page, options) - _, err = playWrightLocatr.GetLocatr("Search Docker Hub input field") + _, err = playWrightLocatr.GetLocatr(ctx, "Search Docker Hub input field") if err != nil { log.Fatalf("could not get locator: %v", err) } diff --git a/examples/dockerhub/docker_hub.go b/examples/dockerhub/docker_hub.go index e43e251..be31906 100644 --- a/examples/dockerhub/docker_hub.go +++ b/examples/dockerhub/docker_hub.go @@ -6,6 +6,7 @@ Example on how to use locatr with playwright to interact with docker hub. */ import ( + "context" "log" "os" "time" @@ -17,6 +18,8 @@ import ( ) func main() { + ctx := context.Background() + pw, err := playwright.Run() if err != nil { log.Fatalf("could not start playwright: %v", err) @@ -53,9 +56,9 @@ func main() { ResultsFilePath: "docker_hub.json", } - playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(page, options) + playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(ctx, page, options) - sBarLoc, err := playWrightLocatr.GetLocatr("Search Docker Hub input field") + sBarLoc, err := playWrightLocatr.GetLocatr(ctx, "Search Docker Hub input field") if err != nil { log.Fatalf("could not get locator: %v", err) } @@ -70,7 +73,10 @@ func main() { } time.Sleep(5 * time.Second) - pLink, err := playWrightLocatr.GetLocatr("Link to python repo on docker hub. It has the following description: 'Python is an interpreted, interactive, object-oriented, open-source programming language.'") + pLink, err := playWrightLocatr.GetLocatr( + ctx, + "Link to python repo on docker hub. It has the following description: 'Python is an interpreted, interactive, object-oriented, open-source programming language.'", + ) if err != nil { log.Fatalf("could not get locator: %v", err) } @@ -81,7 +87,7 @@ func main() { } time.Sleep(3 * time.Second) - tagsLoc, err := playWrightLocatr.GetLocatr("Tags tab on the page. It is made up of anchor tag") + tagsLoc, err := playWrightLocatr.GetLocatr(ctx, "Tags tab on the page. It is made up of anchor tag") playWrightLocatr.WriteResultsToFile() if err != nil { log.Fatalf("could not get locator: %v", err) diff --git a/examples/github/github.go b/examples/github/github.go index e2c049d..34804b6 100644 --- a/examples/github/github.go +++ b/examples/github/github.go @@ -5,6 +5,7 @@ package main Example on how to use locatr with playwright to interact with github. */ import ( + "context" "fmt" "log" "os" @@ -51,10 +52,11 @@ func main() { log.Fatalf("could not create llm client: %v", err) } options := locatr.BaseLocatrOptions{UseCache: true, LlmClient: llmClient} + ctx := context.Background() - locatr := playwrightLocatr.NewPlaywrightLocatr(page, options) + locatr := playwrightLocatr.NewPlaywrightLocatr(ctx, page, options) - cDropDownLoc, err := locatr.GetLocatr("<> Code dropdown") + cDropDownLoc, err := locatr.GetLocatr(ctx, "<> Code dropdown") if err != nil { log.Fatalf("could not get locator: %v", err) return @@ -64,7 +66,7 @@ func main() { return } - dZipLoc, err := locatr.GetLocatr("Download ZIP button on the opened dropdown") + dZipLoc, err := locatr.GetLocatr(ctx, "Download ZIP button on the opened dropdown") if err != nil { log.Fatalf("could not get download ZIP locator: %v", err) return diff --git a/examples/rerank/main.go b/examples/rerank/main.go index 1f95624..48ca74e 100644 --- a/examples/rerank/main.go +++ b/examples/rerank/main.go @@ -6,6 +6,7 @@ Example on how to use locatr without passing the llm client. */ import ( + "context" "log" "os" "time" @@ -48,9 +49,10 @@ func main() { ReRankClient: reRankClient, } // llm client is created by default by reading the environment variables. - playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(page, options) + ctx := context.Background() + playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(ctx, page, options) - nItem, err := playWrightLocatr.GetLocatr("First news link") + nItem, err := playWrightLocatr.GetLocatr(ctx, "First news link") if err != nil { log.Fatalf("could not get locator: %v", err) } diff --git a/examples/selenium/main.go b/examples/selenium/main.go index d5bb4e8..dfc18a7 100644 --- a/examples/selenium/main.go +++ b/examples/selenium/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log" "os" @@ -22,13 +23,15 @@ func main() { caps := selenium.Capabilities{} caps.AddChrome(chrome.Capabilities{Args: []string{}}) - defer service.Stop() + defer func() { + _ = service.Stop() + }() driver, err := selenium.NewRemote(caps, "") if err != nil { log.Fatal(err) return } - driver.Get("https://news.ycombinator.com/") + _ = driver.Get("https://news.ycombinator.com/") reRankClient := reranker.NewCohereClient(os.Getenv("COHERE_API_KEY")) @@ -40,8 +43,13 @@ func main() { // wait for page to load time.Sleep(3 * time.Second) + ctx := context.Background() seleniumLocatr, err := seleniumLocatr.NewRemoteConnSeleniumLocatr( - "http://localhost:4444/wd/hub", "ca0d56a6a3dcfc51eb0110750f0abab7", options) // the path must end with /wd/hub + ctx, + "http://localhost:4444/wd/hub", + "ca0d56a6a3dcfc51eb0110750f0abab7", + options, + ) // the path must end with /wd/hub /* or: directly pass the driver @@ -51,7 +59,7 @@ func main() { log.Fatal(err) return } - newsLocatr, err := seleniumLocatr.GetLocatrStr("First news link in the site..") + newsLocatr, err := seleniumLocatr.GetLocatrStr(ctx, "First news link in the site..") if err != nil { log.Fatal(err) return diff --git a/examples/steam/steam.go b/examples/steam/steam.go index ae2f522..2de188c 100644 --- a/examples/steam/steam.go +++ b/examples/steam/steam.go @@ -6,6 +6,7 @@ Example on how to use locatr with playwright to interact with steam. */ import ( + "context" "fmt" "log" "os" @@ -56,10 +57,11 @@ func main() { log.Fatalf("could not create llm client: %v", err) } options := locatr.BaseLocatrOptions{UseCache: true, LlmClient: llmClient} + ctx := context.Background() - playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(page, options) + playWrightLocatr := playwrightLocatr.NewPlaywrightLocatr(ctx, page, options) - sBarLoc, err := playWrightLocatr.GetLocatr("Search input bar on the steam store.") + sBarLoc, err := playWrightLocatr.GetLocatr(ctx, "Search input bar on the steam store.") if err != nil { log.Fatalf("could not get search bar locator: %v", err) } @@ -73,7 +75,7 @@ func main() { } time.Sleep(5 * time.Second) - cStrikeLoc, err := playWrightLocatr.GetLocatr("Counter Strike 2 game on the list") + cStrikeLoc, err := playWrightLocatr.GetLocatr(ctx, "Counter Strike 2 game on the list") if err != nil { log.Fatalf("could not get Counter Strike 2 locator: %v", err) return @@ -84,7 +86,7 @@ func main() { } time.Sleep(5 * time.Second) - sysReqLoc, err := playWrightLocatr.GetLocatr("System Requirements section on the game page.") + sysReqLoc, err := playWrightLocatr.GetLocatr(ctx, "System Requirements section on the game page.") if err != nil { log.Fatalf("could not get system requirements locator: %v", err) return diff --git a/go.mod b/go.mod index 91b0eb4..93500ff 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,24 @@ module github.com/vertexcover-io/locatr -go 1.23 +go 1.23.0 -toolchain go1.23.2 +toolchain go1.24.1 require ( - github.com/antchfx/xmlquery v1.4.3 + github.com/antchfx/xmlquery v1.4.4 github.com/cohere-ai/cohere-go/v2 v2.12.0 github.com/go-resty/resty/v2 v2.16.2 github.com/liushuangls/go-anthropic/v2 v2.4.1 github.com/mafredri/cdp v0.35.0 github.com/openai/openai-go v0.1.0-alpha.50 github.com/playwright-community/playwright-go v0.4501.1 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/vertexcover-io/selenium v0.0.0-20241204163435-6f7f74f598ec + go.opentelemetry.io/otel v1.35.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 + go.opentelemetry.io/otel/sdk v1.35.0 + go.opentelemetry.io/otel/trace v1.35.0 gopkg.in/validator.v2 v2.0.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -23,22 +28,34 @@ require ( github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect github.com/aws/smithy-go v1.20.3 // indirect github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-stack/stack v1.8.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.3.4 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect github.com/mediabuyerbot/go-crx3 v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tidwall/gjson v1.17.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/grpc v1.71.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect ) diff --git a/go.sum b/go.sum index 134fd50..56d88db 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antchfx/xmlquery v1.4.3 h1:f6jhxCzANrWfa93O+NmRWvieVyLs+R2Szfpy+YrZaww= github.com/antchfx/xmlquery v1.4.3/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc= +github.com/antchfx/xmlquery v1.4.4 h1:mxMEkdYP3pjKSftxss4nUHfjBhnMk4imGoR96FRY2dg= +github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fusrx9b12fc= github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs= github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -25,6 +27,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chromedp/cdproto v0.0.0-20200209033844-7e00b02ea7d2/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -52,6 +56,11 @@ github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQr github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -59,25 +68,30 @@ github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v27 v27.0.4/go.mod h1:/0Gr8pJ55COkmv+S/yPKCczSkUPIM/LnFyubufRNIS0= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -93,6 +107,8 @@ github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -109,8 +125,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/liushuangls/go-anthropic/v2 v2.4.1 h1:NrqITJX+zQ2kBYx6jPU+wqEK2GPDu4tnoJORhcyUHCM= github.com/liushuangls/go-anthropic/v2 v2.4.1/go.mod h1:8BKv/fkeTaL5R9R9bGkaknYBueyw2WxY20o7bImbOek= github.com/mafredri/cdp v0.35.0 h1:fKQ6LbcH3WsxVrWbi/DSgLunJTqmF5o/7w8iFDDj71c= @@ -145,8 +162,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -163,8 +180,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= @@ -185,7 +202,27 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -234,8 +271,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -250,8 +288,9 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -273,6 +312,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -293,8 +334,9 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= @@ -328,10 +370,18 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY= +google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/golang/appium/appium.go b/golang/appium/appium.go index 7bd8af2..00f7b9f 100644 --- a/golang/appium/appium.go +++ b/golang/appium/appium.go @@ -1,6 +1,7 @@ package appiumLocatr import ( + "context" "encoding/json" "fmt" "strings" @@ -8,21 +9,44 @@ import ( locatr "github.com/vertexcover-io/locatr/golang" "github.com/vertexcover-io/locatr/golang/appium/appiumClient" "github.com/vertexcover-io/locatr/golang/elementSpec" + "github.com/vertexcover-io/locatr/golang/minifier" + "github.com/vertexcover-io/locatr/golang/tracing" + "go.opentelemetry.io/otel/attribute" ) +type AppiumClient interface { + IsWebView(context.Context) bool + ExecuteScript(context.Context, string, ...any) (any, error) + GetPageSource(context.Context) (string, error) + GetCapabilities(context.Context) (*appiumClient.SessionResponse, error) + GetCurrentActivity(context.Context) (string, error) + FindElement(ctx context.Context, locator, locator_type string) error +} + type appiumPlugin struct { - client *appiumClient.AppiumClient + client AppiumClient } type appiumLocatr struct { locatr *locatr.BaseLocatr } -func NewAppiumLocatr(serverUrl string, sessionId string, opts locatr.BaseLocatrOptions) (*appiumLocatr, error) { - apC, err := appiumClient.NewAppiumClient(serverUrl, sessionId) +func NewAppiumLocatr( + ctx context.Context, + serverUrl string, + sessionId string, + opts locatr.BaseLocatrOptions, +) (*appiumLocatr, error) { + ctx, span := tracing.StartSpan(ctx, "Connecting to remote appium instance") + defer span.End() + + apC, err := appiumClient.NewAppiumCacheClient(ctx, serverUrl, sessionId) if err != nil { return nil, err } + + span.AddEvent("Connected to remote appium instance") + plugin := &appiumPlugin{ client: apC, } @@ -33,29 +57,32 @@ func NewAppiumLocatr(serverUrl string, sessionId string, opts locatr.BaseLocatrO return locatr, nil } -func (apPlugin *appiumPlugin) GetMinifiedDomAndLocatorMap() ( +func (apPlugin *appiumPlugin) GetMinifiedDomAndLocatorMap(ctx context.Context) ( *elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error, ) { - if apPlugin.client.IsWebView() { - return apPlugin.htmlMinification() + ctx, span := tracing.StartSpan(ctx, "GetMinifiedDomAndLocatorMap") + defer span.End() + + if apPlugin.client.IsWebView(ctx) { + return apPlugin.htmlMinification(ctx) } - return apPlugin.xmlMinification() + return apPlugin.xmlMinification(ctx) } -func (apPlugin *appiumPlugin) evaluateJsScript(script string) error { - _, err := apPlugin.client.ExecuteScript(script, nil) +func (apPlugin *appiumPlugin) evaluateJsScript(ctx context.Context, script string) error { + _, err := apPlugin.client.ExecuteScript(ctx, script) if err != nil { return fmt.Errorf("failed to evaluate JS script: %w", err) } return nil } -func (apPlugin *appiumPlugin) evaluateJsFunction(function string) (string, error) { +func (apPlugin *appiumPlugin) evaluateJsFunction(ctx context.Context, function string) (string, error) { rFunction := "return " + function - result, err := apPlugin.client.ExecuteScript(rFunction, nil) + result, err := apPlugin.client.ExecuteScript(ctx, rFunction) if err != nil { return "", fmt.Errorf("failed to evaluate JS function: %w", err) } @@ -73,33 +100,44 @@ func (apPlugin *appiumPlugin) evaluateJsFunction(function string) (string, error } } -func (apPlugin *appiumPlugin) htmlMinification() (*elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error) { - if err := apPlugin.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { +func (apPlugin *appiumPlugin) htmlMinification(ctx context.Context) (*elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error) { + if err := apPlugin.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return nil, nil, "", fmt.Errorf("%v", err) } - result, err := apPlugin.evaluateJsFunction("minifyHTML()") + result, err := apPlugin.evaluateJsFunction(ctx, "minifyHTML()") if err != nil { return nil, nil, "", err } elementsSpec := &elementSpec.ElementSpec{} if err := json.Unmarshal([]byte(result), elementsSpec); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal ElementSpec json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal ElementSpec json: %v, expected json, received: %s", + err, + result, + ) } - result, _ = apPlugin.evaluateJsFunction("mapElementsToJson()") + result, _ = apPlugin.evaluateJsFunction(ctx, "mapElementsToJson()") idLocatorMap := &elementSpec.IdToLocatorMap{} if err := json.Unmarshal([]byte(result), idLocatorMap); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal IdToLocatorMap json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal IdToLocatorMap json: %v, expected json, received: %s", + err, + result, + ) } return elementsSpec, idLocatorMap, "css selector", nil } -func (apPlugin *appiumPlugin) xmlMinification() (*elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error) { - pageSource, err := apPlugin.client.GetPageSource() +func (apPlugin *appiumPlugin) xmlMinification(ctx context.Context) (*elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error) { + ctx, span := tracing.StartSpan(ctx, "xmlMinification") + defer span.End() + + pageSource, err := apPlugin.client.GetPageSource(ctx) if err != nil { return nil, nil, "", err } - capabilities, err := apPlugin.client.GetCapabilities() + capabilities, err := apPlugin.client.GetCapabilities(ctx) if err != nil { return nil, nil, "", err } @@ -107,46 +145,62 @@ func (apPlugin *appiumPlugin) xmlMinification() (*elementSpec.ElementSpec, *elem if platFormName == "" { platFormName = capabilities.Value.Cap.PlatformName } - eSpec, err := minifySource(pageSource, platFormName) + span.AddEvent("minifying source") + eSpec, err := minifier.MinifyXMLSource(pageSource, platFormName) if err != nil { return nil, nil, "", err } - locatrMap, err := mapElementsToJson(pageSource, platFormName) + span.AddEvent("mapping elements to json") + locatrMap, err := minifier.MapXMLElementsToJson(pageSource, platFormName) if err != nil { return nil, nil, "", err } return eSpec, locatrMap, "xpath", nil } -func (apPlugin *appiumPlugin) GetCurrentContext() string { - capabilities, err := apPlugin.client.GetCapabilities() +func (apPlugin *appiumPlugin) GetCurrentContext(ctx context.Context) string { + ctx, span := tracing.StartSpan(ctx, "GetCurrentContext") + defer span.End() + + capabilities, err := apPlugin.client.GetCapabilities(ctx) if err != nil { return "" } + + span.SetAttributes(attribute.String("platform", capabilities.Value.PlatformName)) + if strings.ToLower(capabilities.Value.PlatformName) != "andriod" { return "" } - if currentActivity, err := apPlugin.client.GetCurrentActivity(); err != nil { + if currentActivity, err := apPlugin.client.GetCurrentActivity(ctx); err != nil { return currentActivity } return "" } -func (apPlugin *appiumPlugin) IsValidLocator(locatr string) (bool, error) { +func (apPlugin *appiumPlugin) IsValidLocator(ctx context.Context, locatr string) (bool, error) { + ctx, span := tracing.StartSpan(ctx, "IsValidLocator") + defer span.End() + locator_type := "xpath" - if apPlugin.client.IsWebView() { + if apPlugin.client.IsWebView(ctx) { locator_type = "css selector" } - if err := apPlugin.client.FindElement(locatr, locator_type); err == nil { + span.SetAttributes(attribute.String("locator_type", locator_type)) + + if err := apPlugin.client.FindElement(ctx, locatr, locator_type); err == nil { return true, nil } else { return false, err } } -func (apLocatr *appiumLocatr) GetLocatrStr(userReq string) (*locatr.LocatrOutput, error) { - locatrOutput, err := apLocatr.locatr.GetLocatorStr(userReq) +func (apLocatr *appiumLocatr) GetLocatrStr(ctx context.Context, userReq string) (*locatr.LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "GetLocatrStr") + defer span.End() + + locatrOutput, err := apLocatr.locatr.GetLocatorStr(ctx, userReq) if err != nil { return nil, err } diff --git a/golang/appium/appiumClient/appiumCacheClient.go b/golang/appium/appiumClient/appiumCacheClient.go new file mode 100644 index 0000000..223ac0b --- /dev/null +++ b/golang/appium/appiumClient/appiumCacheClient.go @@ -0,0 +1,130 @@ +package appiumClient + +import ( + "context" + "errors" + "fmt" + + "github.com/vertexcover-io/locatr/golang/tracing" +) + +var ErrNotSupported = errors.New("action is not supported") +var ErrAppiumCommandFailed = errors.New("appium command failed") + +type appiumCacheClient struct { + sessionId string + serverUrl string + appiumClient *AppiumClient + + cache map[string]any +} + +func NewAppiumCacheClient( + ctx context.Context, + serverUrl string, + sessionId string, +) (*appiumCacheClient, error) { + ctx, span := tracing.StartSpan(ctx, "NewAppiumCacheClient") + defer span.End() + + appiumClient, err := NewAppiumClient(ctx, serverUrl, sessionId) + if err != nil { + return nil, err + } + + cache := appiumCacheClient{ + sessionId: sessionId, + serverUrl: serverUrl, + appiumClient: appiumClient, + cache: make(map[string]any), + } + return &cache, nil +} + +func (a *appiumCacheClient) ExecuteScript( + ctx context.Context, + script string, + args ...any, +) (any, error) { + if !a.IsWebView(ctx) { + return nil, fmt.Errorf("can't run script in native view: %w", ErrNotSupported) + } + + return a.appiumClient.ExecuteScript(ctx, script, args) +} + +func (a *appiumCacheClient) GetCurrentViewContext(ctx context.Context) (string, error) { + cacheKey := "getCurrentViewContext" + + if value, ok := a.cache[cacheKey]; ok { + return value.(string), nil + } + + viewCtx, err := a.appiumClient.GetCurrentViewContext(ctx) + if err != nil { + return "", err + } + a.cache[cacheKey] = viewCtx + return viewCtx, nil +} + +func (a *appiumCacheClient) IsWebView(ctx context.Context) bool { + cacheKey := "isWebView" + + if value, ok := a.cache[cacheKey]; ok { + return value.(bool) + } + + webView := a.appiumClient.IsWebView(ctx) + a.cache[cacheKey] = webView + return webView +} + +func (a *appiumCacheClient) GetPageSource(ctx context.Context) (string, error) { + cacheKey := "getPageSource" + if value, ok := a.cache[cacheKey]; ok { + return value.(string), nil + } + + source, err := a.appiumClient.GetPageSource(ctx) + if err != nil { + return "", err + } + a.cache[cacheKey] = source + return source, nil +} + +func (a *appiumCacheClient) FindElement( + ctx context.Context, + locator, locator_type string, +) error { + return a.appiumClient.FindElement(ctx, locator, locator_type) +} + +func (a *appiumCacheClient) GetCapabilities(ctx context.Context) (*SessionResponse, error) { + cacheKey := "getCapabilities" + if value, ok := a.cache[cacheKey]; ok { + return value.(*SessionResponse), nil + } + + cap, err := a.appiumClient.GetCapabilities(ctx) + if err != nil { + return nil, err + } + a.cache[cacheKey] = cap + return cap, nil +} + +func (a *appiumCacheClient) GetCurrentActivity(ctx context.Context) (string, error) { + cacheKey := "getCurrentActivity" + if value, ok := a.cache[cacheKey]; ok { + return value.(string), nil + } + + act, err := a.appiumClient.GetCurrentActivity(ctx) + if err != nil { + return "", err + } + a.cache[cacheKey] = act + return act, nil +} diff --git a/golang/appium/appiumClient/appiumClient.go b/golang/appium/appiumClient/appiumClient.go index 2ea026a..ee05d38 100644 --- a/golang/appium/appiumClient/appiumClient.go +++ b/golang/appium/appiumClient/appiumClient.go @@ -1,15 +1,19 @@ package appiumClient import ( + "context" "encoding/json" "errors" "fmt" + "log/slog" "net/url" "strings" "time" "github.com/go-resty/resty/v2" "github.com/vertexcover-io/locatr/golang/logger" + "github.com/vertexcover-io/locatr/golang/tracing" + "go.opentelemetry.io/otel/attribute" ) type AppiumClient struct { @@ -45,7 +49,7 @@ type Capabilities struct { LastScrollData interface{} `json:"lastScrollData"` } -type sessionResponse struct { +type SessionResponse struct { Value struct { Capabilities Cap Capabilities `json:"capabilities"` @@ -78,18 +82,32 @@ func CreateNewHttpClient(baseUrl string) *resty.Client { return client } -func NewAppiumClient(serverUrl string, sessionId string) (*AppiumClient, error) { +func NewAppiumClient(ctx context.Context, serverUrl string, sessionId string) (*AppiumClient, error) { + _, span := tracing.StartSpan(ctx, "NewAppiumClient") + defer span.End() + defer logger.GetTimeLogger("Creating appium client")() + baseUrl, err := url.Parse(serverUrl) if err != nil { return nil, err } + // should be in milliseconds joinedUrl := baseUrl.JoinPath("session").JoinPath(sessionId) client := CreateNewHttpClient(joinedUrl.String()) + + span.SetAttributes(attribute.String("client-url", joinedUrl.String())) + + // added to test session still exists. + // TODO: consider a parameter to skip the test when interacting from python side + // todo: create a cached session, Make a stateful session. + + span.AddEvent("fetching /context") resp, err := client.R().Get("/context") if err != nil { return nil, fmt.Errorf("%w : %w", ErrFailedConnectingToAppiumServer, err) } + span.AddEvent("received /context") if resp.StatusCode() != 200 { return nil, fmt.Errorf("%w : %s", ErrSessionNotActive, sessionId) } @@ -103,27 +121,43 @@ type resp struct { Value any `json:"value"` } -func (ac *AppiumClient) ExecuteScript(script string, args []any) (any, error) { +func (ac *AppiumClient) ExecuteScript(ctx context.Context, script string, args ...any) (any, error) { defer logger.GetTimeLogger("Appium: ExecuteScript")() + _, span := tracing.StartSpan(ctx, "ExecuteScript") + defer span.End() + body := map[string]any{ "script": script, - "args": []string{}, + "args": args, } bodyJson, err := json.Marshal(body) if err != nil { return nil, err } + span.AddEvent("request /execute/sync") + response, err := ac.httpClient.R(). SetHeader("Content-Type", "application/json"). SetBody(bodyJson). Post("/execute/sync") + + span.AddEvent("response /execute/sync") + + logger.Logger.Debug( + "Request sent to Appium server", + slog.String("url", response.Request.URL), + slog.String("method", response.Request.Method), + ) + if err != nil { return nil, fmt.Errorf("%w: %w", ErrFailedConnectingToAppiumServer, err) } if response.StatusCode() != 200 { return nil, fmt.Errorf("%w: %s", ErrSessionNotActive, ac.sessionId) } + + span.AddEvent("reading response") var respBody resp err = json.Unmarshal(response.Body(), &respBody) if err != nil { @@ -132,26 +166,49 @@ func (ac *AppiumClient) ExecuteScript(script string, args []any) (any, error) { return respBody.Value, nil } -func (ac *AppiumClient) GetCurrentViewContext() (string, error) { +func (ac *AppiumClient) GetCurrentViewContext(ctx context.Context) (string, error) { defer logger.GetTimeLogger("Appium: GetCurrentViewContext")() + _, span := tracing.StartSpan(ctx, "GetCurrentViewContext") + defer span.End() + + span.AddEvent("request /context") response, err := ac.httpClient.R().Get("/context") + span.AddEvent("response /context") + + logger.Logger.Debug( + "Request sent to Appium server", + slog.String("url", response.Request.URL), + slog.String("method", response.Request.Method), + ) + if err != nil { return "", fmt.Errorf("%w: %w", ErrFailedConnectingToAppiumServer, err) } if response.StatusCode() != 200 { return "", fmt.Errorf("%w: %s", ErrSessionNotActive, ac.sessionId) } + + span.AddEvent("reading response") + var responseBody appiumGetCurrentContextResponse - err = json.Unmarshal(response.Body(), &responseBody) + body := response.Body() + err = json.Unmarshal(body, &responseBody) if err != nil { - return "", fmt.Errorf("failed to unmarshal response: %w", err) + return "", fmt.Errorf( + "failed to unmarshal response: %w, expected json, received: %s", + err, + body, + ) } return responseBody.Value, nil } -func (ac *AppiumClient) IsWebView() bool { - view, err := ac.GetCurrentViewContext() +func (ac *AppiumClient) IsWebView(ctx context.Context) bool { + ctx, span := tracing.StartSpan(ctx, "IsWebView") + defer span.End() + + view, err := ac.GetCurrentViewContext(ctx) if err != nil { return false } @@ -159,72 +216,147 @@ func (ac *AppiumClient) IsWebView() bool { return strings.Contains(view, "webview") || strings.Contains(view, "chromium") } -func (ac *AppiumClient) GetPageSource() (string, error) { +func (ac *AppiumClient) GetPageSource(ctx context.Context) (string, error) { defer logger.GetTimeLogger("Appium: GetPageSource")() + _, span := tracing.StartSpan(ctx, "GetPageSource") + defer span.End() + + span.AddEvent("request /source/") response, err := ac.httpClient.R().Get("source/") + span.AddEvent("response /source/") + + logger.Logger.Debug( + "Request sent to Appium server", + slog.String("url", response.Request.URL), + slog.String("method", response.Request.Method), + ) + if err != nil { return "", fmt.Errorf("%w : %w", ErrFailedConnectingToAppiumServer, err) } if response.StatusCode() != 200 { return "", fmt.Errorf("%w : %s ", ErrSessionNotActive, ac.sessionId) } + + span.AddEvent("read response") + var responseBody appiumPageSourceResponse - err = json.Unmarshal(response.Body(), &responseBody) + body := response.Body() + err = json.Unmarshal(body, &responseBody) if err != nil { - return "", fmt.Errorf("failed to unmarshal response: %w", err) + return "", fmt.Errorf( + "failed to unmarshal response: %w, expected json, received: %s", + err, + body, + ) } return responseBody.Value, nil } -func (ac *AppiumClient) FindElement(locator, locator_type string) error { +func (ac *AppiumClient) FindElement(ctx context.Context, locator, locator_type string) error { defer logger.GetTimeLogger("Appium: FindElement")() + _, span := tracing.StartSpan(ctx, "FindElement") + defer span.End() + requestBody := findElementRequest{ Using: locator_type, Value: locator, } + span.SetAttributes( + attribute.String("locator-type", locator_type), + attribute.String("locator", locator), + ) + + span.AddEvent("request /element") response, err := ac.httpClient.R(). SetBody(requestBody). SetResult(&appiumGetElementResponse{}). Post("element") + span.AddEvent("response /element") + + logger.Logger.Debug( + "Request sent to Appium server", + slog.String("url", response.Request.URL), + slog.String("method", response.Request.Method), + ) if err != nil { return fmt.Errorf("%w : %w", ErrFailedConnectingToAppiumServer, err) } if response.StatusCode() != 200 { + span.AddEvent("read response") + var result appiumGetElementResponse - err = json.Unmarshal(response.Body(), &result) + body := response.Body() + err = json.Unmarshal(body, &result) if err != nil { - return fmt.Errorf("failed to unmarshal response: %w", err) + return fmt.Errorf( + "failed to unmarshal response: %w, expected json, received: %s", + err, + body, + ) } return fmt.Errorf("%s : %s", result.Value.Error, result.Value.Message) } return nil } -func (ac *AppiumClient) GetCapabilities() (*sessionResponse, error) { +func (ac *AppiumClient) GetCapabilities(ctx context.Context) (*SessionResponse, error) { defer logger.GetTimeLogger("Appium: GetCapabilities")() - response, err := ac.httpClient.R().SetResult(&sessionResponse{}).Get("/") + _, span := tracing.StartSpan(ctx, "GetCapabilities") + defer span.End() + + span.AddEvent("request /") + response, err := ac.httpClient.R().SetResult(&SessionResponse{}).Get("/") + span.AddEvent("response /") + + logger.Logger.Debug( + "Request sent to Appium server", + slog.String("url", response.Request.URL), + slog.String("method", response.Request.Method), + ) + if err != nil { return nil, fmt.Errorf("%w : %w", ErrFailedConnectingToAppiumServer, err) } - var result sessionResponse - err = json.Unmarshal(response.Body(), &result) + + span.AddEvent("read response") + + var result SessionResponse + body := response.Body() + err = json.Unmarshal(body, &result) if response.StatusCode() != 200 { if err != nil { - return nil, fmt.Errorf("failed to unmarshal response: %w", err) + return nil, fmt.Errorf( + "failed to unmarshal response: %w, expected json, received: %s", + err, + body, + ) } return nil, fmt.Errorf("%s : %s", result.Value.Error, result.Value.Message) } return &result, nil } -func (ac *AppiumClient) GetCurrentActivity() (string, error) { +func (ac *AppiumClient) GetCurrentActivity(ctx context.Context) (string, error) { defer logger.GetTimeLogger("Appium: GetCurrentActivity")() + _, span := tracing.StartSpan(ctx, "GetCurrentActivity") + defer span.End() + + span.AddEvent("request /appium/device/current_activity") response, err := ac.httpClient.R().SetResult(&getActivityResponse{}).Get("appium/device/current_activity") + span.AddEvent("response /appium/device/current_activity") + + logger.Logger.Debug( + "Request sent to Appium server", + slog.String("url", response.Request.URL), + slog.String("method", response.Request.Method), + ) + if err != nil { return "", fmt.Errorf("%w : %w", ErrFailedConnectingToAppiumServer, err) } diff --git a/golang/baseLocatr.go b/golang/baseLocatr.go index 1e8f08e..dd3bfd7 100644 --- a/golang/baseLocatr.go +++ b/golang/baseLocatr.go @@ -1,11 +1,13 @@ package locatr import ( + "context" _ "embed" "encoding/json" "errors" "fmt" "io" + "log/slog" "os" "path/filepath" "strings" @@ -15,25 +17,27 @@ import ( "github.com/vertexcover-io/locatr/golang/llm" "github.com/vertexcover-io/locatr/golang/logger" "github.com/vertexcover-io/locatr/golang/reranker" + "github.com/vertexcover-io/locatr/golang/tracing" + "go.opentelemetry.io/otel/attribute" ) type SelectorType string type PluginInterface interface { - GetMinifiedDomAndLocatorMap() ( + GetMinifiedDomAndLocatorMap(context.Context) ( *elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, SelectorType, error, ) - GetCurrentContext() string - IsValidLocator(locatr string) (bool, error) + GetCurrentContext(context.Context) string + IsValidLocator(ctx context.Context, locatr string) (bool, error) } type LocatrInterface interface { WriteResultsToFile() GetLocatrResults() []LocatrResult - GetLocatrStr(userReq string) (*LocatrOutput, error) + GetLocatrStr(ctx context.Context, userReq string) (*LocatrOutput, error) } type llmLocatorOutputDto struct { @@ -181,23 +185,36 @@ func (l *LocatrResult) MarshalJSON() ([]byte, error) { } // GetLocatorStr returns the locator string for the given user request -func (l *BaseLocatr) GetLocatorStr(userReq string) (*LocatrOutput, error) { +func (l *BaseLocatr) GetLocatorStr(ctx context.Context, userReq string) (*LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "GetLocatrStr") + defer span.End() + + span.AddEvent("initializing state") + l.initializeState() logger.Logger.Info(fmt.Sprintf("Getting locator for user request: `%s`", userReq)) - currentUrl := l.plugin.GetCurrentContext() - locatr, err := l.loadLocatrsFromCache(userReq) + + currentUrl := l.plugin.GetCurrentContext(ctx) + + span.AddEvent("loading locatrs from cache") + locatr, err := l.loadLocatrsFromCache(ctx, userReq) if err == nil { return locatr, nil } + logger.Logger.Info("Cache miss, starting dom minification") - minifiedDOM, locatorsMap, selectorType, err := l.plugin.GetMinifiedDomAndLocatorMap() + span.AddEvent("minifying dom and generating locator map") + + minifiedDOM, locatorsMap, selectorType, err := l.plugin.GetMinifiedDomAndLocatorMap(ctx) if err != nil { logger.Logger.Error(fmt.Sprintf("Failed to minify DOM and extract ID locator map: %v", err)) return nil, ErrUnableToMinifyHtmlDom } logger.Logger.Info("Extracting element ID using LLM") - llmOutputs, err := l.locateElementId(minifiedDOM.ContentStr(), userReq) + span.AddEvent("locating element using LLM") + + llmOutputs, err := l.locateElementId(ctx, minifiedDOM.ContentStr(), userReq) if err != nil { logger.Logger.Error(fmt.Sprintf("Failed to locate element ID: %v", err)) if len(llmOutputs) > 0 { @@ -207,16 +224,25 @@ func (l *BaseLocatr) GetLocatorStr(userReq string) (*LocatrOutput, error) { )..., ) } - return nil, ErrUnableToLocateElementId + return nil, fmt.Errorf("llm generated %d outputs, %w", len(llmOutputs), ErrUnableToLocateElementId) } locators, ok := (*locatorsMap)[llmOutputs[len(llmOutputs)-1].LocatorID] + + slog.Debug("Locators generated", slog.Int("count", len(locators))) + span.AddEvent("candidate locators generated") + if !ok { logger.Logger.Error("Invalid element ID generated") return nil, ErrInvalidElementIdGenerated } - validLocator, err := l.getValidLocator(locators) + span.SetAttributes(attribute.Int("candidate-locator-count", len(locators))) + + validLocator, err := l.getValidLocator(ctx, locators) + + slog.Debug("Valid locators found", slog.String("valid_locator", strings.Join(validLocator, "\n"))) + if err != nil { logger.Logger.Error(fmt.Sprintf("Failed to find valid locator: %v", err)) return nil, ErrUnableToFindValidLocator @@ -235,6 +261,8 @@ func (l *BaseLocatr) GetLocatorStr(userReq string) (*LocatrOutput, error) { )..., ) if l.options.UseCache { + span.AddEvent("updating cache with new locators") + logger.Logger.Info(fmt.Sprintf("Adding locatrs of `%s` to cache", userReq)) logger.Logger.Debug(fmt.Sprintf("Adding Locatrs of `%s`: `%v` to cache", userReq, locators)) l.addCachedLocatrs(currentUrl, userReq, locatrOutput) @@ -249,18 +277,17 @@ func (l *BaseLocatr) GetLocatorStr(userReq string) (*LocatrOutput, error) { } } return locatrOutput, nil - } -func (l *BaseLocatr) getValidLocator(locators []string) ([]string, error) { +func (l *BaseLocatr) getValidLocator(ctx context.Context, locators []string) ([]string, error) { locatrsToReturn := []string{} for _, locator := range locators { - ok, err := l.plugin.IsValidLocator(locator) + ok, err := l.plugin.IsValidLocator(ctx, locator) if ok { locatrsToReturn = append(locatrsToReturn, locator) logger.Logger.Debug(fmt.Sprintf("Valid locator found: `%s`", locator)) - } else { - logger.Logger.Debug(fmt.Sprintf("error while checking for valid locatr %v", err)) + } else if err != nil { + logger.Logger.Debug(fmt.Sprintf("error while checking for valid locatr: %v", err)) } } if len(locatrsToReturn) == 0 { @@ -268,14 +295,20 @@ func (l *BaseLocatr) getValidLocator(locators []string) ([]string, error) { } return locatrsToReturn, nil } -func (l *BaseLocatr) getReRankedChunks(htmlDom string, userReq string) ([]string, error) { + +func (l *BaseLocatr) getReRankedChunks(ctx context.Context, htmlDom string, userReq string) ([]string, error) { + ctx, span := tracing.StartSpan(ctx, "splitting html using reranker") + defer span.End() + chunks := reranker.SplitHtml(htmlDom, HTML_SEPARATORS, CHUNK_SIZE) logger.Logger.Debug(fmt.Sprintf("SplitHtml resulted in %d chunks.", len(chunks))) request := reranker.ReRankRequest{ Query: userReq, Documents: chunks, } - reRankResults, err := l.reRankClient.ReRank(request) + + span.AddEvent("reranking HTML chunks") + reRankResults, err := l.reRankClient.ReRank(ctx, request) if err != nil { logger.Logger.Error(fmt.Sprintf("Failed to re-rank chunks: %v", err)) return nil, fmt.Errorf("failed to re-rank chunks: %v", err) @@ -285,7 +318,8 @@ func (l *BaseLocatr) getReRankedChunks(htmlDom string, userReq string) ([]string } return sortRerankChunks(chunks, *reRankResults), nil } -func (l *BaseLocatr) llmGetElementId(htmlDom string, userReq string) (*llmLocatorOutputDto, error) { + +func (l *BaseLocatr) llmGetElementId(ctx context.Context, htmlDom string, userReq string) (*llmLocatorOutputDto, error) { jsonData, err := json.Marshal(&llm.LlmWebInputDto{ HtmlDom: htmlDom, UserReq: userReq, @@ -296,7 +330,7 @@ func (l *BaseLocatr) llmGetElementId(htmlDom string, userReq string) (*llmLocato prompt := fmt.Sprintf("%s%s", LOCATR_PROMPT, string(jsonData)) - llmResponse, err := l.llmClient.ChatCompletion(prompt) + llmResponse, err := l.llmClient.ChatCompletion(ctx, prompt) if err != nil { return nil, fmt.Errorf("failed to get response from LLM: %w", err) } @@ -312,16 +346,26 @@ func (l *BaseLocatr) llmGetElementId(htmlDom string, userReq string) (*llmLocato logger.Logger.Debug(fmt.Sprintf("Repaired LLM response: %s", llmResponse.Completion)) if err = json.Unmarshal([]byte(llmResponse.Completion), llmLocatorOutput); err != nil { - return nil, fmt.Errorf("failed to unmarshal llmLocatorOutputDto json: %w", err) + return nil, fmt.Errorf( + "failed to unmarshal llmLocatorOutputDto json: %w, expectd json: recevied: %s", + err, + llmResponse.Completion, + ) } return llmLocatorOutput, nil } -func (l *BaseLocatr) getLocatrOutput(htmlDOM string, userReq string) (*locatrOutputDto, error) { - result, err := l.llmGetElementId(htmlDOM, userReq) +func (l *BaseLocatr) getLocatrOutput(ctx context.Context, htmlDOM string, userReq string) (*locatrOutputDto, error) { + result, err := l.llmGetElementId(ctx, htmlDOM, userReq) if err != nil { return nil, err } endAt := time.Now() + + if result.LocatorID != "" { + logger.Logger.Warn("LLM gave locatorId and error, Using the provided locatorId", slog.String("error", result.Error)) + result.Error = "" + } + if result.Error == "" { return &locatrOutputDto{ llmLocatorOutputDto: *result, @@ -330,12 +374,18 @@ func (l *BaseLocatr) getLocatrOutput(htmlDOM string, userReq string) (*locatrOut } return nil, ErrLocatrRetrievalFailed } -func (l *BaseLocatr) locateElementId(htmlDOM string, userReq string) ([]locatrOutputDto, error) { + +func (l *BaseLocatr) locateElementId(ctx context.Context, htmlDOM string, userReq string) ([]locatrOutputDto, error) { + ctx, span := tracing.StartSpan(ctx, "locateElementId") + defer span.End() + llmOutputs := []locatrOutputDto{} requestInitiatedAt := time.Now() if l.reRankClient == nil { + span.AddEvent("sending full dom to llm") + logger.Logger.Debug("No rerank client setup sending full dom to llm.") - result, err := l.getLocatrOutput(htmlDOM, userReq) + result, err := l.getLocatrOutput(ctx, htmlDOM, userReq) if err != nil { return llmOutputs, err } @@ -346,7 +396,9 @@ func (l *BaseLocatr) locateElementId(htmlDOM string, userReq string) ([]locatrOu } return llmOutputs, ErrLocatrRetrievalFailed } - chunks, err := l.getReRankedChunks(htmlDOM, userReq) + + span.AddEvent("fetching reranked chunks") + chunks, err := l.getReRankedChunks(ctx, htmlDOM, userReq) if err != nil { return llmOutputs, err } @@ -354,9 +406,12 @@ func (l *BaseLocatr) locateElementId(htmlDOM string, userReq string) ([]locatrOu logger.Logger.Debug("No chunks to process") return llmOutputs, ErrNoChunksToProcess } + + span.SetAttributes(attribute.Int("chunk-count", len(chunks))) + if len(chunks) == 1 { logger.Logger.Debug("Only one chunk to process, sending to llm.") - result, err := l.getLocatrOutput(htmlDOM, userReq) + result, err := l.getLocatrOutput(ctx, htmlDOM, userReq) if err != nil { return llmOutputs, err } @@ -371,6 +426,9 @@ func (l *BaseLocatr) locateElementId(htmlDOM string, userReq string) ([]locatrOu var domToProcess string for attempt := 0; attempt < MAX_RETRIES_WITH_RERANK; attempt++ { + + span.AddEvent(fmt.Sprintf("attempt %d chunk", attempt)) + switch attempt { case 0: domToProcess = strings.Join(chunks[0:MAX_RETRIES_WITH_RERANK], "\n") @@ -390,7 +448,7 @@ func (l *BaseLocatr) locateElementId(htmlDOM string, userReq string) ([]locatrOu logger.Logger.Debug(fmt.Sprintf("attempt no (%d) to find locatr with reranking", attempt+1)) requestCompletedAt := time.Now() - result, err := l.llmGetElementId(domToProcess, userReq) + result, err := l.llmGetElementId(ctx, domToProcess, userReq) if err != nil { return llmOutputs, err } @@ -481,17 +539,29 @@ func (l *BaseLocatr) getLocatrsFromState(key string, currentContext string) ([]s logger.Logger.Debug(fmt.Sprintf("Key `%s not found in cache", key)) return nil, "", fmt.Errorf("key `%s` not found in cache", key) } -func (l *BaseLocatr) loadLocatrsFromCache(userReq string) (*LocatrOutput, error) { + +func (l *BaseLocatr) loadLocatrsFromCache(ctx context.Context, userReq string) (*LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "loadLocatorsFromCache") + defer span.End() + requestInitiatedAt := time.Now() - currentContext := l.plugin.GetCurrentContext() + currentContext := l.plugin.GetCurrentContext(ctx) + + span.AddEvent("fetching locators from cache") locators, selectorType, err := l.getLocatrsFromState(userReq, currentContext) if err != nil { + span.AddEvent("failed to load locators from cache") + logger.Logger.Error(fmt.Sprintf("Failed to get locators from cache: %v", err)) return nil, err + } else { if len(locators) > 0 { - validLocators, err := l.getValidLocator(locators) + span.AddEvent("validating locators from cache") + span.SetAttributes(attribute.Int("candidate-locators", len(locators))) + + validLocators, err := l.getValidLocator(ctx, locators) if err == nil { result := LocatrResult{ LocatrDescription: userReq, @@ -509,6 +579,7 @@ func (l *BaseLocatr) loadLocatrsFromCache(userReq string) (*LocatrOutput, error) Selectors: locators, }, nil } else { + span.AddEvent("valid locator not found in cache") logger.Logger.Error(fmt.Sprintf("Failed to find valid locator in cache: %v", err)) } logger.Logger.Info("All cached locators are outdated.") @@ -531,7 +602,12 @@ func (l *BaseLocatr) loadLocatorsCache(cachePath string) error { } err = json.Unmarshal(byteValue, &l.cachedLocatrs) if err != nil { - return fmt.Errorf("failed to unmarshal cache file `(%s)`: %v", cachePath, err) + return fmt.Errorf( + "failed to unmarshal cache file `(%s)`: %v, expected json received: %s", + cachePath, + err, + byteValue, + ) } return nil } diff --git a/golang/baseLocatr_test.go b/golang/baseLocatr_test.go index 60b0253..483864c 100644 --- a/golang/baseLocatr_test.go +++ b/golang/baseLocatr_test.go @@ -1,6 +1,7 @@ package locatr // baseLocator_test.go import ( + "context" "encoding/json" "errors" "fmt" @@ -20,18 +21,18 @@ type MockPlugin struct { invalidLocators map[string]bool } -func (m *MockPlugin) GetCurrentContext() string { +func (m *MockPlugin) GetCurrentContext(context.Context) string { return "test_url" } -func (m *MockPlugin) IsValidLocator(locatr string) (bool, error) { +func (m *MockPlugin) IsValidLocator(ctx context.Context, locatr string) (bool, error) { if m.invalidLocators == nil { return true, nil } return !m.invalidLocators[locatr], nil } -func (m *MockPlugin) GetMinifiedDomAndLocatorMap() ( +func (m *MockPlugin) GetMinifiedDomAndLocatorMap(context.Context) ( *elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, SelectorType, @@ -45,7 +46,7 @@ type MockLlmClient struct { returnError bool } -func (m *MockLlmClient) ChatCompletion(prompt string) (*llm.ChatCompletionResponse, error) { +func (m *MockLlmClient) ChatCompletion(ctx context.Context, prompt string) (*llm.ChatCompletionResponse, error) { if m.returnError { return nil, errors.New("mock llm client error") } @@ -904,7 +905,7 @@ func TestGetValidLocator(t *testing.T) { // Test case 1: All locators are valid locators := []string{"locator1", "locator2", "locator3"} - validLocators, err := baseLocatr.getValidLocator(locators) + validLocators, err := baseLocatr.getValidLocator(context.TODO(), locators) assert.NoError(t, err) assert.Equal(t, locators, validLocators) @@ -919,7 +920,7 @@ func TestGetValidLocator(t *testing.T) { } locatorsWithInvalid := []string{"locator1", "locator2", "locator3"} - validLocators, err = baseLocatrInvalid.getValidLocator(locatorsWithInvalid) + validLocators, err = baseLocatrInvalid.getValidLocator(context.TODO(), locatorsWithInvalid) assert.NoError(t, err) assert.Equal(t, []string{"locator1", "locator3"}, validLocators) @@ -935,7 +936,7 @@ func TestGetValidLocator(t *testing.T) { plugin: mockPluginNoValid, } - validLocators, err = baseLocatrNoValid.getValidLocator(locators) + validLocators, err = baseLocatrNoValid.getValidLocator(context.TODO(), locators) assert.Error(t, err) assert.Nil(t, validLocators) } @@ -957,7 +958,7 @@ func TestLlmGetElementId(t *testing.T) { htmlDom := "test dom" userReq := "find element" - result, err := baseLocatr.llmGetElementId(htmlDom, userReq) + result, err := baseLocatr.llmGetElementId(context.TODO(), htmlDom, userReq) assert.NoError(t, err) assert.Equal(t, "test_id", result.LocatorID) @@ -969,7 +970,7 @@ func TestLlmGetElementId(t *testing.T) { llmClient: mockLlmClientError, } - _, err = baseLocatrError.llmGetElementId(htmlDom, userReq) + _, err = baseLocatrError.llmGetElementId(context.TODO(), htmlDom, userReq) assert.Error(t, err) } @@ -990,7 +991,7 @@ func TestGetLocatrOutput(t *testing.T) { htmlDom := "test dom" userReq := "find element" - result, err := baseLocatr.getLocatrOutput(htmlDom, userReq) + result, err := baseLocatr.getLocatrOutput(context.TODO(), htmlDom, userReq) assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, "test_id", result.LocatorID) @@ -1005,6 +1006,6 @@ func TestGetLocatrOutput(t *testing.T) { llmClient: mockLlmClientError, } - _, err = baseLocatrError.getLocatrOutput(htmlDom, userReq) + _, err = baseLocatrError.getLocatrOutput(context.TODO(), htmlDom, userReq) assert.Error(t, err) } diff --git a/golang/cdp/cdp.go b/golang/cdp/cdp.go index 6f718e7..7d41e50 100644 --- a/golang/cdp/cdp.go +++ b/golang/cdp/cdp.go @@ -14,6 +14,7 @@ import ( "github.com/mafredri/cdp/rpcc" locatr "github.com/vertexcover-io/locatr/golang" "github.com/vertexcover-io/locatr/golang/elementSpec" + "github.com/vertexcover-io/locatr/golang/tracing" "gopkg.in/validator.v2" ) @@ -34,8 +35,10 @@ type CdpConnectionOptions struct { var ErrUnableToLoadJsScriptsThroughCdp = errors.New("unable to load js script through cdp") -func CreateCdpConnection(options CdpConnectionOptions) (*rpcc.Conn, error) { - ctx := context.Background() +func CreateCdpConnection(ctx context.Context, options CdpConnectionOptions) (*rpcc.Conn, error) { + ctx, span := tracing.StartSpan(ctx, "createCDPConnection") + defer span.End() + if len(options.HostName) == 0 { options.HostName = "localhost" } @@ -43,14 +46,20 @@ func CreateCdpConnection(options CdpConnectionOptions) (*rpcc.Conn, error) { if err := optionValidator.Validate(options); err != nil { return nil, err } - wsUrl, err := getWebsocketDebugUrl(fmt.Sprintf("http://%s:%d", options.HostName, options.Port), 0, ctx) + url := fmt.Sprintf("http://%s:%d", options.HostName, options.Port) + wsUrl, err := getWebsocketDebugUrl(ctx, url, 0) if err != nil { return nil, err } + + span.AddEvent("Dailing cdp") + conn, err := rpcc.DialContext(ctx, wsUrl, rpcc.WithWriteBufferSize(1048576)) if err != nil { return nil, fmt.Errorf("could not connect to cdp server: %s, err: %w", wsUrl, err) } + + span.AddEvent("CDP connected") return conn, nil } @@ -64,35 +73,50 @@ func NewCdpLocatr(connection *rpcc.Conn, locatrOptions locatr.BaseLocatrOptions) }, nil } -func (cPlugin *cdpPlugin) GetMinifiedDomAndLocatorMap() ( +func (cPlugin *cdpPlugin) GetMinifiedDomAndLocatorMap(ctx context.Context) ( *elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error, ) { - if err := cPlugin.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { + ctx, span := tracing.StartSpan(ctx, "GetMinifiedDomAndLocatorMap") + defer span.End() + + span.AddEvent("inject HTML minifier script") + if err := cPlugin.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return nil, nil, "", fmt.Errorf("%v : %v", ErrUnableToLoadJsScriptsThroughCdp, err) } - result, err := cPlugin.evaluateJsFunction("minifyHTML()") + + span.AddEvent("evaluate minifyHTML function") + result, err := cPlugin.evaluateJsFunction(ctx, "minifyHTML()") if err != nil { return nil, nil, "", err } elementsSpec := &elementSpec.ElementSpec{} if err := json.Unmarshal([]byte(result), elementsSpec); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal ElementSpec json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal ElementSpec json: %v, expected json, received: %s", + err, + result, + ) } - result, _ = cPlugin.evaluateJsFunction("mapElementsToJson()") + span.AddEvent("evaluate mapElementsToJson") + result, _ = cPlugin.evaluateJsFunction(ctx, "mapElementsToJson()") idLocatorMap := &elementSpec.IdToLocatorMap{} if err := json.Unmarshal([]byte(result), idLocatorMap); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal IdToLocatorMap json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal IdToLocatorMap json: %v, expected json, received: %s", + err, + result, + ) } return elementsSpec, idLocatorMap, "css selector", nil } -func (cdpPlugin *cdpPlugin) evaluateJsFunction(function string) (string, error) { +func (cdpPlugin *cdpPlugin) evaluateJsFunction(ctx context.Context, function string) (string, error) { pageRuntime := cdpPlugin.client.Runtime - result, err := pageRuntime.Evaluate(context.Background(), &runtime.EvaluateArgs{ + result, err := pageRuntime.Evaluate(ctx, &runtime.EvaluateArgs{ Expression: function, }) if err != nil { @@ -108,9 +132,9 @@ func (cdpPlugin *cdpPlugin) evaluateJsFunction(function string) (string, error) } -func (cdpPlugin *cdpPlugin) evaluateJsScript(scriptContent string) error { +func (cdpPlugin *cdpPlugin) evaluateJsScript(ctx context.Context, scriptContent string) error { pageRuntime := cdpPlugin.client.Runtime - _, err := pageRuntime.Evaluate(context.Background(), &runtime.EvaluateArgs{ + _, err := pageRuntime.Evaluate(ctx, &runtime.EvaluateArgs{ Expression: scriptContent, }) if err != nil { @@ -119,8 +143,11 @@ func (cdpPlugin *cdpPlugin) evaluateJsScript(scriptContent string) error { return nil } -func (cdpLocatr *cdpLocatr) GetLocatrStr(userReq string) (*locatr.LocatrOutput, error) { - locatrOutput, err := cdpLocatr.locatr.GetLocatorStr(userReq) +func (cdpLocatr *cdpLocatr) GetLocatrStr(ctx context.Context, userReq string) (*locatr.LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "GetLocatrStr") + defer span.End() + + locatrOutput, err := cdpLocatr.locatr.GetLocatorStr(ctx, userReq) if err != nil { return nil, fmt.Errorf("error getting locator string: %w", err) } @@ -145,7 +172,7 @@ func filterTargets(pages []*devtool.Target) []*devtool.Target { return newTargets } -func getWebsocketDebugUrl(url string, tabIndex int, ctx context.Context) (string, error) { +func getWebsocketDebugUrl(ctx context.Context, url string, tabIndex int) (string, error) { devt := devtool.New(url) targets, err := devt.List(ctx) if err != nil { @@ -159,18 +186,29 @@ func getWebsocketDebugUrl(url string, tabIndex int, ctx context.Context) (string } return "", fmt.Errorf("tab with index %d not present in the browser", tabIndex) } -func (cPlugin *cdpPlugin) GetCurrentContext() string { - if value, err := cPlugin.evaluateJsFunction("window.location.href"); err == nil { + +func (cPlugin *cdpPlugin) GetCurrentContext(ctx context.Context) string { + ctx, span := tracing.StartSpan(ctx, "GetCurrentContext") + defer span.End() + + span.AddEvent("fetching window location") + if value, err := cPlugin.evaluateJsFunction(ctx, "window.location.href"); err == nil { return value } else { return "" } } -func (cPlugin *cdpPlugin) IsValidLocator(locatrString string) (bool, error) { - if err := cPlugin.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { + +func (cPlugin *cdpPlugin) IsValidLocator(ctx context.Context, locatrString string) (bool, error) { + ctx, span := tracing.StartSpan(ctx, "IsValidLocator") + defer span.End() + + span.AddEvent("injecting HTML minifier script") + if err := cPlugin.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return false, fmt.Errorf("%v : %v", ErrUnableToLoadJsScriptsThroughCdp, err) } - value, err := cPlugin.evaluateJsFunction(fmt.Sprintf("isValidLocator('%s')", locatrString)) + span.AddEvent("evaluating valid locator check") + value, err := cPlugin.evaluateJsFunction(ctx, fmt.Sprintf("isValidLocator('%s')", locatrString)) if value == "true" && err == nil { return true, nil } else { diff --git a/golang/constants.go b/golang/constants.go index 69c36cf..22cb337 100644 --- a/golang/constants.go +++ b/golang/constants.go @@ -5,7 +5,7 @@ import _ "embed" //go:embed meta/htmlMinifier.js var HTML_MINIFIER_JS_CONTENT string -const LOCATR_PROMPT = `Your task is to identify the HTML element that matches a user's requirement from a given HTML DOM structure and return its unique_id in a JSON format. If the element is not found, provide an appropriate error message in the JSON output. +const LOCATR_PROMPT = `Your task is to identify the HTML element that matches a user's requirement from a given HTML DOM structure and return its unique_id in a JSON format. The element may not match user's requirement exactly. If the element is not found, provide an appropriate error message in the JSON output. Each HTML element may contain an attribute called "data-supported-primitives" which indicates its supported interactions. The following attributes determine whether an element is "clickable", "hoverable", "inputable", or "selectable": diff --git a/golang/llm/llm.go b/golang/llm/llm.go index 4307a49..974aabf 100644 --- a/golang/llm/llm.go +++ b/golang/llm/llm.go @@ -10,6 +10,7 @@ import ( "github.com/openai/openai-go" openai_option "github.com/openai/openai-go/option" "github.com/vertexcover-io/locatr/golang/logger" + "github.com/vertexcover-io/locatr/golang/tracing" "gopkg.in/validator.v2" ) @@ -29,7 +30,7 @@ type llmClient struct { } type LlmClientInterface interface { - ChatCompletion(prompt string) (*ChatCompletionResponse, error) + ChatCompletion(ctx context.Context, prompt string) (*ChatCompletionResponse, error) GetProvider() LlmProvider GetModel() string } @@ -93,23 +94,29 @@ func NewLlmClient(provider LlmProvider, model string, apiKey string) (*llmClient } // ChatCompletion sends a prompt to the LLM model and returns the completion string or and error. -func (c *llmClient) ChatCompletion(prompt string) (*ChatCompletionResponse, error) { +func (c *llmClient) ChatCompletion(ctx context.Context, prompt string) (*ChatCompletionResponse, error) { switch c.provider { case OpenAI, OpenRouter, Groq: - return c.openaiRequest(prompt) + return c.openaiRequest(ctx, prompt) case Anthropic: - return c.anthropicRequest(prompt) + return c.anthropicRequest(ctx, prompt) default: return nil, ErrInvalidProviderForLlm } } -func (c *llmClient) anthropicRequest(prompt string) (*ChatCompletionResponse, error) { +func (c *llmClient) anthropicRequest(ctx context.Context, prompt string) (*ChatCompletionResponse, error) { defer logger.GetTimeLogger("LLM: AnthropicCompletion")() + ctx, span := tracing.StartSpan(ctx, "anthropic completion request") + defer span.End() + start := time.Now() + + span.AddEvent("Message created") + resp, err := c.anthropicClient.CreateMessages( - context.Background(), + ctx, anthropic.MessagesRequest{ Model: c.model, Messages: []anthropic.Message{ @@ -117,9 +124,11 @@ func (c *llmClient) anthropicRequest(prompt string) (*ChatCompletionResponse, er }, MaxTokens: MAX_TOKENS, }) + span.AddEvent("Response Received") if err != nil { return nil, err } + completionResponse := ChatCompletionResponse{ Prompt: prompt, Completion: resp.Content[0].GetText(), @@ -133,12 +142,19 @@ func (c *llmClient) anthropicRequest(prompt string) (*ChatCompletionResponse, er return &completionResponse, nil } -func (c *llmClient) openaiRequest(prompt string) (*ChatCompletionResponse, error) { +func (c *llmClient) openaiRequest(ctx context.Context, prompt string) (*ChatCompletionResponse, error) { defer logger.GetTimeLogger("LLM: OpenAICompletion")() + ctx, span := tracing.StartSpan(ctx, "openai completion request") + defer span.End() + start := time.Now() + + span.AddEvent("chat completion request") + resp, err := c.openaiClient.Chat.Completions.New( - context.Background(), openai.ChatCompletionNewParams{ + ctx, + openai.ChatCompletionNewParams{ Model: openai.F(c.model), Messages: openai.F([]openai.ChatCompletionMessageParamUnion{ openai.UserMessage(prompt), @@ -150,9 +166,11 @@ func (c *llmClient) openaiRequest(prompt string) (*ChatCompletionResponse, error ), }, ) + span.AddEvent("chat completion response") if err != nil { return nil, err } + completionResponse := ChatCompletionResponse{ Prompt: prompt, Completion: resp.Choices[0].Message.Content, diff --git a/golang/minifier/adapter.go b/golang/minifier/adapter.go new file mode 100644 index 0000000..6f10f50 --- /dev/null +++ b/golang/minifier/adapter.go @@ -0,0 +1,101 @@ +package minifier + +import "github.com/antchfx/xmlquery" + +type XMLDoc struct { + root *xmlquery.Node +} + +func NewXMLDoc(root *xmlquery.Node) *XMLDoc { + return &XMLDoc{ + root: root, + } +} + +func (d *XMLDoc) Find(xpath string) []Node { + var nodes []Node + elems := xmlquery.Find(d.root, xpath) + + for _, elem := range elems { + node := NewXMLNode(elem) + nodes = append(nodes, node) + } + return nodes +} + +func (d *XMLDoc) Root() *XMLNode { + return NewXMLNode(d.root) +} + +type XMLNode struct { + node *xmlquery.Node +} + +func NewXMLNode(node *xmlquery.Node) *XMLNode { + return &XMLNode{ + node: node, + } +} + +func (n XMLNode) TagName() string { + return n.node.Data +} + +func (n XMLNode) IsElement() bool { + return n.node.Type == xmlquery.ElementNode +} + +func (n *XMLNode) HasParent() bool { + return n.node.Parent != nil +} + +func (n *XMLNode) GetAttribute(key string) string { + for _, attr := range n.node.Attr { + if attr.Name.Local == key { + return attr.Value + } + } + return "" +} + +func (n *XMLNode) GetParent() Node { + return NewXMLNode(n.node.Parent) +} + +func (n *XMLNode) ChildNodes() []Node { + var nodes []Node + + for c := n.node.FirstChild; c != nil; c = c.NextSibling { + xn := NewXMLNode(c) + nodes = append(nodes, xn) + } + return nodes +} + +func (n *XMLNode) Equal(n1 Node) bool { + xn1, ok := n1.(*XMLNode) + if !ok { + return false + } + + return n.node == xn1.node +} + +func (n *XMLNode) Index() int { + if n.node.Parent == nil { + return 1 + } + + idx := 0 + parent := n.node.Parent + for c := parent.FirstChild; c != nil; c = c.NextSibling { + if c.Type == xmlquery.ElementNode && c.Data == n.node.Data { + idx += 1 + if c == n.node { + return idx + } + + } + } + return 1 +} diff --git a/golang/appium/xmlMinifier.go b/golang/minifier/xmlMinifier.go similarity index 86% rename from golang/appium/xmlMinifier.go rename to golang/minifier/xmlMinifier.go index 57abf5b..484af4d 100644 --- a/golang/appium/xmlMinifier.go +++ b/golang/minifier/xmlMinifier.go @@ -1,4 +1,4 @@ -package appiumLocatr +package minifier import ( "crypto/md5" @@ -266,6 +266,12 @@ func isValidElement( if element.Data == "hierarchy" { return true } + // this check is essential, in iOS, there are cases where the parent heirarchy is marked as + // not visible, despite having children as visible. In case of iOS, we can't trust on + // element visibility. + if element.FirstChild != nil { + return true + } visible := isElementVisible(element, platform) return visible } @@ -326,11 +332,14 @@ func createElementSpec( return nil, fmt.Errorf("not a valid element") } text := getVisibleText(element, platform) - locatrs := getElementLocatrs(element) - uniqueId := "" - if len(locatrs) > 0 { - uniqueId = generateUniqueId(locatrs[0]) - } + doc := NewXMLDoc(root) + xn := NewXMLNode(element) + xpath := GetOptimalXPath(doc, xn) + // locatrs := getElementLocatrs(element) + // if len(locatrs) > 0 { + // uniqueId = generateUniqueId(locatrs[0]) + // } + uniqueId := generateUniqueId(xpath) children := []elementSpec.ElementSpec{} for child := element.FirstChild; child != nil; child = child.NextSibling { c, err := createElementSpec(child, root, platform) @@ -347,7 +356,7 @@ func createElementSpec( }, nil } -func minifySource(source string, platform string) (*elementSpec.ElementSpec, error) { +func MinifyXMLSource(source string, platform string) (*elementSpec.ElementSpec, error) { if source == "" { return nil, fmt.Errorf("source is empty") } @@ -367,7 +376,7 @@ func minifySource(source string, platform string) (*elementSpec.ElementSpec, err return spec, nil } -func mapElementsToJson(source string, platform string) (*elementSpec.IdToLocatorMap, error) { +func MapXMLElementsToJson(source string, platform string) (*elementSpec.IdToLocatorMap, error) { if source == "" { return nil, fmt.Errorf("source is empty") } @@ -376,14 +385,31 @@ func mapElementsToJson(source string, platform string) (*elementSpec.IdToLocator return nil, err } elementMap := make(elementSpec.IdToLocatorMap) + var processElement func(*xmlquery.Node) - processElement = func(element *xmlquery.Node) { - locatrs := getElementLocatrs(element) - if len(locatrs) != 0 { - uniqueId := generateUniqueId(locatrs[0]) - elementMap[uniqueId] = locatrs + // processElement = func(element *xmlquery.Node) { + // locatrs := getElementLocatrs(element) + // if len(locatrs) != 0 { + // uniqueId := generateUniqueId(locatrs[0]) + // elementMap[uniqueId] = locatrs + // } + // for child := element.FirstChild; child != nil; child = child.NextSibling { + // if isValidElement(child, platform) { + // processElement(child) + // } + // } + // } + // processElement(findFirstElementNode(root)) + doc := NewXMLDoc(root) + processElement = func(elem *xmlquery.Node) { + xn := NewXMLNode(elem) + xpath := GetOptimalXPath(doc, xn) + if xpath != "" { + uniqueId := generateUniqueId(xpath) + elementMap[uniqueId] = []string{xpath} } - for child := element.FirstChild; child != nil; child = child.NextSibling { + + for child := elem.FirstChild; child != nil; child = child.NextSibling { if isValidElement(child, platform) { processElement(child) } diff --git a/golang/appium/xmlMinifier_test.go b/golang/minifier/xmlMinifier_test.go similarity index 97% rename from golang/appium/xmlMinifier_test.go rename to golang/minifier/xmlMinifier_test.go index 0a5fcd8..d4d4e05 100644 --- a/golang/appium/xmlMinifier_test.go +++ b/golang/minifier/xmlMinifier_test.go @@ -1,4 +1,4 @@ -package appiumLocatr +package minifier import ( "encoding/xml" @@ -17,7 +17,7 @@ func TestFindFirstElementNode(t *testing.T) { result := findFirstElementNode(doc) if result == nil { - t.Errorf("Expected to find an element node, got nil") + t.Fatal("Expected to find an element node, got nil") } if result.Data != "root" { t.Errorf("Expected 'root' element, got %s", result.Data) @@ -31,7 +31,7 @@ func TestFindFirstElementNode(t *testing.T) { result := findFirstElementNode(doc) if result == nil { - t.Errorf("Expected to find an element node, got nil") + t.Fatal("Expected to find an element node, got nil") } if result.Data != "root" { t.Errorf("Expected 'root' element, got %s", result.Data) @@ -45,7 +45,7 @@ func TestFindFirstElementNode(t *testing.T) { result := findFirstElementNode(doc) if result == nil { - t.Errorf("Expected to find an element node, got nil") + t.Fatal("Expected to find an element node, got nil") } if result.Data != "root" { t.Errorf("Expected 'root' element, got %s", result.Data) @@ -88,7 +88,7 @@ func TestFindFirstElementNode(t *testing.T) { result := findFirstElementNode(doc) if result == nil { - t.Errorf("Expected to find an element node, got nil") + t.Fatal("Expected to find an element node, got nil") } if result.Data != "root" { t.Errorf("Expected 'root' element, got %s", result.Data) @@ -635,14 +635,14 @@ func TestMinifySource(t *testing.T) { t.Run(tt.name, func(t *testing.T) { if tt.xml == "" { - _, err := minifySource(tt.xml, tt.platform) + _, err := MinifyXMLSource(tt.xml, tt.platform) if err == nil { t.Error("Expected error for empty source") } return } - spec, err := minifySource(tt.xml, tt.platform) + spec, err := MinifyXMLSource(tt.xml, tt.platform) if tt.wantErr { if err == nil { t.Error("Expected error, got none") @@ -692,14 +692,14 @@ func TestMapElementsToJson(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.xml == "" { - _, err := mapElementsToJson(tt.xml, tt.platform) + _, err := MapXMLElementsToJson(tt.xml, tt.platform) if err == nil { t.Error("Expected error for empty source") } return } - elementMap, err := mapElementsToJson(tt.xml, tt.platform) + elementMap, err := MapXMLElementsToJson(tt.xml, tt.platform) if tt.wantErr { if err == nil { t.Error("Expected error, got none") diff --git a/golang/minifier/xpath.go b/golang/minifier/xpath.go new file mode 100644 index 0000000..40647e6 --- /dev/null +++ b/golang/minifier/xpath.go @@ -0,0 +1,211 @@ +package minifier + +import ( + "fmt" + "slices" + "strings" +) + +type Document interface { + Find(xpath string) []Node +} + +type Node interface { + TagName() string + IsElement() bool + HasParent() bool + GetAttribute(key string) string + GetParent() Node + ChildNodes() []Node + Index() int + Equal(Node) bool +} + +var ( + // Attributes on nodes that are likely to be unique to the node. These are considered in order + unique_xpath_attrs = []string{"name", "content-desc", "id", "resource-id", "accessibility-id"} + + // Attributes that are recommended as fallback but ideally only in conjunction with other + // attributes + maybe_unique_xpath_attrs = []string{"label", "text", "value"} +) + +func GetOptimalXPath(doc Document, domNode Node) string { + // BASE CASE #1: If this isn't an element, we're above the root, return empty string + if domNode == nil || domNode.TagName() == "" || !domNode.IsElement() { + return "" + } + + attrPairs := generateAttrPairs() + + cases := [][]string{ + // BASE CASE #2: If the node has a unique attribute or content attribute, return an absolute + // XPath with that attribute + unique_xpath_attrs, + + // BASE CASE #3: If the node has a unique pair of attributes including 'maybe' attributes, + // return an XPATH based on that pair + attrPairs, + + // BASE CASE #4: Look for 'maybe' unique attributes on its own. It's better than if we find one + // of these that's unique in conjunction with another attribute, but if not, it is still better + // than hierarchial query. + maybe_unique_xpath_attrs, + + // BASE CASE #5: Look to see if the node type is unique in the document + {}, + } + + // It's possible that in all of these cases we don't find a truly unique selector. But a selector + // qualified by attribute with an index attached, like //*[@id="foo"][1], which is still better + // than a fully path-based selector. + var semiUniqueXpath string + + for _, attrCase := range cases { + ok, isUnique, xpath := getUniqueXPATH(doc, domNode, attrCase) + if !ok { + continue + } + + if isUnique { + return xpath + } else if semiUniqueXpath == "" { + semiUniqueXpath = xpath + } + } + + // once we have gone through all our cases, if we do still have a semi unique xpath, send that back + if semiUniqueXpath != "" { + return semiUniqueXpath + } + + // otherwise fall back to a purely hierarchial expression of this dom node's position in the + // document as a last resort. + // First get the relative xpath of this node using tagname + xpath := fmt.Sprintf("/%s", domNode.TagName()) + + // if this node has siblings of the same tagname, get the index of this node + if domNode.HasParent() { + var siblings []Node + for _, child := range domNode.GetParent().ChildNodes() { + if child.IsElement() && child.TagName() == domNode.TagName() { + siblings = append(siblings, child) + } + } + + // If there's more than one sibling, append the index + if len(siblings) > 1 { + idx := domNode.Index() + + xpath = fmt.Sprintf("%s[%d]", xpath, idx) + } + + } + + // Make a recursive call to this nodes parents and preprend it to this xpath + parentXPath := GetOptimalXPath(doc, domNode.GetParent()) + return parentXPath + xpath +} + +func generateAttrPairs() []string { + var attrsForPairs []string + attrsForPairs = append(attrsForPairs, unique_xpath_attrs...) + attrsForPairs = append(attrsForPairs, maybe_unique_xpath_attrs...) + + var attrPairs []string + for i, attr := range attrsForPairs { + for j := i + 1; j < len(attrsForPairs); j += 1 { + pair := fmt.Sprintf("%s %s", attr, attrsForPairs[j]) + attrPairs = append(attrPairs, pair) + } + } + + return attrPairs +} + +func getUniqueXPATH(doc Document, domNode Node, attrs []string) (valid bool, unique bool, xpath string) { + isNodeName := len(attrs) == 0 + if isNodeName { + xpath := fmt.Sprintf("//%s", domNode.TagName()) + + isUnique, _ := determineXpathUniqueness(xpath, doc, domNode) + if isUnique { + if !domNode.HasParent() { + xpath = fmt.Sprintf("/%s", domNode.TagName()) + } + return true, true, xpath + } + return false, false, "" + } + + var uniqueXPath string + var semiUniqueXPath string + + tagForXpath := domNode.TagName() + if tagForXpath == "" { + tagForXpath = "*" + } + isPair := len(strings.Fields(attrs[0])) > 1 + + for _, attr := range attrs { + var xpath string + + if isPair { + attr1, attr2, ok := strings.Cut(attr, " ") + if !ok { + panic("generateUniqueXPATH invalid state") + } + + attr1Value, attr2Value := domNode.GetAttribute(attr1), domNode.GetAttribute(attr2) + if attr1Value == "" || attr2Value == "" { + continue + } + + xpath = fmt.Sprintf( + "//%s[@%s=\"%s\" and @%s=\"%s\"]", + tagForXpath, + attr1, attr1Value, + attr2, attr2Value, + ) + } else { + attrValue := domNode.GetAttribute(attr) + if attrValue == "" { + continue + } + xpath = fmt.Sprintf( + "//%s[@%s=\"%s\"]", + tagForXpath, + attr, attrValue, + ) + } + + isUnique, idx := determineXpathUniqueness(xpath, doc, domNode) + if isUnique { + uniqueXPath = xpath + break + } + + if semiUniqueXPath == "" { + semiUniqueXPath = fmt.Sprintf("(%s)[%d]", xpath, idx) + } + } + + if uniqueXPath != "" { + return true, true, uniqueXPath + } + if semiUniqueXPath != "" { + return true, false, semiUniqueXPath + } + return false, false, "" +} + +func determineXpathUniqueness(xpath string, doc Document, domNode Node) (bool, int) { + elems := doc.Find(xpath) + if len(elems) > 1 { + idx := slices.IndexFunc(elems, func(node Node) bool { + return domNode.Equal(node) + }) + return false, idx + 1 + } + return true, 0 +} diff --git a/golang/playwrightLocatr/playwright.go b/golang/playwrightLocatr/playwright.go index e366880..4125c30 100644 --- a/golang/playwrightLocatr/playwright.go +++ b/golang/playwrightLocatr/playwright.go @@ -1,6 +1,7 @@ package playwrightLocatr import ( + "context" "encoding/json" "errors" "fmt" @@ -8,6 +9,7 @@ import ( "github.com/playwright-community/playwright-go" locatr "github.com/vertexcover-io/locatr/golang" "github.com/vertexcover-io/locatr/golang/elementSpec" + "github.com/vertexcover-io/locatr/golang/tracing" ) type playwrightPlugin struct { @@ -22,7 +24,12 @@ type PlaywrightLocator struct { var ErrUnableToLoadJsScriptsThroughPlaywright = errors.New("unable to load js script through playwright") // NewPlaywrightLocatr creates a new playwright locator. Use the returned struct methods to get locators. -func NewPlaywrightLocatr(page playwright.Page, options locatr.BaseLocatrOptions) *PlaywrightLocator { +func NewPlaywrightLocatr(ctx context.Context, page playwright.Page, options locatr.BaseLocatrOptions) *PlaywrightLocator { + _, span := tracing.StartSpan(ctx, "NewPlaywrightLocatr") + defer span.End() + + span.AddEvent("Creating new playwright plugin") + pwPlugin := &playwrightPlugin{page: page} return &PlaywrightLocator{ @@ -31,34 +38,49 @@ func NewPlaywrightLocatr(page playwright.Page, options locatr.BaseLocatrOptions) } } -func (pl *playwrightPlugin) GetMinifiedDomAndLocatorMap() ( +func (pl *playwrightPlugin) GetMinifiedDomAndLocatorMap(ctx context.Context) ( *elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error, ) { - if err := pl.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { + ctx, span := tracing.StartSpan(ctx, "GetMinifiedDomAndLocatorMap") + defer span.End() + + span.AddEvent("injecting HTML minifier script") + if err := pl.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return nil, nil, "", fmt.Errorf("%v : %v", ErrUnableToLoadJsScriptsThroughPlaywright, err) } - result, err := pl.evaluateJsFunction("minifyHTML()") + + span.AddEvent("evaluating minifyHTML function") + result, err := pl.evaluateJsFunction(ctx, "minifyHTML()") if err != nil { return nil, nil, "", err } elementsSpec := &elementSpec.ElementSpec{} if err := json.Unmarshal([]byte(result), elementsSpec); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal ElementSpec json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal ElementSpec json: %v, expected json, received: %s", + err, + result, + ) } - result, _ = pl.evaluateJsFunction("mapElementsToJson()") + span.AddEvent("evaluating mapElementsToJson function") + result, _ = pl.evaluateJsFunction(ctx, "mapElementsToJson()") idLocatorMap := &elementSpec.IdToLocatorMap{} if err := json.Unmarshal([]byte(result), idLocatorMap); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal IdToLocatorMap json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal IdToLocatorMap json: %v, expected json, received: %s", + err, + result, + ) } return elementsSpec, idLocatorMap, "css selector", nil } // evaluateJsFunction runs the given javascript function in the browser and returns the result as a string. -func (pl *playwrightPlugin) evaluateJsFunction(function string) (string, error) { +func (pl *playwrightPlugin) evaluateJsFunction(ctx context.Context, function string) (string, error) { result, err := pl.page.Evaluate(function) if err != nil { return "", fmt.Errorf("error evaluating js function: %v", err) @@ -80,7 +102,7 @@ func (pl *playwrightPlugin) evaluateJsFunction(function string) (string, error) } // evaluateJsScript runs the given javascript script in the browser. -func (pl *playwrightPlugin) evaluateJsScript(scriptContent string) error { +func (pl *playwrightPlugin) evaluateJsScript(ctx context.Context, scriptContent string) error { if _, err := pl.page.Evaluate(scriptContent); err != nil { fmt.Println("here ---") return err @@ -89,12 +111,16 @@ func (pl *playwrightPlugin) evaluateJsScript(scriptContent string) error { } // GetLocatr returns a playwright locator object for the given user request. -func (pl *PlaywrightLocator) GetLocatr(userReq string) (*locatr.LocatrOutput, error) { +func (pl *PlaywrightLocator) GetLocatr(ctx context.Context, userReq string) (*locatr.LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "GetLocatr") + defer span.End() + + span.AddEvent("waiting for DOM content to load") if err := pl.page.WaitForLoadState(playwright.PageWaitForLoadStateOptions{State: playwright.LoadStateDomcontentloaded}); err != nil { return nil, fmt.Errorf("error waiting for load state: %v", err) } - locatorOutput, err := pl.locatr.GetLocatorStr(userReq) + locatorOutput, err := pl.locatr.GetLocatorStr(ctx, userReq) if err != nil { return nil, fmt.Errorf("error getting locator string: %v", err) } @@ -110,18 +136,29 @@ func (pl *PlaywrightLocator) WriteResultsToFile() { func (pl *PlaywrightLocator) GetLocatrResults() []locatr.LocatrResult { return pl.locatr.GetLocatrResults() } -func (pl *playwrightPlugin) GetCurrentContext() string { - if value, err := pl.evaluateJsFunction("window.location.href"); err == nil { +func (pl *playwrightPlugin) GetCurrentContext(ctx context.Context) string { + ctx, span := tracing.StartSpan(ctx, "GetCurrentContext") + defer span.End() + + span.AddEvent("fetching current window location") + if value, err := pl.evaluateJsFunction(ctx, "window.location.href"); err == nil { return value } else { return "" } } -func (pl *playwrightPlugin) IsValidLocator(locatrString string) (bool, error) { - if err := pl.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { + +func (pl *playwrightPlugin) IsValidLocator(ctx context.Context, locatrString string) (bool, error) { + ctx, span := tracing.StartSpan(ctx, "IsValidLocator") + defer span.End() + + span.AddEvent("injecting HTML minifier script") + if err := pl.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return false, fmt.Errorf("%v : %v", ErrUnableToLoadJsScriptsThroughPlaywright, err) } - value, err := pl.evaluateJsFunction(fmt.Sprintf("isValidLocator('%s')", locatrString)) + + span.AddEvent("evaluating valid locator function") + value, err := pl.evaluateJsFunction(ctx, fmt.Sprintf("isValidLocator('%s')", locatrString)) if value == "true" && err == nil { return true, nil } else { diff --git a/golang/rawxml/rawxmllocator.go b/golang/rawxml/rawxmllocator.go new file mode 100644 index 0000000..b061cea --- /dev/null +++ b/golang/rawxml/rawxmllocator.go @@ -0,0 +1,54 @@ +package rawxml + +import ( + "context" + "fmt" + "io" + + locatr "github.com/vertexcover-io/locatr/golang" +) + +type Platform string + +const ( + PLATFORM_ANDROID Platform = "android" + PLATFORM_IOS Platform = "ios" +) + +type rawtextLocatr struct { + locatr *locatr.BaseLocatr +} + +func NewRawTextLocator( + xml io.Reader, + platform Platform, + opts locatr.BaseLocatrOptions, +) (*rawtextLocatr, error) { + fc, err := io.ReadAll(xml) + if err != nil { + return nil, fmt.Errorf("failed to read from reader: %w", err) + } + + plugin, err := newRawTextPlugin(string(fc), string(platform)) + if err != nil { + return nil, err + } + + baseLocatr := locatr.NewBaseLocatr(plugin, opts) + locatr := &rawtextLocatr{ + locatr: baseLocatr, + } + return locatr, nil +} + +func (lc *rawtextLocatr) GetLocatrStr(ctx context.Context, userReq string) (*locatr.LocatrOutput, error) { + return lc.locatr.GetLocatorStr(ctx, userReq) +} + +func (lc *rawtextLocatr) GetLocatrResults() []locatr.LocatrResult { + return lc.locatr.GetLocatrResults() +} + +func (lc *rawtextLocatr) WriteResultsToFile() { + lc.locatr.WriteLocatrResultsToFile() +} diff --git a/golang/rawxml/rawxmlplugin.go b/golang/rawxml/rawxmlplugin.go new file mode 100644 index 0000000..24b23c7 --- /dev/null +++ b/golang/rawxml/rawxmlplugin.go @@ -0,0 +1,74 @@ +package rawxml + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/antchfx/xmlquery" + locatr "github.com/vertexcover-io/locatr/golang" + "github.com/vertexcover-io/locatr/golang/elementSpec" + "github.com/vertexcover-io/locatr/golang/minifier" +) + +type rawtextPlugin struct { + fileContent string + platform string + doc *xmlquery.Node +} + +func newRawTextPlugin(fileContent, platform string) (*rawtextPlugin, error) { + doc, err := xmlquery.Parse(strings.NewReader(fileContent)) + if err != nil { + return nil, fmt.Errorf("failed to parse xml: %w", err) + } + pl := &rawtextPlugin{ + fileContent: fileContent, + platform: platform, + doc: doc, + } + return pl, nil +} + +func (pl *rawtextPlugin) GetMinifiedDomAndLocatorMap(context.Context) ( + *elementSpec.ElementSpec, + *elementSpec.IdToLocatorMap, + locatr.SelectorType, + error, +) { + pageSource := pl.fileContent + platform := pl.platform + + eSpec, err := minifier.MinifyXMLSource(pageSource, platform) + if err != nil { + return nil, nil, "", fmt.Errorf("failed to minify source: %w", err) + } + locatrMap, err := minifier.MapXMLElementsToJson(pageSource, platform) + if err != nil { + return nil, nil, "", fmt.Errorf("failed to map elements to json: %w", err) + } + + return eSpec, locatrMap, "xpath", nil +} + +func (pl *rawtextPlugin) GetCurrentContext(context.Context) string { + if pl.platform == "android" { + return "DUMMY_ANDROID_CONTEXT" + } + return "" +} + +func (pl *rawtextPlugin) IsValidLocator(ctx context.Context, locatr string) (bool, error) { + elem := xmlquery.Find(pl.doc, locatr) + if len(elem) > 0 { + return true, nil + } + + locatr = "/" + locatr + elem = xmlquery.Find(pl.doc, locatr) + if len(elem) > 0 { + return true, nil + } + return false, errors.New("failed to find valid locator") +} diff --git a/golang/reranker/cohereReRanker.go b/golang/reranker/cohereReRanker.go index 6cde0b0..814cfdd 100644 --- a/golang/reranker/cohereReRanker.go +++ b/golang/reranker/cohereReRanker.go @@ -6,6 +6,7 @@ import ( cohere "github.com/cohere-ai/cohere-go/v2" cohereclient "github.com/cohere-ai/cohere-go/v2/client" + "github.com/vertexcover-io/locatr/golang/tracing" ) var TOP_N_CHUNKS int = 8 @@ -13,7 +14,7 @@ var TOP_N_CHUNKS int = 8 const COHERE_RERANK_MODEL = "rerank-english-v3.0" type ReRankInterface interface { - ReRank(request ReRankRequest) (*[]ReRankResult, error) + ReRank(ctx context.Context, request ReRankRequest) (*[]ReRankResult, error) } type cohereClient struct { @@ -32,15 +33,21 @@ type ReRankRequest struct { Documents []string } -func (c *cohereClient) ReRank(request ReRankRequest) (*[]ReRankResult, error) { +func (c *cohereClient) ReRank(ctx context.Context, request ReRankRequest) (*[]ReRankResult, error) { + ctx, span := tracing.StartSpan(ctx, "ReRank") + defer span.End() + + span.AddEvent("generating request documents") rerankDocs := []*cohere.RerankRequestDocumentsItem{} for _, doc := range request.Documents { rerankDocs = append(rerankDocs, &cohere.RerankRequestDocumentsItem{ String: doc, }) } + + span.AddEvent("initiating rerank") response, err := c.client.Rerank( - context.Background(), + ctx, &cohere.RerankRequest{ Query: request.Query, Model: &c.model, @@ -51,6 +58,8 @@ func (c *cohereClient) ReRank(request ReRankRequest) (*[]ReRankResult, error) { if err != nil { return nil, err } + + span.AddEvent("reading result") results := []ReRankResult{} for _, doc := range response.Results { results = append(results, ReRankResult{ diff --git a/golang/seleniumLocatr/selenium.go b/golang/seleniumLocatr/selenium.go index 6ccc763..dad43e1 100644 --- a/golang/seleniumLocatr/selenium.go +++ b/golang/seleniumLocatr/selenium.go @@ -1,12 +1,14 @@ package seleniumLocatr import ( + "context" "encoding/json" "errors" "fmt" locatr "github.com/vertexcover-io/locatr/golang" "github.com/vertexcover-io/locatr/golang/elementSpec" + "github.com/vertexcover-io/locatr/golang/tracing" "github.com/vertexcover-io/selenium" ) @@ -22,15 +24,24 @@ type seleniumLocatr struct { var ErrUnableToLoadJsScriptSelenium = errors.New("unable to load js script through selenium") // NewRemoteConnSeleniumLocatr Create a new selenium locatr with selenium session. -func NewRemoteConnSeleniumLocatr(serverUrl string, +func NewRemoteConnSeleniumLocatr( + ctx context.Context, + serverUrl string, sessionId string, opt locatr.BaseLocatrOptions, ) (*seleniumLocatr, error) { + _, span := tracing.StartSpan(ctx, "NewRemoteConnSeleniumLocatr") + defer span.End() + + span.AddEvent("Connecting to remote selenium") + wd, err := selenium.ConnectRemote(serverUrl, sessionId) if err != nil { return nil, fmt.Errorf("unable to connect to remote selenium instance: %w", err) } + span.AddEvent("Connection to remote selenium established") + seleniumPlugin := seleniumPlugin{driver: wd} locatr := seleniumLocatr{ driver: wd, @@ -39,8 +50,15 @@ func NewRemoteConnSeleniumLocatr(serverUrl string, return &locatr, nil } -func NewSeleniumLocatr(driver selenium.WebDriver, - options locatr.BaseLocatrOptions) (*seleniumLocatr, error) { +func NewSeleniumLocatr( + ctx context.Context, + driver selenium.WebDriver, + options locatr.BaseLocatrOptions, +) (*seleniumLocatr, error) { + _, span := tracing.StartSpan(ctx, "NewSeleniumLocatr") + defer span.End() + + span.AddEvent("Connecting to selenium driver") plugin := &seleniumPlugin{ driver: driver, } @@ -57,7 +75,7 @@ func (sl *seleniumLocatr) Close() error { return sl.driver.Quit() } -func (sl *seleniumPlugin) evaluateJsFunction(function string) (string, error) { +func (sl *seleniumPlugin) evaluateJsFunction(ctx context.Context, function string) (string, error) { rFunction := "return " + function result, err := sl.driver.ExecuteScript(rFunction, nil) if err != nil { @@ -75,7 +93,7 @@ func (sl *seleniumPlugin) evaluateJsFunction(function string) (string, error) { } } -func (sl *seleniumPlugin) evaluateJsScript(script string) error { +func (sl *seleniumPlugin) evaluateJsScript(ctx context.Context, script string) error { _, err := sl.driver.ExecuteScript(script, nil) if err != nil { return fmt.Errorf("failed to evaluate JS script: %w", err) @@ -83,8 +101,11 @@ func (sl *seleniumPlugin) evaluateJsScript(script string) error { return nil } -func (sl *seleniumLocatr) GetLocatrStr(userReq string) (*locatr.LocatrOutput, error) { - locatorOutput, err := sl.locatr.GetLocatorStr(userReq) +func (sl *seleniumLocatr) GetLocatrStr(ctx context.Context, userReq string) (*locatr.LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "GetLocatrStr") + defer span.End() + + locatorOutput, err := sl.locatr.GetLocatorStr(ctx, userReq) if err != nil { return nil, fmt.Errorf("error getting locator string: %w", err) } @@ -99,45 +120,70 @@ func (pl *seleniumLocatr) GetLocatrResults() []locatr.LocatrResult { return pl.locatr.GetLocatrResults() } -func (sl *seleniumPlugin) GetMinifiedDomAndLocatorMap() ( +func (sl *seleniumPlugin) GetMinifiedDomAndLocatorMap(ctx context.Context) ( *elementSpec.ElementSpec, *elementSpec.IdToLocatorMap, locatr.SelectorType, error, ) { - if err := sl.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { + ctx, span := tracing.StartSpan(ctx, "GetMinifiedDomAndLocatorMap") + defer span.End() + + span.AddEvent("injecting HTML minifer script") + if err := sl.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return nil, nil, "", fmt.Errorf("%v : %v", ErrUnableToLoadJsScriptSelenium, err) } - result, err := sl.evaluateJsFunction("minifyHTML()") + + span.AddEvent("evaluating minifyHTML function") + result, err := sl.evaluateJsFunction(ctx, "minifyHTML()") if err != nil { return nil, nil, "", err } elementsSpec := &elementSpec.ElementSpec{} if err := json.Unmarshal([]byte(result), elementsSpec); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal ElementSpec json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal ElementSpec json: %v, expected json, received: %s", + err, + result, + ) } - result, _ = sl.evaluateJsFunction("mapElementsToJson()") + span.AddEvent("evaluating mapElementsToJson function") + result, _ = sl.evaluateJsFunction(ctx, "mapElementsToJson()") idLocatorMap := &elementSpec.IdToLocatorMap{} if err := json.Unmarshal([]byte(result), idLocatorMap); err != nil { - return nil, nil, "", fmt.Errorf("failed to unmarshal IdToLocatorMap json: %v", err) + return nil, nil, "", fmt.Errorf( + "failed to unmarshal IdToLocatorMap json: %v, expected json, received: %s", + err, + result, + ) } return elementsSpec, idLocatorMap, "css selector", nil } -func (sl *seleniumPlugin) GetCurrentContext() string { - if value, err := sl.evaluateJsFunction("window.location.href"); err == nil { +func (sl *seleniumPlugin) GetCurrentContext(ctx context.Context) string { + ctx, span := tracing.StartSpan(ctx, "GetCurrentContext") + defer span.End() + + span.AddEvent("fetching current window location") + if value, err := sl.evaluateJsFunction(ctx, "window.location.href"); err == nil { return value } else { return "" } } -func (sl *seleniumPlugin) IsValidLocator(locatrString string) (bool, error) { - if err := sl.evaluateJsScript(locatr.HTML_MINIFIER_JS_CONTENT); err != nil { +func (sl *seleniumPlugin) IsValidLocator(ctx context.Context, locatrString string) (bool, error) { + ctx, span := tracing.StartSpan(ctx, "IsValidLocator") + defer span.End() + + span.AddEvent("injecting HTML minifier script") + if err := sl.evaluateJsScript(ctx, locatr.HTML_MINIFIER_JS_CONTENT); err != nil { return false, fmt.Errorf("%v : %v", ErrUnableToLoadJsScriptSelenium, err) } - value, err := sl.evaluateJsFunction(fmt.Sprintf("isValidLocator('%s')", locatrString)) + + span.AddEvent("evaluating isValidLocator function") + value, err := sl.evaluateJsFunction(ctx, fmt.Sprintf("isValidLocator('%s')", locatrString)) if value == "true" && err == nil { return true, nil } else { diff --git a/golang/tracing/config.go b/golang/tracing/config.go new file mode 100644 index 0000000..55fd394 --- /dev/null +++ b/golang/tracing/config.go @@ -0,0 +1,47 @@ +package tracing + +type Option interface { + applyOption(*config) *config +} + +type optionFunc func(*config) *config + +func (optFnc optionFunc) applyOption(cfg *config) *config { + return optFnc(cfg) +} + +type config struct { + endpoint string + svcName string + insecure bool +} + +func WithEndpoint(endpoint string) Option { + return optionFunc(func(c *config) *config { + c.endpoint = endpoint + return c + }) +} + +func WithSVCName(svcName string) Option { + return optionFunc(func(c *config) *config { + c.svcName = svcName + return c + }) +} + +func WithInsecure(insecure bool) Option { + return optionFunc(func(c *config) *config { + c.insecure = insecure + return c + }) +} + +func WithDefaults() Option { + return optionFunc(func(c *config) *config { + c.endpoint = DEFAULT_ENDPOINT + c.svcName = DEFAULT_SVC_NAME + c.insecure = DEFAULT_INSECURE + return c + }) +} diff --git a/golang/tracing/defaults.go b/golang/tracing/defaults.go new file mode 100644 index 0000000..a3aaad4 --- /dev/null +++ b/golang/tracing/defaults.go @@ -0,0 +1,9 @@ +package tracing + +const ( + DEFAULT_ENDPOINT = "localhost:4317" + DEFAULT_SVC_NAME = "autohealer-locator" + DEFAULT_INSECURE = true + + DEFAULT_TRACE_NAME = "autohealer-locator-trace" +) diff --git a/golang/tracing/trace.go b/golang/tracing/trace.go new file mode 100644 index 0000000..d8c77bb --- /dev/null +++ b/golang/tracing/trace.go @@ -0,0 +1,159 @@ +package tracing + +import ( + "context" + "errors" + "fmt" + "log/slog" + + "github.com/vertexcover-io/locatr/golang/logger" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + "go.opentelemetry.io/otel/trace" +) + +var ErrOtelGRPCExporter = errors.New("OTEL GRPC exporter failed") +var ErrOtelTraceProvider = errors.New("OTEL Trace Provider create failed") + +type OtelShutdownFunc func(context.Context) error + +func NewGRPCExporter(ctx context.Context, endpoint string, insecure bool) (*otlptrace.Exporter, error) { + var opts []otlptracegrpc.Option + + logger.Logger.Debug("setting up GRPC exporter") + + opts = append(opts, otlptracegrpc.WithEndpoint(endpoint)) + if insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + + exporter, err := otlptracegrpc.New( + ctx, + opts..., + ) + if err != nil { + logger.Logger.Warn("failed to create OTLP gRPC exporter") + return nil, fmt.Errorf("failed to create: %w: %w", ErrOtelGRPCExporter, err) + } + + return exporter, nil +} + +func NewTraceProvider(exp sdktrace.SpanExporter, svcName string) (*sdktrace.TracerProvider, error) { + r, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(svcName), + ), + ) + if err != nil { + logger.Logger.Warn("failed to create Otel Trace Provider") + return nil, fmt.Errorf("failed to create resource: %w: %w", ErrOtelTraceProvider, err) + } + + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithResource(r), + ) + return tp, nil +} + +var setupRun bool = false + +func SetupOtelSDK(ctx context.Context, opts ...Option) (OtelShutdownFunc, error) { + var shutdown OtelShutdownFunc + + cfg := getConfig(opts) + + logger.Logger.Debug( + "setting up Open telemetry SDK", + slog.String("endpoint", cfg.endpoint), + slog.String("svc-name", cfg.svcName), + slog.Bool("insecure", cfg.insecure), + ) + + exp, err := NewGRPCExporter(ctx, cfg.endpoint, cfg.insecure) + if err != nil { + return shutdown, err + } + + tp, err := NewTraceProvider(exp, cfg.svcName) + if err != nil { + return shutdown, err + } + otel.SetTracerProvider(tp) + shutdown = tp.Shutdown + + logger.Logger.Info( + "Open Telemetry SDK setup complete", + ) + + setupRun = true + + return shutdown, err +} + +func getConfig(opts []Option) config { + if len(opts) == 0 { + opts = append(opts, WithDefaults()) + } + + var cfg config + for _, opt := range opts { + cfg = *opt.applyOption(&cfg) + } + return cfg +} + +func GetTracer() trace.Tracer { + tp := otel.GetTracerProvider() + if tp == nil { + panic("ensure setup is called before fetching trace provider") + } + return tp.Tracer(DEFAULT_TRACE_NAME) +} + +func StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { + if setupRun { + tracer := GetTracer() + return tracer.Start(ctx, name, opts...) + } + return ctx, &noopSpan{} +} + +type noopSpan struct { + trace.Span +} + +func (n *noopSpan) End(opt ...trace.SpanEndOption) {} + +func (n *noopSpan) AddEvent(name string, opt ...trace.EventOption) {} + +func (n *noopSpan) AddLink(link trace.Link) {} + +func (n *noopSpan) IsRecording() bool { return false } + +func (n *noopSpan) RecordError(err error, opt ...trace.EventOption) {} + +func (n *noopSpan) SpanContext() trace.SpanContext { + return trace.NewSpanContext(trace.SpanContextConfig{}) +} + +func (n *noopSpan) SetStatus(code codes.Code, description string) {} + +func (n *noopSpan) SetName(name string) {} + +func (n *noopSpan) SetAttributes(kv ...attribute.KeyValue) {} + +func (n *noopSpan) TracerProvider() trace.TracerProvider { return &noopTracerProvider{} } + +type noopTracerProvider struct { + trace.TracerProvider +} diff --git a/pyproject.toml b/pyproject.toml index 0faa406..b3bd472 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,47 +3,29 @@ requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build] -include = [ - "python_client/*", - "LICENSE", -] +include = ["python_client/*", "LICENSE"] [tool.hatch.build.targets.wheel] packages = ["python_client/locatr"] [tool.hatch.build.targets.sdist] -include = [ - "python_client/locatr/bin/locatr.bin", - "python_client/", -] +include = ["python_client/locatr/bin/locatr.bin", "python_client/"] ignore-vcs = true -exclude = [ - "*.cache" -] +exclude = ["*.cache"] [project] name = "test_locatr" -version = "0.42.0" +version = "0.56.2" description = "Get HTML/XML elements css/xpath selectors using natural language." readme = "python_client/README.md" requires-python = ">=3.9" -dependencies = [ - "pydantic>=2.10.4", -] -authors = [{ name = "Neeraj319", email = "neeraj@vertexcover.io"}] -include = [ - "LICENSE", - "python_client/locatr/bin/locatr.bin" -] +dependencies = ["pydantic>=2.10.4"] +authors = [{ name = "Neeraj319", email = "neeraj@vertexcover.io" }] +include = ["LICENSE", "python_client/locatr/bin/locatr.bin"] license = "MIT" [dependency-groups] -dev = [ - "pytest-cov>=6.0.0", - "pre-commit>=4.1.0", - "pytest>=8.3.4", - "ruff>=0.8.6", -] +dev = ["pytest-cov>=6.0.0", "pre-commit>=4.1.0", "pytest>=8.3.4", "ruff>=0.8.6"] [tool.ruff] line-length = 80 @@ -57,12 +39,12 @@ omit = ["python_client/tests/*"] [tool.coverage.report] exclude_lines = [ - "pragma: no cover", - "def __repr__", - "raise NotImplementedError", - "if __name__ == .__main__.:", - "pass", - "raise ImportError", + "pragma: no cover", + "def __repr__", + "raise NotImplementedError", + "if __name__ == .__main__.:", + "pass", + "raise ImportError", ] [tool.uv.workspace] diff --git a/python_client/locatr/_locatr.py b/python_client/locatr/_locatr.py index 75e57bc..0e3a2ad 100644 --- a/python_client/locatr/_locatr.py +++ b/python_client/locatr/_locatr.py @@ -47,11 +47,17 @@ def __init__( locatr_settings: Union[ LocatrCdpSettings, LocatrSeleniumSettings, LocatrAppiumSettings ], + tracing_endpoint: str, + tracing_svcname: str, + tracing_insecure: bool, log_level: LogLevel | None = LogLevel.ERROR, ) -> None: self._settings = locatr_settings self._id = uuid.uuid4() self._log_level = log_level + self._tracing_endpoint = tracing_endpoint + self._tracing_svcname = tracing_svcname + self._tracing_insecure = tracing_insecure self._socket = None def _initialize_process_and_socket(self): @@ -66,6 +72,9 @@ def _initialize_process(self): SocketFilePath.path = change_socket_file() args = [ f"-socketFilePath={SocketFilePath.path}", + f"-tracing.endpoint={self._tracing_endpoint}", + f"-tracing.svcName={self._tracing_svcname}", + f"-tracing.insecure={self._tracing_insecure}", ] if self._log_level is not None: args.append(f"-logLevel={self._log_level.value}") @@ -126,10 +135,15 @@ def _recv_message(self) -> bytes: self._socket.close() raise e - def get_locatr(self, user_req: str) -> LocatrOutput: + def get_locatr( + self, user_req: str, otel_parent_trace: Optional[str] = None + ) -> LocatrOutput: self._initialize_process_and_socket() message = UserRequestMessage( - user_request=user_req, id=self._id, type=MessageType.LOCATR_REQUEST + user_request=user_req, + otel_parent_trace_id=otel_parent_trace, + id=self._id, + type=MessageType.LOCATR_REQUEST, ) message_str = message.model_dump_json(exclude_none=True) packed_data = create_packed_message(message_str) @@ -143,8 +157,12 @@ def get_locatr(self, user_req: str) -> LocatrOutput: except ValidationError as e: raise FailedToRetrieveLocatr(str(e.errors())) - async def get_locatr_async(self, user_req: str) -> LocatrOutput: - return await asyncio.to_thread(self.get_locatr, user_req) + async def get_locatr_async( + self, user_req: str, otel_parent_trace: Optional[str] = None + ) -> LocatrOutput: + return await asyncio.to_thread( + self.get_locatr, user_req, otel_parent_trace + ) def __del__(self): if self._socket: diff --git a/python_client/locatr/_utils.py b/python_client/locatr/_utils.py index dfd325a..00396a0 100644 --- a/python_client/locatr/_utils.py +++ b/python_client/locatr/_utils.py @@ -80,11 +80,21 @@ def log_output(process: Popen[bytes]): print("exception while reading process output", e, file=sys.stderr) +# def create_packed_message(message_str: str) -> bytes: +# message_length = len(message_str) +# version_bytes = bytes(VERSION) + +# packed_data = struct.pack(">3B", *version_bytes) + struct.pack( +# f">I{message_length}s", message_length, message_str.encode() +# ) +# return packed_data + + def create_packed_message(message_str: str) -> bytes: - message_length = len(message_str) - version_bytes = bytes(VERSION) - packed_data = struct.pack(">3B", *version_bytes) + struct.pack( - f">I{message_length}s", message_length, message_str.encode() + message_bytes = message_str.encode() + message_length = len(message_bytes) + packed_data = struct.pack(">3B I", *VERSION, message_length) + struct.pack( + f">{message_length}s", message_bytes ) return packed_data diff --git a/python_client/locatr/schema.py b/python_client/locatr/schema.py index 9423fdf..9a47de7 100644 --- a/python_client/locatr/schema.py +++ b/python_client/locatr/schema.py @@ -95,3 +95,4 @@ class InitialHandshakeMessage(Message): class UserRequestMessage(Message): user_request: str + otel_parent_trace_id: Optional[str] diff --git a/server/main.go b/server/main.go index f2055bf..7871a7f 100644 --- a/server/main.go +++ b/server/main.go @@ -1,12 +1,14 @@ package main import ( + "context" "encoding/binary" "encoding/json" "errors" "flag" "fmt" "log/slog" + "sync" "net" "net/url" @@ -22,6 +24,9 @@ import ( "github.com/vertexcover-io/locatr/golang/logger" "github.com/vertexcover-io/locatr/golang/reranker" "github.com/vertexcover-io/locatr/golang/seleniumLocatr" + "github.com/vertexcover-io/locatr/golang/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" ) var VERSION = []uint8{0, 0, 1} @@ -48,6 +53,8 @@ func createLocatrOptions(message incomingMessage) (locatr.BaseLocatrOptions, err llmConfig.ModelName, llmConfig.LlmApiKey, ) + slog.Debug("Using llm model", slog.String("model", llmConfig.ModelName)) + if err != nil { return locatr.BaseLocatrOptions{}, fmt.Errorf("%v : %v", FailedToCreateLlmClient, err) } @@ -56,12 +63,15 @@ func createLocatrOptions(message incomingMessage) (locatr.BaseLocatrOptions, err return opts, nil } -func handleLocatrRequest(message incomingMessage) (*locatr.LocatrOutput, error) { +func handleLocatrRequest(ctx context.Context, message incomingMessage) (*locatr.LocatrOutput, error) { + ctx, span := tracing.StartSpan(ctx, "locator-request") + defer span.End() + baseLocatr, ok := clientAndLocatrs[message.ClientId] if !ok { return nil, fmt.Errorf("%v of id: %s", ErrClientNotInstantiated, message.ClientId) } - locatr, err := baseLocatr.GetLocatrStr(message.UserRequest) + locatr, err := baseLocatr.GetLocatrStr(ctx, message.UserRequest) if err != nil { return nil, fmt.Errorf("%v: %w", ErrFailedToRetrieveLocatr, err) } @@ -69,20 +79,32 @@ func handleLocatrRequest(message incomingMessage) (*locatr.LocatrOutput, error) } -func handleInitialHandshake(message incomingMessage) error { +func handleInitialHandshake(ctx context.Context, message incomingMessage) error { + ctx, span := tracing.StartSpan(ctx, "initial-handshake") + defer span.End() + baseLocatrOpts, err := createLocatrOptions(message) if err != nil { return err } + + span.SetAttributes( + attribute.String("plugin-type", message.Settings.PluginType), + ) + switch message.Settings.PluginType { case "cdp": + span.SetAttributes( + attribute.String("cdp-url", message.Settings.CdpURl), + ) + parsedUrl, _ := url.Parse(message.Settings.CdpURl) port, _ := strconv.Atoi(parsedUrl.Port()) connectionOpts := cdpLocatr.CdpConnectionOptions{ Port: port, HostName: parsedUrl.Hostname(), } - connection, err := cdpLocatr.CreateCdpConnection(connectionOpts) + connection, err := cdpLocatr.CreateCdpConnection(ctx, connectionOpts) if err != nil { return fmt.Errorf("%w: %w", ErrCdpConnectionCreation, err) } @@ -91,16 +113,40 @@ func handleInitialHandshake(message incomingMessage) error { return fmt.Errorf("%w: %w", ErrCdpLocatrCreation, err) } clientAndLocatrs[message.ClientId] = cdpLocatr + case "selenium": settings := message.Settings - seleniumLocatr, err := seleniumLocatr.NewRemoteConnSeleniumLocatr(settings.SeleniumUrl, settings.SeleniumSessionId, baseLocatrOpts) + + span.SetAttributes( + attribute.String("selenium-url", settings.SeleniumUrl), + attribute.String("selenium-session-id", settings.SeleniumSessionId), + ) + + seleniumLocatr, err := seleniumLocatr.NewRemoteConnSeleniumLocatr( + ctx, + settings.SeleniumUrl, + settings.SeleniumSessionId, + baseLocatrOpts, + ) if err != nil { return fmt.Errorf("%w: %w", ErrSeleniumLocatrCreation, err) } clientAndLocatrs[message.ClientId] = seleniumLocatr + case "appium": settings := message.Settings - appiumLocatr, err := appiumLocatr.NewAppiumLocatr(settings.AppiumUrl, settings.AppiumSessionId, baseLocatrOpts) + + span.SetAttributes( + attribute.String("appium-url", settings.AppiumUrl), + attribute.String("appium-session-id", settings.AppiumSessionId), + ) + + appiumLocatr, err := appiumLocatr.NewAppiumLocatr( + ctx, + settings.AppiumUrl, + settings.AppiumSessionId, + baseLocatrOpts, + ) if err != nil { return fmt.Errorf("%w: %w", ErrAppiumLocatrCreation, err) } @@ -191,9 +237,22 @@ func acceptConnection(fd net.Conn) { defer delete(clientAndLocatrs, clientMessage.ClientId) + var ctx context.Context + if clientMessage.OtelParentTraceId != "" { + carrier := propagation.MapCarrier{"traceparent": clientMessage.OtelParentTraceId} + + propagator := propagation.TraceContext{} + ctx = propagator.Extract(context.Background(), carrier) + } else { + ctx = context.Background() + } + + ctx, span := tracing.StartSpan(ctx, "locator-reqest") + defer span.End() + switch clientMessage.Type { case "initial_handshake": - err := handleInitialHandshake(clientMessage) + err := handleInitialHandshake(ctx, clientMessage) if err != nil { errResp := outgoingMessage{ Type: clientMessage.Type, @@ -222,8 +281,9 @@ func acceptConnection(fd net.Conn) { } logger.Logger.Info("Initial handshake successful with client", "clientId", clientMessage.ClientId) + case "locatr_request": - locatrOutput, err := handleLocatrRequest(clientMessage) + locatrOutput, err := handleLocatrRequest(ctx, clientMessage) if err != nil { errResp := outgoingMessage{ Type: clientMessage.Type, @@ -257,45 +317,87 @@ func acceptConnection(fd net.Conn) { } } +type Config struct { + SocketFilePath string + LogLevel int + + Tracing struct { + Endpoint string + ServiceName string + Insecure bool + } +} + func main() { - var socketFilePath string - var logLevel int - flag.StringVar(&socketFilePath, "socketFilePath", "/tmp/locatr.sock", "path to the socket file to listen at.") - flag.IntVar(&logLevel, "logLevel", int(slog.LevelError), "log level for the server") + var cfg Config + flag.StringVar(&cfg.SocketFilePath, "socketFilePath", "/tmp/locatr.sock", "path to the socket file to listen at.") + flag.IntVar(&cfg.LogLevel, "logLevel", int(slog.LevelError), "log level for the server") + flag.StringVar(&cfg.Tracing.Endpoint, "tracing.endpoint", tracing.DEFAULT_ENDPOINT, "gRPC endpoint for otel receiver") + flag.StringVar(&cfg.Tracing.ServiceName, "tracing.svcName", tracing.DEFAULT_SVC_NAME, "name for service to use in Open Telemetry Logs") + flag.BoolVar(&cfg.Tracing.Insecure, "tracing.insecure", tracing.DEFAULT_INSECURE, "is gRPC endpoint insecure") + flag.Parse() - if _, err := os.Stat(socketFilePath); !errors.Is(err, os.ErrNotExist) { - os.Remove(socketFilePath) + shutdown, err := tracing.SetupOtelSDK( + context.Background(), + tracing.WithEndpoint(cfg.Tracing.Endpoint), + tracing.WithSVCName(cfg.Tracing.ServiceName), + tracing.WithInsecure(cfg.Tracing.Insecure), + ) + if err != nil { + logger.Logger.Error( + "Failed to setup Open Telemetry SDK", + slog.Any("error", err), + ) + os.Exit(1) } - if (logLevel == int(slog.LevelDebug)) || - (logLevel == int(slog.LevelInfo)) || - (logLevel == int(slog.LevelWarn)) || - (logLevel == int(slog.LevelError)) { - logger.Level.Set(slog.Level(logLevel)) + defer func() { + if sErr := shutdown(context.Background()); sErr != nil { + err = errors.Join(err, sErr) + logger.Logger.Error( + "Error while shutting down Open Telemetry service", + slog.Any("error", err), + ) + } + }() + + if _, err := os.Stat(cfg.SocketFilePath); !errors.Is(err, os.ErrNotExist) { + os.Remove(cfg.SocketFilePath) + } + if (cfg.LogLevel == int(slog.LevelDebug)) || + (cfg.LogLevel == int(slog.LevelInfo)) || + (cfg.LogLevel == int(slog.LevelWarn)) || + (cfg.LogLevel == int(slog.LevelError)) { + logger.Level.Set(slog.Level(cfg.LogLevel)) } - socket, err := net.Listen("unix", socketFilePath) + socket, err := net.Listen("unix", cfg.SocketFilePath) if err != nil { logger.Logger.Error("Failed connecting to socket", "error", err) os.Exit(1) } defer socket.Close() - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + ctx := context.Background() + + // there is no manual terminate action + ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) + + var wg sync.WaitGroup go func() { - sig := <-sigChan + sig := <-ctx.Done() + logger.Logger.Info("Received signal, shutting down...", "signal", sig) + wg.Wait() - if err := os.Remove(socketFilePath); err != nil { + if err := os.Remove(cfg.SocketFilePath); err != nil { logger.Logger.Error("Failed to remove socket file", "error", err) } - os.Exit(0) }() - logger.Logger.Info("Ready to accept connections", "socketFilePath", socketFilePath) - defer os.Remove(socketFilePath) + logger.Logger.Info("Ready to accept connections", "socketFilePath", cfg.SocketFilePath) + defer os.Remove(cfg.SocketFilePath) for { client, err := socket.Accept() @@ -303,9 +405,12 @@ func main() { logger.Logger.Error("Failed accepting socket", "error", err) continue } + wg.Add(1) go func() { + defer client.Close() + defer wg.Done() + acceptConnection(client) - client.Close() }() } } diff --git a/server/schemas.go b/server/schemas.go index 85bb86e..2aa1b02 100644 --- a/server/schemas.go +++ b/server/schemas.go @@ -21,10 +21,11 @@ type locatrSettings struct { } type incomingMessage struct { - Type string `json:"type" binding:"required,oneof=initial_handshake locatr_request error"` - UserRequest string `json:"user_request"` - ClientId string `json:"id" binding:"required"` - Settings locatrSettings `json:"locatr_settings"` + Type string `json:"type" binding:"required,oneof=initial_handshake locatr_request error"` + UserRequest string `json:"user_request"` + ClientId string `json:"id" binding:"required"` + OtelParentTraceId string `json:"otel_parent_trace_id"` + Settings locatrSettings `json:"locatr_settings"` } type outgoingMessage struct { diff --git a/uv.lock b/uv.lock index 260a297..0ae1f1e 100644 --- a/uv.lock +++ b/uv.lock @@ -1,81 +1,6 @@ version = 1 -requires-python = ">=3.12.0" - -[manifest] -members = [ - "benchmarking", - "test-locatr", -] - -[[package]] -name = "aiohappyeyeballs" -version = "2.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/08/07/508f9ebba367fc3370162e53a3cfd12f5652ad79f0e0bfdf9f9847c6f159/aiohappyeyeballs-2.4.6.tar.gz", hash = "sha256:9b05052f9042985d32ecbe4b59a77ae19c006a78f1344d7fdad69d28ded3d0b0", size = 21726 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/4c/03fb05f56551828ec67ceb3665e5dc51638042d204983a03b0a1541475b6/aiohappyeyeballs-2.4.6-py3-none-any.whl", hash = "sha256:147ec992cf873d74f5062644332c539fcd42956dc69453fe5204195e560517e1", size = 14543 }, -] - -[[package]] -name = "aiohttp" -version = "3.11.12" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohappyeyeballs" }, - { name = "aiosignal" }, - { name = "attrs" }, - { name = "frozenlist" }, - { name = "multidict" }, - { name = "propcache" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/37/4b/952d49c73084fb790cb5c6ead50848c8e96b4980ad806cf4d2ad341eaa03/aiohttp-3.11.12.tar.gz", hash = "sha256:7603ca26d75b1b86160ce1bbe2787a0b706e592af5b2504e12caa88a217767b0", size = 7673175 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/d0/94346961acb476569fca9a644cc6f9a02f97ef75961a6b8d2b35279b8d1f/aiohttp-3.11.12-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e392804a38353900c3fd8b7cacbea5132888f7129f8e241915e90b85f00e3250", size = 704837 }, - { url = "https://files.pythonhosted.org/packages/a9/af/05c503f1cc8f97621f199ef4b8db65fb88b8bc74a26ab2adb74789507ad3/aiohttp-3.11.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8fa1510b96c08aaad49303ab11f8803787c99222288f310a62f493faf883ede1", size = 464218 }, - { url = "https://files.pythonhosted.org/packages/f2/48/b9949eb645b9bd699153a2ec48751b985e352ab3fed9d98c8115de305508/aiohttp-3.11.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dc065a4285307607df3f3686363e7f8bdd0d8ab35f12226362a847731516e42c", size = 456166 }, - { url = "https://files.pythonhosted.org/packages/14/fb/980981807baecb6f54bdd38beb1bd271d9a3a786e19a978871584d026dcf/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddb31f8474695cd61fc9455c644fc1606c164b93bff2490390d90464b4655df", size = 1682528 }, - { url = "https://files.pythonhosted.org/packages/90/cb/77b1445e0a716914e6197b0698b7a3640590da6c692437920c586764d05b/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dec0000d2d8621d8015c293e24589d46fa218637d820894cb7356c77eca3259", size = 1737154 }, - { url = "https://files.pythonhosted.org/packages/ff/24/d6fb1f4cede9ccbe98e4def6f3ed1e1efcb658871bbf29f4863ec646bf38/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3552fe98e90fdf5918c04769f338a87fa4f00f3b28830ea9b78b1bdc6140e0d", size = 1793435 }, - { url = "https://files.pythonhosted.org/packages/17/e2/9f744cee0861af673dc271a3351f59ebd5415928e20080ab85be25641471/aiohttp-3.11.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfe7f984f28a8ae94ff3a7953cd9678550dbd2a1f9bda5dd9c5ae627744c78e", size = 1692010 }, - { url = "https://files.pythonhosted.org/packages/90/c4/4a1235c1df544223eb57ba553ce03bc706bdd065e53918767f7fa1ff99e0/aiohttp-3.11.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a481a574af914b6e84624412666cbfbe531a05667ca197804ecc19c97b8ab1b0", size = 1619481 }, - { url = "https://files.pythonhosted.org/packages/60/70/cf12d402a94a33abda86dd136eb749b14c8eb9fec1e16adc310e25b20033/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1987770fb4887560363b0e1a9b75aa303e447433c41284d3af2840a2f226d6e0", size = 1641578 }, - { url = "https://files.pythonhosted.org/packages/1b/25/7211973fda1f5e833fcfd98ccb7f9ce4fbfc0074e3e70c0157a751d00db8/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a4ac6a0f0f6402854adca4e3259a623f5c82ec3f0c049374133bcb243132baf9", size = 1684463 }, - { url = "https://files.pythonhosted.org/packages/93/60/b5905b4d0693f6018b26afa9f2221fefc0dcbd3773fe2dff1a20fb5727f1/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c96a43822f1f9f69cc5c3706af33239489a6294be486a0447fb71380070d4d5f", size = 1646691 }, - { url = "https://files.pythonhosted.org/packages/b4/fc/ba1b14d6fdcd38df0b7c04640794b3683e949ea10937c8a58c14d697e93f/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a5e69046f83c0d3cb8f0d5bd9b8838271b1bc898e01562a04398e160953e8eb9", size = 1702269 }, - { url = "https://files.pythonhosted.org/packages/5e/39/18c13c6f658b2ba9cc1e0c6fb2d02f98fd653ad2addcdf938193d51a9c53/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:68d54234c8d76d8ef74744f9f9fc6324f1508129e23da8883771cdbb5818cbef", size = 1734782 }, - { url = "https://files.pythonhosted.org/packages/9f/d2/ccc190023020e342419b265861877cd8ffb75bec37b7ddd8521dd2c6deb8/aiohttp-3.11.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9fd9dcf9c91affe71654ef77426f5cf8489305e1c66ed4816f5a21874b094b9", size = 1694740 }, - { url = "https://files.pythonhosted.org/packages/3f/54/186805bcada64ea90ea909311ffedcd74369bfc6e880d39d2473314daa36/aiohttp-3.11.12-cp312-cp312-win32.whl", hash = "sha256:0ed49efcd0dc1611378beadbd97beb5d9ca8fe48579fc04a6ed0844072261b6a", size = 411530 }, - { url = "https://files.pythonhosted.org/packages/3d/63/5eca549d34d141bcd9de50d4e59b913f3641559460c739d5e215693cb54a/aiohttp-3.11.12-cp312-cp312-win_amd64.whl", hash = "sha256:54775858c7f2f214476773ce785a19ee81d1294a6bedc5cc17225355aab74802", size = 437860 }, - { url = "https://files.pythonhosted.org/packages/c3/9b/cea185d4b543ae08ee478373e16653722c19fcda10d2d0646f300ce10791/aiohttp-3.11.12-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:413ad794dccb19453e2b97c2375f2ca3cdf34dc50d18cc2693bd5aed7d16f4b9", size = 698148 }, - { url = "https://files.pythonhosted.org/packages/91/5c/80d47fe7749fde584d1404a68ade29bcd7e58db8fa11fa38e8d90d77e447/aiohttp-3.11.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a93d28ed4b4b39e6f46fd240896c29b686b75e39cc6992692e3922ff6982b4c", size = 460831 }, - { url = "https://files.pythonhosted.org/packages/8e/f9/de568f8a8ca6b061d157c50272620c53168d6e3eeddae78dbb0f7db981eb/aiohttp-3.11.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d589264dbba3b16e8951b6f145d1e6b883094075283dafcab4cdd564a9e353a0", size = 453122 }, - { url = "https://files.pythonhosted.org/packages/8b/fd/b775970a047543bbc1d0f66725ba72acef788028fce215dc959fd15a8200/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5148ca8955affdfeb864aca158ecae11030e952b25b3ae15d4e2b5ba299bad2", size = 1665336 }, - { url = "https://files.pythonhosted.org/packages/82/9b/aff01d4f9716245a1b2965f02044e4474fadd2bcfe63cf249ca788541886/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:525410e0790aab036492eeea913858989c4cb070ff373ec3bc322d700bdf47c1", size = 1718111 }, - { url = "https://files.pythonhosted.org/packages/e0/a9/166fd2d8b2cc64f08104aa614fad30eee506b563154081bf88ce729bc665/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9bd8695be2c80b665ae3f05cb584093a1e59c35ecb7d794d1edd96e8cc9201d7", size = 1775293 }, - { url = "https://files.pythonhosted.org/packages/13/c5/0d3c89bd9e36288f10dc246f42518ce8e1c333f27636ac78df091c86bb4a/aiohttp-3.11.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0203433121484b32646a5f5ea93ae86f3d9559d7243f07e8c0eab5ff8e3f70e", size = 1677338 }, - { url = "https://files.pythonhosted.org/packages/72/b2/017db2833ef537be284f64ead78725984db8a39276c1a9a07c5c7526e238/aiohttp-3.11.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd36749a1035c34ba8d8aaf221b91ca3d111532e5ccb5fa8c3703ab1b967ed", size = 1603365 }, - { url = "https://files.pythonhosted.org/packages/fc/72/b66c96a106ec7e791e29988c222141dd1219d7793ffb01e72245399e08d2/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7442662afebbf7b4c6d28cb7aab9e9ce3a5df055fc4116cc7228192ad6cb484", size = 1618464 }, - { url = "https://files.pythonhosted.org/packages/3f/50/e68a40f267b46a603bab569d48d57f23508801614e05b3369898c5b2910a/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8a2fb742ef378284a50766e985804bd6adb5adb5aa781100b09befdbfa757b65", size = 1657827 }, - { url = "https://files.pythonhosted.org/packages/c5/1d/aafbcdb1773d0ba7c20793ebeedfaba1f3f7462f6fc251f24983ed738aa7/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2cee3b117a8d13ab98b38d5b6bdcd040cfb4181068d05ce0c474ec9db5f3c5bb", size = 1616700 }, - { url = "https://files.pythonhosted.org/packages/b0/5e/6cd9724a2932f36e2a6b742436a36d64784322cfb3406ca773f903bb9a70/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f6a19bcab7fbd8f8649d6595624856635159a6527861b9cdc3447af288a00c00", size = 1685643 }, - { url = "https://files.pythonhosted.org/packages/8b/38/ea6c91d5c767fd45a18151675a07c710ca018b30aa876a9f35b32fa59761/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e4cecdb52aaa9994fbed6b81d4568427b6002f0a91c322697a4bfcc2b2363f5a", size = 1715487 }, - { url = "https://files.pythonhosted.org/packages/8e/24/e9edbcb7d1d93c02e055490348df6f955d675e85a028c33babdcaeda0853/aiohttp-3.11.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:30f546358dfa0953db92ba620101fefc81574f87b2346556b90b5f3ef16e55ce", size = 1672948 }, - { url = "https://files.pythonhosted.org/packages/25/be/0b1fb737268e003198f25c3a68c2135e76e4754bf399a879b27bd508a003/aiohttp-3.11.12-cp313-cp313-win32.whl", hash = "sha256:ce1bb21fc7d753b5f8a5d5a4bae99566386b15e716ebdb410154c16c91494d7f", size = 410396 }, - { url = "https://files.pythonhosted.org/packages/68/fd/677def96a75057b0a26446b62f8fbb084435b20a7d270c99539c26573bfd/aiohttp-3.11.12-cp313-cp313-win_amd64.whl", hash = "sha256:f7914ab70d2ee8ab91c13e5402122edbc77821c66d2758abb53aabe87f013287", size = 436234 }, -] - -[[package]] -name = "aiosignal" -version = "1.3.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "frozenlist" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597 }, -] +revision = 1 +requires-python = ">=3.9" [[package]] name = "annotated-types" @@ -86,83 +11,6 @@ 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 = "anthropic" -version = "0.45.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "distro" }, - { name = "httpx" }, - { name = "jiter" }, - { name = "pydantic" }, - { name = "sniffio" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/74/2b2485fc120da834c0c5be07462541ec082e9fa8851d845f2587e480535a/anthropic-0.45.2.tar.gz", hash = "sha256:32a18b9ecd12c91b2be4cae6ca2ab46a06937b5aa01b21308d97a6d29794fb5e", size = 200901 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/74/86/e81814e542d1eaeec84d2312bec93a99b9ef1d78d9bfae1fc5dd74abdf15/anthropic-0.45.2-py3-none-any.whl", hash = "sha256:ecd746f7274451dfcb7e1180571ead624c7e1195d1d46cb7c70143d2aedb4d35", size = 222797 }, -] - -[[package]] -name = "anyio" -version = "4.8.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/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, -] - -[[package]] -name = "attrs" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, -] - -[[package]] -name = "benchmarking" -version = "0.1.0" -source = { virtual = "benchmarking" } -dependencies = [ - { name = "anthropic" }, - { name = "gradio-client" }, - { name = "json-repair" }, - { name = "pillow" }, - { name = "playwright" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "supabase" }, -] - -[package.metadata] -requires-dist = [ - { name = "anthropic", specifier = ">=0.45.2" }, - { name = "gradio-client", specifier = ">=1.7.0" }, - { name = "json-repair", specifier = ">=0.37.0" }, - { name = "pillow", specifier = ">=11.1.0" }, - { name = "playwright", specifier = ">=1.50.0" }, - { name = "python-dotenv", specifier = ">=1.0.1" }, - { name = "pyyaml", specifier = ">=6.0.2" }, - { name = "supabase", specifier = ">=2.13.0" }, -] - -[[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]] name = "cfgv" version = "3.4.0" @@ -172,41 +20,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 }, ] -[[package]] -name = "charset-normalizer" -version = "3.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, - { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, - { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, - { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, - { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, - { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, - { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, - { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, - { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, - { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, - { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, - { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, - { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, - { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, - { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, - { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, - { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, - { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, - { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, - { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, - { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, - { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, - { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, - { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, - { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, - { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, - { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -222,6 +35,26 @@ version = "7.6.10" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/84/ba/ac14d281f80aab516275012e8875991bb06203957aa1e19950139238d658/coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23", size = 803868 } wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/12/2a2a923edf4ddabdffed7ad6da50d96a5c126dae7b80a33df7310e329a1e/coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78", size = 207982 }, + { url = "https://files.pythonhosted.org/packages/ca/49/6985dbca9c7be3f3cb62a2e6e492a0c88b65bf40579e16c71ae9c33c6b23/coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c", size = 208414 }, + { url = "https://files.pythonhosted.org/packages/35/93/287e8f1d1ed2646f4e0b2605d14616c9a8a2697d0d1b453815eb5c6cebdb/coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a", size = 236860 }, + { url = "https://files.pythonhosted.org/packages/de/e1/cfdb5627a03567a10031acc629b75d45a4ca1616e54f7133ca1fa366050a/coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165", size = 234758 }, + { url = "https://files.pythonhosted.org/packages/6d/85/fc0de2bcda3f97c2ee9fe8568f7d48f7279e91068958e5b2cc19e0e5f600/coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988", size = 235920 }, + { url = "https://files.pythonhosted.org/packages/79/73/ef4ea0105531506a6f4cf4ba571a214b14a884630b567ed65b3d9c1975e1/coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5", size = 234986 }, + { url = "https://files.pythonhosted.org/packages/c6/4d/75afcfe4432e2ad0405c6f27adeb109ff8976c5e636af8604f94f29fa3fc/coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3", size = 233446 }, + { url = "https://files.pythonhosted.org/packages/86/5b/efee56a89c16171288cafff022e8af44f8f94075c2d8da563c3935212871/coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5", size = 234566 }, + { url = "https://files.pythonhosted.org/packages/f2/db/67770cceb4a64d3198bf2aa49946f411b85ec6b0a9b489e61c8467a4253b/coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244", size = 210675 }, + { url = "https://files.pythonhosted.org/packages/8d/27/e8bfc43f5345ec2c27bc8a1fa77cdc5ce9dcf954445e11f14bb70b889d14/coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e", size = 211518 }, + { url = "https://files.pythonhosted.org/packages/85/d2/5e175fcf6766cf7501a8541d81778fd2f52f4870100e791f5327fd23270b/coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3", size = 208088 }, + { url = "https://files.pythonhosted.org/packages/4b/6f/06db4dc8fca33c13b673986e20e466fd936235a6ec1f0045c3853ac1b593/coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43", size = 208536 }, + { url = "https://files.pythonhosted.org/packages/0d/62/c6a0cf80318c1c1af376d52df444da3608eafc913b82c84a4600d8349472/coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132", size = 240474 }, + { url = "https://files.pythonhosted.org/packages/a3/59/750adafc2e57786d2e8739a46b680d4fb0fbc2d57fbcb161290a9f1ecf23/coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f", size = 237880 }, + { url = "https://files.pythonhosted.org/packages/2c/f8/ef009b3b98e9f7033c19deb40d629354aab1d8b2d7f9cfec284dbedf5096/coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994", size = 239750 }, + { url = "https://files.pythonhosted.org/packages/a6/e2/6622f3b70f5f5b59f705e680dae6db64421af05a5d1e389afd24dae62e5b/coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99", size = 238642 }, + { url = "https://files.pythonhosted.org/packages/2d/10/57ac3f191a3c95c67844099514ff44e6e19b2915cd1c22269fb27f9b17b6/coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd", size = 237266 }, + { url = "https://files.pythonhosted.org/packages/ee/2d/7016f4ad9d553cabcb7333ed78ff9d27248ec4eba8dd21fa488254dff894/coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377", size = 238045 }, + { url = "https://files.pythonhosted.org/packages/a7/fe/45af5c82389a71e0cae4546413266d2195c3744849669b0bab4b5f2c75da/coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8", size = 210647 }, + { url = "https://files.pythonhosted.org/packages/db/11/3f8e803a43b79bc534c6a506674da9d614e990e37118b4506faf70d46ed6/coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609", size = 211508 }, { url = "https://files.pythonhosted.org/packages/86/77/19d09ea06f92fdf0487499283b1b7af06bc422ea94534c8fe3a4cd023641/coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853", size = 208281 }, { url = "https://files.pythonhosted.org/packages/b6/67/5479b9f2f99fcfb49c0d5cf61912a5255ef80b6e80a3cddba39c38146cf4/coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078", size = 208514 }, { url = "https://files.pythonhosted.org/packages/15/d1/febf59030ce1c83b7331c3546d7317e5120c5966471727aa7ac157729c4b/coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0", size = 241537 }, @@ -252,18 +85,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/69/b3f2416725621e9f112e74e8470793d5b5995f146f596f133678a633b77e/coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2", size = 248737 }, { url = "https://files.pythonhosted.org/packages/3c/6e/fe899fb937657db6df31cc3e61c6968cb56d36d7326361847440a430152e/coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312", size = 211611 }, { url = "https://files.pythonhosted.org/packages/1c/55/52f5e66142a9d7bc93a15192eba7a78513d2abf6b3558d77b4ca32f5f424/coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d", size = 212781 }, + { url = "https://files.pythonhosted.org/packages/40/41/473617aadf9a1c15bc2d56be65d90d7c29bfa50a957a67ef96462f7ebf8e/coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a", size = 207978 }, + { url = "https://files.pythonhosted.org/packages/10/f6/480586607768b39a30e6910a3c4522139094ac0f1677028e1f4823688957/coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27", size = 208415 }, + { url = "https://files.pythonhosted.org/packages/f1/af/439bb760f817deff6f4d38fe7da08d9dd7874a560241f1945bc3b4446550/coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4", size = 236452 }, + { url = "https://files.pythonhosted.org/packages/d0/13/481f4ceffcabe29ee2332e60efb52e4694f54a402f3ada2bcec10bb32e43/coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f", size = 234374 }, + { url = "https://files.pythonhosted.org/packages/c5/59/4607ea9d6b1b73e905c7656da08d0b00cdf6e59f2293ec259e8914160025/coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25", size = 235505 }, + { url = "https://files.pythonhosted.org/packages/85/60/d66365723b9b7f29464b11d024248ed3523ce5aab958e4ad8c43f3f4148b/coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315", size = 234616 }, + { url = "https://files.pythonhosted.org/packages/74/f8/2cf7a38e7d81b266f47dfcf137fecd8fa66c7bdbd4228d611628d8ca3437/coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90", size = 233099 }, + { url = "https://files.pythonhosted.org/packages/50/2b/bff6c1c6b63c4396ea7ecdbf8db1788b46046c681b8fcc6ec77db9f4ea49/coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d", size = 234089 }, + { url = "https://files.pythonhosted.org/packages/bf/b5/baace1c754d546a67779358341aa8d2f7118baf58cac235db457e1001d1b/coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18", size = 210701 }, + { url = "https://files.pythonhosted.org/packages/b1/bf/9e1e95b8b20817398ecc5a1e8d3e05ff404e1b9fb2185cd71561698fe2a2/coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59", size = 211482 }, + { url = "https://files.pythonhosted.org/packages/a1/70/de81bfec9ed38a64fc44a77c7665e20ca507fc3265597c28b0d989e4082e/coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f", size = 200223 }, ] -[[package]] -name = "deprecation" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, ] [[package]] @@ -276,12 +113,12 @@ wheels = [ ] [[package]] -name = "distro" -version = "1.9.0" +name = "exceptiongroup" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] [[package]] @@ -293,208 +130,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, ] -[[package]] -name = "frozenlist" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, - { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, - { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, - { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, - { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, - { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, - { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, - { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, - { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, - { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, - { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, - { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, - { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, - { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, - { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, - { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, - { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, - { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, - { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, - { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, - { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, - { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, - { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, - { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, - { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, - { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, - { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, - { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, - { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, - { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, - { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, -] - -[[package]] -name = "fsspec" -version = "2025.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/79/68612ed99700e6413de42895aa725463e821a6b3be75c87fcce1b4af4c70/fsspec-2025.2.0.tar.gz", hash = "sha256:1c24b16eaa0a1798afa0337aa0db9b256718ab2a89c425371f5628d22c3b6afd", size = 292283 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/94/758680531a00d06e471ef649e4ec2ed6bf185356a7f9fbfbb7368a40bd49/fsspec-2025.2.0-py3-none-any.whl", hash = "sha256:9de2ad9ce1f85e1931858535bc882543171d197001a0a5eb2ddc04f1781ab95b", size = 184484 }, -] - -[[package]] -name = "gotrue" -version = "2.11.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx", extra = ["http2"] }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/c3/c53a34f3fa23c2840f124df028c7757ef58e5c775efd6347bcf37a5e00b9/gotrue-2.11.3.tar.gz", hash = "sha256:14b03eb856b94a96fab73c8d41970ad645a74326ee4da95e66395e6b2c208ff7", size = 41872 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/f6/b868acd6556f599c12e73680201caf61f1a86d4c5897189e44fe1682fc5a/gotrue-2.11.3-py3-none-any.whl", hash = "sha256:8ad90771ff6b8ede180cf6242c5b0246b9288ad58b57ce0387ef94166e84284b", size = 49107 }, -] - -[[package]] -name = "gradio-client" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fsspec" }, - { name = "httpx" }, - { name = "huggingface-hub" }, - { name = "packaging" }, - { name = "typing-extensions" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/78/e5a4a2b0f4d1ba01ec4169e181a3134fc65b6360d40817070892c3557000/gradio_client-1.7.0.tar.gz", hash = "sha256:87f6ade197951f38bac0431b2a436a8ebb2f33b2ceba2ef8e1e5bef8d8b238e4", size = 320039 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c1/def2bd93b8beab342c443bf5ac47f85e48b78eca010bbff51d6978472a3f/gradio_client-1.7.0-py3-none-any.whl", hash = "sha256:b403570c67f121ebbbc19ac1f0afa2ab1bab085ce60d96eb190832fe871aa946", size = 321900 }, -] - -[[package]] -name = "greenlet" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/ff/df5fede753cc10f6a5be0931204ea30c35fa2f2ea7a35b25bdaf4fe40e46/greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467", size = 186022 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/ec/bad1ac26764d26aa1353216fcbfa4670050f66d445448aafa227f8b16e80/greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d", size = 274260 }, - { url = "https://files.pythonhosted.org/packages/66/d4/c8c04958870f482459ab5956c2942c4ec35cac7fe245527f1039837c17a9/greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79", size = 649064 }, - { url = "https://files.pythonhosted.org/packages/51/41/467b12a8c7c1303d20abcca145db2be4e6cd50a951fa30af48b6ec607581/greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa", size = 663420 }, - { url = "https://files.pythonhosted.org/packages/27/8f/2a93cd9b1e7107d5c7b3b7816eeadcac2ebcaf6d6513df9abaf0334777f6/greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441", size = 658035 }, - { url = "https://files.pythonhosted.org/packages/57/5c/7c6f50cb12be092e1dccb2599be5a942c3416dbcfb76efcf54b3f8be4d8d/greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36", size = 660105 }, - { url = "https://files.pythonhosted.org/packages/f1/66/033e58a50fd9ec9df00a8671c74f1f3a320564c6415a4ed82a1c651654ba/greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9", size = 613077 }, - { url = "https://files.pythonhosted.org/packages/19/c5/36384a06f748044d06bdd8776e231fadf92fc896bd12cb1c9f5a1bda9578/greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0", size = 1135975 }, - { url = "https://files.pythonhosted.org/packages/38/f9/c0a0eb61bdf808d23266ecf1d63309f0e1471f284300ce6dac0ae1231881/greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942", size = 1163955 }, - { url = "https://files.pythonhosted.org/packages/43/21/a5d9df1d21514883333fc86584c07c2b49ba7c602e670b174bd73cfc9c7f/greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01", size = 299655 }, - { url = "https://files.pythonhosted.org/packages/f3/57/0db4940cd7bb461365ca8d6fd53e68254c9dbbcc2b452e69d0d41f10a85e/greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1", size = 272990 }, - { url = "https://files.pythonhosted.org/packages/1c/ec/423d113c9f74e5e402e175b157203e9102feeb7088cee844d735b28ef963/greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff", size = 649175 }, - { url = "https://files.pythonhosted.org/packages/a9/46/ddbd2db9ff209186b7b7c621d1432e2f21714adc988703dbdd0e65155c77/greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a", size = 663425 }, - { url = "https://files.pythonhosted.org/packages/bc/f9/9c82d6b2b04aa37e38e74f0c429aece5eeb02bab6e3b98e7db89b23d94c6/greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e", size = 657736 }, - { url = "https://files.pythonhosted.org/packages/d9/42/b87bc2a81e3a62c3de2b0d550bf91a86939442b7ff85abb94eec3fc0e6aa/greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4", size = 660347 }, - { url = "https://files.pythonhosted.org/packages/37/fa/71599c3fd06336cdc3eac52e6871cfebab4d9d70674a9a9e7a482c318e99/greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e", size = 615583 }, - { url = "https://files.pythonhosted.org/packages/4e/96/e9ef85de031703ee7a4483489b40cf307f93c1824a02e903106f2ea315fe/greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1", size = 1133039 }, - { url = "https://files.pythonhosted.org/packages/87/76/b2b6362accd69f2d1889db61a18c94bc743e961e3cab344c2effaa4b4a25/greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c", size = 1160716 }, - { url = "https://files.pythonhosted.org/packages/1f/1b/54336d876186920e185066d8c3024ad55f21d7cc3683c856127ddb7b13ce/greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761", size = 299490 }, - { url = "https://files.pythonhosted.org/packages/5f/17/bea55bf36990e1638a2af5ba10c1640273ef20f627962cf97107f1e5d637/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011", size = 643731 }, - { url = "https://files.pythonhosted.org/packages/78/d2/aa3d2157f9ab742a08e0fd8f77d4699f37c22adfbfeb0c610a186b5f75e0/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13", size = 649304 }, - { url = "https://files.pythonhosted.org/packages/f1/8e/d0aeffe69e53ccff5a28fa86f07ad1d2d2d6537a9506229431a2a02e2f15/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475", size = 646537 }, - { url = "https://files.pythonhosted.org/packages/05/79/e15408220bbb989469c8871062c97c6c9136770657ba779711b90870d867/greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b", size = 642506 }, - { url = "https://files.pythonhosted.org/packages/18/87/470e01a940307796f1d25f8167b551a968540fbe0551c0ebb853cb527dd6/greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822", size = 602753 }, - { url = "https://files.pythonhosted.org/packages/e2/72/576815ba674eddc3c25028238f74d7b8068902b3968cbe456771b166455e/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01", size = 1122731 }, - { url = "https://files.pythonhosted.org/packages/ac/38/08cc303ddddc4b3d7c628c3039a61a3aae36c241ed01393d00c2fd663473/greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6", size = 1142112 }, -] - -[[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 = "h2" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "hpack" }, - { name = "hyperframe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1b/38/d7f80fd13e6582fb8e0df8c9a653dcc02b03ca34f4d72f34869298c5baf8/h2-4.2.0.tar.gz", hash = "sha256:c8a52129695e88b1a0578d8d2cc6842bbd79128ac685463b887ee278126ad01f", size = 2150682 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/9e/984486f2d0a0bd2b024bf4bc1c62688fcafa9e61991f041fb0e2def4a982/h2-4.2.0-py3-none-any.whl", hash = "sha256:479a53ad425bb29af087f3458a61d30780bc818e4ebcf01f0b536ba916462ed0", size = 60957 }, -] - -[[package]] -name = "hpack" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357 }, -] - -[[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.optional-dependencies] -http2 = [ - { name = "h2" }, -] - -[[package]] -name = "huggingface-hub" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "tqdm" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/a734204aaae6c35a22f9956ebcd8d8708ae5b842e15d6f42bd6f49e634a4/huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae", size = 387074 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/da/6c2bea5327b640920267d3bf2c9fc114cfbd0a5de234d81cda80cc9e33c8/huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7", size = 464068 }, -] - -[[package]] -name = "hyperframe" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007 }, -] - [[package]] name = "identify" version = "2.6.6" @@ -504,15 +139,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/a1/68a395c17eeefb04917034bd0a1bfa765e7654fa150cca473d669aa3afb5/identify-2.6.6-py2.py3-none-any.whl", hash = "sha256:cbd1810bce79f8b671ecb20f53ee0ae8e86ae84b557de31d89709dc2a48ba881", size = 99083 }, ] -[[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]] name = "iniconfig" version = "2.0.0" @@ -522,89 +148,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] -[[package]] -name = "jiter" -version = "0.8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/17/c8747af8ea4e045f57d6cfd6fc180752cab9bc3de0e8a0c9ca4e8af333b1/jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f", size = 302027 }, - { url = "https://files.pythonhosted.org/packages/3c/c1/6da849640cd35a41e91085723b76acc818d4b7d92b0b6e5111736ce1dd10/jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44", size = 310326 }, - { url = "https://files.pythonhosted.org/packages/06/99/a2bf660d8ccffee9ad7ed46b4f860d2108a148d0ea36043fd16f4dc37e94/jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f", size = 334242 }, - { url = "https://files.pythonhosted.org/packages/a7/5f/cea1c17864828731f11427b9d1ab7f24764dbd9aaf4648a7f851164d2718/jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60", size = 356654 }, - { url = "https://files.pythonhosted.org/packages/e9/13/62774b7e5e7f5d5043efe1d0f94ead66e6d0f894ae010adb56b3f788de71/jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57", size = 379967 }, - { url = "https://files.pythonhosted.org/packages/ec/fb/096b34c553bb0bd3f2289d5013dcad6074948b8d55212aa13a10d44c5326/jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e", size = 389252 }, - { url = "https://files.pythonhosted.org/packages/17/61/beea645c0bf398ced8b199e377b61eb999d8e46e053bb285c91c3d3eaab0/jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887", size = 345490 }, - { url = "https://files.pythonhosted.org/packages/d5/df/834aa17ad5dcc3cf0118821da0a0cf1589ea7db9832589278553640366bc/jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d", size = 376991 }, - { url = "https://files.pythonhosted.org/packages/67/80/87d140399d382fb4ea5b3d56e7ecaa4efdca17cd7411ff904c1517855314/jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152", size = 510822 }, - { url = "https://files.pythonhosted.org/packages/5c/37/3394bb47bac1ad2cb0465601f86828a0518d07828a650722e55268cdb7e6/jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29", size = 503730 }, - { url = "https://files.pythonhosted.org/packages/f9/e2/253fc1fa59103bb4e3aa0665d6ceb1818df1cd7bf3eb492c4dad229b1cd4/jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e", size = 203375 }, - { url = "https://files.pythonhosted.org/packages/41/69/6d4bbe66b3b3b4507e47aa1dd5d075919ad242b4b1115b3f80eecd443687/jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c", size = 204740 }, - { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 }, - { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 }, - { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 }, - { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 }, - { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 }, - { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 }, - { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 }, - { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 }, - { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 }, - { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 }, - { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 }, - { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 }, - { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 }, - { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 }, - { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 }, -] - -[[package]] -name = "json-repair" -version = "0.37.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/1a/c2b275d9495b8f9549064369652f1f4015e4306a5a70a4ef81a153a8d03d/json_repair-0.37.0.tar.gz", hash = "sha256:af7bb6a028cde3d316f2958b76cb0ba07c87af4a3694d5ce08bf6897197b7a03", size = 29166 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/46/76b380dff81ca0c8ce3adc8fda8d423bec34835564924210ba778fcaae70/json_repair-0.37.0-py3-none-any.whl", hash = "sha256:2d81196cbecbdd8917ad75187b399b77e7341e7e5fb41299dd042b7d5c9092f9", size = 19978 }, -] - -[[package]] -name = "multidict" -version = "6.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, - { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, - { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, - { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, - { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, - { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, - { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, - { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, - { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, - { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, - { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, - { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, - { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, - { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, - { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, - { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, - { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, - { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, - { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, - { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, - { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, - { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, - { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, - { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, - { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, - { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, - { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, - { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, - { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, - { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, - { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, -] - [[package]] name = "nodeenv" version = "1.9.1" @@ -623,44 +166,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] -[[package]] -name = "pillow" -version = "11.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/af/c097e544e7bd278333db77933e535098c259609c4eb3b85381109602fb5b/pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20", size = 46742715 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/20/9ce6ed62c91c073fcaa23d216e68289e19d95fb8188b9fb7a63d36771db8/pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a", size = 3226818 }, - { url = "https://files.pythonhosted.org/packages/b9/d8/f6004d98579a2596c098d1e30d10b248798cceff82d2b77aa914875bfea1/pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b", size = 3101662 }, - { url = "https://files.pythonhosted.org/packages/08/d9/892e705f90051c7a2574d9f24579c9e100c828700d78a63239676f960b74/pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3", size = 4329317 }, - { url = "https://files.pythonhosted.org/packages/8c/aa/7f29711f26680eab0bcd3ecdd6d23ed6bce180d82e3f6380fb7ae35fcf3b/pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a", size = 4412999 }, - { url = "https://files.pythonhosted.org/packages/c8/c4/8f0fe3b9e0f7196f6d0bbb151f9fba323d72a41da068610c4c960b16632a/pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1", size = 4368819 }, - { url = "https://files.pythonhosted.org/packages/38/0d/84200ed6a871ce386ddc82904bfadc0c6b28b0c0ec78176871a4679e40b3/pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f", size = 4496081 }, - { url = "https://files.pythonhosted.org/packages/84/9c/9bcd66f714d7e25b64118e3952d52841a4babc6d97b6d28e2261c52045d4/pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91", size = 4296513 }, - { url = "https://files.pythonhosted.org/packages/db/61/ada2a226e22da011b45f7104c95ebda1b63dcbb0c378ad0f7c2a710f8fd2/pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c", size = 4431298 }, - { url = "https://files.pythonhosted.org/packages/e7/c4/fc6e86750523f367923522014b821c11ebc5ad402e659d8c9d09b3c9d70c/pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6", size = 2291630 }, - { url = "https://files.pythonhosted.org/packages/08/5c/2104299949b9d504baf3f4d35f73dbd14ef31bbd1ddc2c1b66a5b7dfda44/pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf", size = 2626369 }, - { url = "https://files.pythonhosted.org/packages/37/f3/9b18362206b244167c958984b57c7f70a0289bfb59a530dd8af5f699b910/pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5", size = 2375240 }, - { url = "https://files.pythonhosted.org/packages/b3/31/9ca79cafdce364fd5c980cd3416c20ce1bebd235b470d262f9d24d810184/pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc", size = 3226640 }, - { url = "https://files.pythonhosted.org/packages/ac/0f/ff07ad45a1f172a497aa393b13a9d81a32e1477ef0e869d030e3c1532521/pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0", size = 3101437 }, - { url = "https://files.pythonhosted.org/packages/08/2f/9906fca87a68d29ec4530be1f893149e0cb64a86d1f9f70a7cfcdfe8ae44/pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1", size = 4326605 }, - { url = "https://files.pythonhosted.org/packages/b0/0f/f3547ee15b145bc5c8b336401b2d4c9d9da67da9dcb572d7c0d4103d2c69/pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec", size = 4411173 }, - { url = "https://files.pythonhosted.org/packages/b1/df/bf8176aa5db515c5de584c5e00df9bab0713548fd780c82a86cba2c2fedb/pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5", size = 4369145 }, - { url = "https://files.pythonhosted.org/packages/de/7c/7433122d1cfadc740f577cb55526fdc39129a648ac65ce64db2eb7209277/pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114", size = 4496340 }, - { url = "https://files.pythonhosted.org/packages/25/46/dd94b93ca6bd555588835f2504bd90c00d5438fe131cf01cfa0c5131a19d/pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352", size = 4296906 }, - { url = "https://files.pythonhosted.org/packages/a8/28/2f9d32014dfc7753e586db9add35b8a41b7a3b46540e965cb6d6bc607bd2/pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3", size = 4431759 }, - { url = "https://files.pythonhosted.org/packages/33/48/19c2cbe7403870fbe8b7737d19eb013f46299cdfe4501573367f6396c775/pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9", size = 2291657 }, - { url = "https://files.pythonhosted.org/packages/3b/ad/285c556747d34c399f332ba7c1a595ba245796ef3e22eae190f5364bb62b/pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c", size = 2626304 }, - { url = "https://files.pythonhosted.org/packages/e5/7b/ef35a71163bf36db06e9c8729608f78dedf032fc8313d19bd4be5c2588f3/pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65", size = 2375117 }, - { url = "https://files.pythonhosted.org/packages/79/30/77f54228401e84d6791354888549b45824ab0ffde659bafa67956303a09f/pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861", size = 3230060 }, - { url = "https://files.pythonhosted.org/packages/ce/b1/56723b74b07dd64c1010fee011951ea9c35a43d8020acd03111f14298225/pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081", size = 3106192 }, - { url = "https://files.pythonhosted.org/packages/e1/cd/7bf7180e08f80a4dcc6b4c3a0aa9e0b0ae57168562726a05dc8aa8fa66b0/pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c", size = 4446805 }, - { url = "https://files.pythonhosted.org/packages/97/42/87c856ea30c8ed97e8efbe672b58c8304dee0573f8c7cab62ae9e31db6ae/pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547", size = 4530623 }, - { url = "https://files.pythonhosted.org/packages/ff/41/026879e90c84a88e33fb00cc6bd915ac2743c67e87a18f80270dfe3c2041/pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab", size = 4465191 }, - { url = "https://files.pythonhosted.org/packages/e5/fb/a7960e838bc5df57a2ce23183bfd2290d97c33028b96bde332a9057834d3/pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9", size = 2295494 }, - { url = "https://files.pythonhosted.org/packages/d7/6c/6ec83ee2f6f0fda8d4cf89045c6be4b0373ebfc363ba8538f8c999f63fcd/pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe", size = 2631595 }, - { url = "https://files.pythonhosted.org/packages/cf/6c/41c21c6c8af92b9fea313aa47c75de49e2f9a467964ee33eb0135d47eb64/pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756", size = 2377651 }, -] - [[package]] name = "platformdirs" version = "4.3.6" @@ -670,24 +175,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] -[[package]] -name = "playwright" -version = "1.50.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "greenlet" }, - { name = "pyee" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/5e/068dea3c96e9c09929b45c92cf7e573403b52a89aa463f89b9da9b87b7a4/playwright-1.50.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:f36d754a6c5bd9bf7f14e8f57a2aea6fd08f39ca4c8476481b9c83e299531148", size = 40277564 }, - { url = "https://files.pythonhosted.org/packages/78/85/b3deb3d2add00d2a6ee74bf6f57ccefb30efc400fd1b7b330ba9a3626330/playwright-1.50.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:40f274384591dfd27f2b014596250b2250c843ed1f7f4ef5d2960ecb91b4961e", size = 39521844 }, - { url = "https://files.pythonhosted.org/packages/f3/f6/002b3d98df9c84296fea84f070dc0d87c2270b37f423cf076a913370d162/playwright-1.50.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:9922ef9bcd316995f01e220acffd2d37a463b4ad10fd73e388add03841dfa230", size = 40277563 }, - { url = "https://files.pythonhosted.org/packages/b9/63/c9a73736e434df894e484278dddc0bf154312ff8d0f16d516edb790a7d42/playwright-1.50.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fc628c492d12b13d1f347137b2ac6c04f98197ff0985ef0403a9a9ee0d39131", size = 45076712 }, - { url = "https://files.pythonhosted.org/packages/bd/2c/a54b5a64cc7d1a62f2d944c5977fb3c88e74d76f5cdc7966e717426bce66/playwright-1.50.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcff35f72db2689a79007aee78f1b0621a22e6e3d6c1f58aaa9ac805bf4497c", size = 44493111 }, - { url = "https://files.pythonhosted.org/packages/2b/4a/047cbb2ffe1249bd7a56441fc3366fb4a8a1f44bc36a9061d10edfda2c86/playwright-1.50.0-py3-none-win32.whl", hash = "sha256:3b906f4d351260016a8c5cc1e003bb341651ae682f62213b50168ed581c7558a", size = 34784543 }, - { url = "https://files.pythonhosted.org/packages/bc/2b/e944e10c9b18e77e43d3bb4d6faa323f6cc27597db37b75bc3fd796adfd5/playwright-1.50.0-py3-none-win_amd64.whl", hash = "sha256:1859423da82de631704d5e3d88602d755462b0906824c1debe140979397d2e8d", size = 34784546 }, -] - [[package]] name = "pluggy" version = "1.5.0" @@ -697,20 +184,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] -[[package]] -name = "postgrest" -version = "0.19.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "deprecation" }, - { name = "httpx", extra = ["http2"] }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/80/b0306469da7ad89db165ce4c76de2f12eccc7fadb900cab9cbaff760a587/postgrest-0.19.3.tar.gz", hash = "sha256:28a70f03bf3a975aa865a10487b1ce09b7195f56453f7c318a70d3117a3d323c", size = 15095 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/82/f1825a85745912cdd8956aad8ebc4b797d2f891c380c2b8825b35914dbd1/postgrest-0.19.3-py3-none-any.whl", hash = "sha256:03a7e638962454d10bb712c35e63a8a4bc452917917a4e9eb7427bd5b3c6c485", size = 22198 }, -] - [[package]] name = "pre-commit" version = "4.1.0" @@ -727,47 +200,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/b3/df14c580d82b9627d173ceea305ba898dca135feb360b6d84019d0803d3b/pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b", size = 220560 }, ] -[[package]] -name = "propcache" -version = "0.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, - { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, - { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, - { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, - { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, - { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, - { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, - { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, - { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, - { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, - { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, - { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, - { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, - { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, - { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, - { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, - { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, - { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, - { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, - { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, - { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, - { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, - { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, - { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, - { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, - { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, - { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, - { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, - { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, - { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, - { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, - { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, - { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, -] - [[package]] name = "pydantic" version = "2.10.6" @@ -791,6 +223,33 @@ dependencies = [ ] 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/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938 }, + { url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684 }, + { url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169 }, + { url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227 }, + { url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695 }, + { url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662 }, + { url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370 }, + { url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813 }, + { url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287 }, + { url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414 }, + { url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301 }, + { url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685 }, + { url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876 }, + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 }, { 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 }, @@ -819,18 +278,37 @@ wheels = [ { 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]] -name = "pyee" -version = "12.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/37/8fb6e653597b2b67ef552ed49b438d5398ba3b85a9453f8ada0fd77d455c/pyee-12.1.1.tar.gz", hash = "sha256:bbc33c09e2ff827f74191e3e5bbc6be7da02f627b7ec30d86f5ce1a6fb2424a3", size = 30915 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/68/7e150cba9eeffdeb3c5cecdb6896d70c8edd46ce41c0491e12fb2b2256ff/pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef", size = 15527 }, + { url = "https://files.pythonhosted.org/packages/27/97/3aef1ddb65c5ccd6eda9050036c956ff6ecbfe66cb7eb40f280f121a5bb0/pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993", size = 1896475 }, + { url = "https://files.pythonhosted.org/packages/ad/d3/5668da70e373c9904ed2f372cb52c0b996426f302e0dee2e65634c92007d/pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308", size = 1772279 }, + { url = "https://files.pythonhosted.org/packages/8a/9e/e44b8cb0edf04a2f0a1f6425a65ee089c1d6f9c4c2dcab0209127b6fdfc2/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4", size = 1829112 }, + { url = "https://files.pythonhosted.org/packages/1c/90/1160d7ac700102effe11616e8119e268770f2a2aa5afb935f3ee6832987d/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf", size = 1866780 }, + { url = "https://files.pythonhosted.org/packages/ee/33/13983426df09a36d22c15980008f8d9c77674fc319351813b5a2739b70f3/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76", size = 2037943 }, + { url = "https://files.pythonhosted.org/packages/01/d7/ced164e376f6747e9158c89988c293cd524ab8d215ae4e185e9929655d5c/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118", size = 2740492 }, + { url = "https://files.pythonhosted.org/packages/8b/1f/3dc6e769d5b7461040778816aab2b00422427bcaa4b56cc89e9c653b2605/pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630", size = 1995714 }, + { url = "https://files.pythonhosted.org/packages/07/d7/a0bd09bc39283530b3f7c27033a814ef254ba3bd0b5cfd040b7abf1fe5da/pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54", size = 1997163 }, + { url = "https://files.pythonhosted.org/packages/2d/bb/2db4ad1762e1c5699d9b857eeb41959191980de6feb054e70f93085e1bcd/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f", size = 2005217 }, + { url = "https://files.pythonhosted.org/packages/53/5f/23a5a3e7b8403f8dd8fc8a6f8b49f6b55c7d715b77dcf1f8ae919eeb5628/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362", size = 2127899 }, + { url = "https://files.pythonhosted.org/packages/c2/ae/aa38bb8dd3d89c2f1d8362dd890ee8f3b967330821d03bbe08fa01ce3766/pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96", size = 2155726 }, + { url = "https://files.pythonhosted.org/packages/98/61/4f784608cc9e98f70839187117ce840480f768fed5d386f924074bf6213c/pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e", size = 1817219 }, + { url = "https://files.pythonhosted.org/packages/57/82/bb16a68e4a1a858bb3768c2c8f1ff8d8978014e16598f001ea29a25bf1d1/pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67", size = 1985382 }, + { url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159 }, + { url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331 }, + { url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467 }, + { url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797 }, + { url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839 }, + { url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861 }, + { url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582 }, + { url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985 }, + { url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715 }, + { url = "https://files.pythonhosted.org/packages/29/0e/dcaea00c9dbd0348b723cae82b0e0c122e0fa2b43fa933e1622fd237a3ee/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656", size = 1891733 }, + { url = "https://files.pythonhosted.org/packages/86/d3/e797bba8860ce650272bda6383a9d8cad1d1c9a75a640c9d0e848076f85e/pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278", size = 1768375 }, + { url = "https://files.pythonhosted.org/packages/41/f7/f847b15fb14978ca2b30262548f5fc4872b2724e90f116393eb69008299d/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb", size = 1822307 }, + { url = "https://files.pythonhosted.org/packages/9c/63/ed80ec8255b587b2f108e514dc03eed1546cd00f0af281e699797f373f38/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd", size = 1979971 }, + { url = "https://files.pythonhosted.org/packages/a9/6d/6d18308a45454a0de0e975d70171cadaf454bc7a0bf86b9c7688e313f0bb/pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc", size = 1987616 }, + { url = "https://files.pythonhosted.org/packages/82/8a/05f8780f2c1081b800a7ca54c1971e291c2d07d1a50fb23c7e4aef4ed403/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b", size = 1998943 }, + { url = "https://files.pythonhosted.org/packages/5e/3e/fe5b6613d9e4c0038434396b46c5303f5ade871166900b357ada4766c5b7/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b", size = 2116654 }, + { url = "https://files.pythonhosted.org/packages/db/ad/28869f58938fad8cc84739c4e592989730bfb69b7c90a8fff138dff18e1e/pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2", size = 2152292 }, + { url = "https://files.pythonhosted.org/packages/a1/0c/c5c5cd3689c32ed1fe8c5d234b079c12c281c051759770c05b8bed6412b5/pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35", size = 2004961 }, ] [[package]] @@ -839,9 +317,11 @@ version = "8.3.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } wheels = [ @@ -853,7 +333,7 @@ name = "pytest-cov" version = "6.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "coverage" }, + { name = "coverage", extra = ["toml"] }, { name = "pytest" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } @@ -861,33 +341,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, ] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, -] - -[[package]] -name = "python-dotenv" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, -] - [[package]] name = "pyyaml" version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, @@ -906,36 +383,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, -] - -[[package]] -name = "realtime" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "python-dateutil" }, - { name = "typing-extensions" }, - { name = "websockets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/78/ae/c650a38d44df563ddc215e67adcefc5e5316d7d62c13ad3476af57182e34/realtime-2.3.0.tar.gz", hash = "sha256:4071b095d7f750fcd68ec322e05045fce067b5cd5309a7ca809fcc87e50f56a1", size = 18412 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/d8/b43232d64542906a89e9f7365a4aa1bf3290ceaa5a0a26ea029e8377090b/realtime-2.3.0-py3-none-any.whl", hash = "sha256:6c241681d0517a3bc5e0132842bffd8b592286131b01a68b41cf7e0be94828fc", size = 21477 }, -] - -[[package]] -name = "requests" -version = "2.32.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, ] [[package]] @@ -963,79 +419,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/e6/3d6ec3bc3d254e7f005c543a661a41c3e788976d0e52a1ada195bd664344/ruff-0.9.4-py3-none-win_arm64.whl", hash = "sha256:585792f1e81509e38ac5123492f8875fbc36f3ede8185af0a26df348e5154f41", size = 10078251 }, ] -[[package]] -name = "six" -version = "1.17.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[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 = "storage3" -version = "0.11.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx", extra = ["http2"] }, - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/25/83eb4e4612dc07a3bb3cab96253c9c83752d4816f2cf38aa832dfb8d8813/storage3-0.11.3.tar.gz", hash = "sha256:883637132aad36d9d92b7c497a8a56dff7c51f15faf2ff7acbccefbbd5e97347", size = 9930 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/8d/ff89f85c4b48285ac7cddf0fafe5e55bb3742d374672b2fbd2627c213fa6/storage3-0.11.3-py3-none-any.whl", hash = "sha256:090c42152217d5d39bd94af3ddeb60c8982f3a283dcd90b53d058f2db33e6007", size = 17831 }, -] - -[[package]] -name = "strenum" -version = "0.4.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851 }, -] - -[[package]] -name = "supabase" -version = "2.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gotrue" }, - { name = "httpx" }, - { name = "postgrest" }, - { name = "realtime" }, - { name = "storage3" }, - { name = "supafunc" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/0e/3d2f01d465b4636deb78f102e6feff47568aae5873946184afb75ff5abe3/supabase-2.13.0.tar.gz", hash = "sha256:452574d34bd978c8d11b5f02b0182b48e8854e511c969483c83875ec01495f11", size = 14251 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/a7/2ffbd3bea564927e74966a1a3a512a68b491d602d77890daa67e3033bdf4/supabase-2.13.0-py3-none-any.whl", hash = "sha256:6cfccc055be21dab311afc5e9d5b37f3a4966f8394703763fbc8f8e86f36eaa6", size = 17171 }, -] - -[[package]] -name = "supafunc" -version = "0.9.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx", extra = ["http2"] }, - { name = "strenum" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/03/2ca4dddd4a8d28f5dbe204ea0350fb3e4fbf16156ef446f12e0a73d9e718/supafunc-0.9.3.tar.gz", hash = "sha256:29a06d0dc9fe049ecc1249e53ccf3d2a80d72239200f69b510740217aca6497c", size = 4730 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/ec/56e3de38ee99f11c6d645ce8f2a1c29c4561adcb47e53e7781b9c073aa7e/supafunc-0.9.3-py3-none-any.whl", hash = "sha256:83e36ed5e94d2dd0484011aad0b09337d35a87992adbc97acc31c8201aca05d0", size = 7690 }, -] - [[package]] name = "test-locatr" -version = "0.42.0" +version = "0.49.0" source = { editable = "." } dependencies = [ { name = "pydantic" }, @@ -1061,15 +447,42 @@ dev = [ ] [[package]] -name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, ] [[package]] @@ -1081,15 +494,6 @@ 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]] -name = "urllib3" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, -] - [[package]] name = "virtualenv" version = "20.29.1" @@ -1103,80 +507,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/a7/ca/f23dcb02e161a9bba wheels = [ { url = "https://files.pythonhosted.org/packages/89/9b/599bcfc7064fbe5740919e78c5df18e5dceb0887e676256a1061bb5ae232/virtualenv-20.29.1-py3-none-any.whl", hash = "sha256:4e4cb403c0b0da39e13b46b1b2476e505cb0046b25f242bee80f62bf990b2779", size = 4282379 }, ] - -[[package]] -name = "websockets" -version = "14.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/54/8359678c726243d19fae38ca14a334e740782336c9f19700858c4eb64a1e/websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5", size = 164394 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/81/04f7a397653dc8bec94ddc071f34833e8b99b13ef1a3804c149d59f92c18/websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c", size = 163096 }, - { url = "https://files.pythonhosted.org/packages/ec/c5/de30e88557e4d70988ed4d2eabd73fd3e1e52456b9f3a4e9564d86353b6d/websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967", size = 160758 }, - { url = "https://files.pythonhosted.org/packages/e5/8c/d130d668781f2c77d106c007b6c6c1d9db68239107c41ba109f09e6c218a/websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990", size = 160995 }, - { url = "https://files.pythonhosted.org/packages/a6/bc/f6678a0ff17246df4f06765e22fc9d98d1b11a258cc50c5968b33d6742a1/websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda", size = 170815 }, - { url = "https://files.pythonhosted.org/packages/d8/b2/8070cb970c2e4122a6ef38bc5b203415fd46460e025652e1ee3f2f43a9a3/websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95", size = 169759 }, - { url = "https://files.pythonhosted.org/packages/81/da/72f7caabd94652e6eb7e92ed2d3da818626e70b4f2b15a854ef60bf501ec/websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3", size = 170178 }, - { url = "https://files.pythonhosted.org/packages/31/e0/812725b6deca8afd3a08a2e81b3c4c120c17f68c9b84522a520b816cda58/websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9", size = 170453 }, - { url = "https://files.pythonhosted.org/packages/66/d3/8275dbc231e5ba9bb0c4f93144394b4194402a7a0c8ffaca5307a58ab5e3/websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267", size = 169830 }, - { url = "https://files.pythonhosted.org/packages/a3/ae/e7d1a56755ae15ad5a94e80dd490ad09e345365199600b2629b18ee37bc7/websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe", size = 169824 }, - { url = "https://files.pythonhosted.org/packages/b6/32/88ccdd63cb261e77b882e706108d072e4f1c839ed723bf91a3e1f216bf60/websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205", size = 163981 }, - { url = "https://files.pythonhosted.org/packages/b3/7d/32cdb77990b3bdc34a306e0a0f73a1275221e9a66d869f6ff833c95b56ef/websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce", size = 164421 }, - { url = "https://files.pythonhosted.org/packages/82/94/4f9b55099a4603ac53c2912e1f043d6c49d23e94dd82a9ce1eb554a90215/websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e", size = 163102 }, - { url = "https://files.pythonhosted.org/packages/8e/b7/7484905215627909d9a79ae07070057afe477433fdacb59bf608ce86365a/websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad", size = 160766 }, - { url = "https://files.pythonhosted.org/packages/a3/a4/edb62efc84adb61883c7d2c6ad65181cb087c64252138e12d655989eec05/websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03", size = 160998 }, - { url = "https://files.pythonhosted.org/packages/f5/79/036d320dc894b96af14eac2529967a6fc8b74f03b83c487e7a0e9043d842/websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f", size = 170780 }, - { url = "https://files.pythonhosted.org/packages/63/75/5737d21ee4dd7e4b9d487ee044af24a935e36a9ff1e1419d684feedcba71/websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5", size = 169717 }, - { url = "https://files.pythonhosted.org/packages/2c/3c/bf9b2c396ed86a0b4a92ff4cdaee09753d3ee389be738e92b9bbd0330b64/websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a", size = 170155 }, - { url = "https://files.pythonhosted.org/packages/75/2d/83a5aca7247a655b1da5eb0ee73413abd5c3a57fc8b92915805e6033359d/websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20", size = 170495 }, - { url = "https://files.pythonhosted.org/packages/79/dd/699238a92761e2f943885e091486378813ac8f43e3c84990bc394c2be93e/websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2", size = 169880 }, - { url = "https://files.pythonhosted.org/packages/c8/c9/67a8f08923cf55ce61aadda72089e3ed4353a95a3a4bc8bf42082810e580/websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307", size = 169856 }, - { url = "https://files.pythonhosted.org/packages/17/b1/1ffdb2680c64e9c3921d99db460546194c40d4acbef999a18c37aa4d58a3/websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc", size = 163974 }, - { url = "https://files.pythonhosted.org/packages/14/13/8b7fc4cb551b9cfd9890f0fd66e53c18a06240319915533b033a56a3d520/websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f", size = 164420 }, - { url = "https://files.pythonhosted.org/packages/7b/c8/d529f8a32ce40d98309f4470780631e971a5a842b60aec864833b3615786/websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b", size = 157416 }, -] - -[[package]] -name = "yarl" -version = "1.18.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "multidict" }, - { name = "propcache" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, - { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, - { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, - { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, - { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, - { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, - { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, - { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, - { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, - { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, - { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, - { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, - { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, - { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, - { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, - { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, - { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, - { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, - { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, - { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, - { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, - { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, - { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, - { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, - { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, - { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, - { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, - { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, - { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, - { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, - { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, - { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, - { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, -]