diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..61277bf --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,54 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/universal +{ + "name": "Default Linux Universal", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/universal:5-linux", + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}, + "ghcr.io/devcontainers/features/python:1": {}, + "ghcr.io/devcontainers/features/dotnet:2": {}, + "ghcr.io/stu-bell/devcontainer-features/gemini-cli:0": {}, + "ghcr.io/devcontainers-extra/features/shellcheck:1": {}, + "ghcr.io/devcontainers-extra/features/uv:1": {}, + "ghcr.io/devcontainers-extra/features/direnv:1": {}, + "ghcr.io/devcontainers/features/node:1": { + "version": "lts" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-dotnettools.csdevkit", + "ms-azuretools.vscode-containers", + "Upstash.context7-mcp", + "mads-hartmann.bash-ide-vscode", + "fill-labs.dependi", + "docker.docker", + "tamasfe.even-better-toml", + "vscjava.vscode-java-pack", + "PKief.material-icon-theme", + "Postman.postman-for-vscode", + "charliermarsh.ruff", + "mtxr.sqltools", + "redhat.vscode-yaml", + "astral-sh.ty" + ] + } + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..307141a --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +XRG13_SSH_USERNAME= +XRG13_SSH_PRIVATE_KEY_PATH=~/.ssh/id_rsa # change to valid path + +OLLAMA_PORT=11434 +DOTNET_SERVICE_PORT=5083 +JAVA_SERVICE_PORT=8090 +MONGODB_MIGRATOR_PORT=8091 +DB_TOOLBOX_PORT=5010 + +MSSQL_PORT=1444 +MSSQL_USER=sa +MSSQL_PASSWORD=Testingorms123 +MONGODB_PORT=27027 +MONGODB_USER=uom_readonly +MONGODB_PASSWORD=uom_readonly +MONGODB_DATABASE=uom +NEO4J_PORT=7697 +NEO4J_BROWSER_PORT=7484 +NEO4J_USERNAME=neo4j +NEO4J_PASSWORD=password \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8c29662 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +* text=auto + +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf +*.sh text eol=lf diff --git a/.gitignore b/.gitignore index d3bd805..4fece80 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.bkp -.gemini/ gha-creds-*.json +.env +.env.dev +/.idea/ +*.dump +.serena/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d111564 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,70 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Neo4jSchemaValidationEntrypoint", + "request": "launch", + "mainClass": "uom.services.Neo4jSchemaValidationEntrypoint", + "projectName": "neo4j_sandbox", + "envFile": "${workspaceFolder}/services/java-services/.env.dev" + }, + { + "type": "java", + "name": "MongoSchemaValidationEntrypoint", + "request": "launch", + "mainClass": "uom.services.MongoSchemaValidationEntrypoint", + "projectName": "mongo_sandbox", + "envFile": "${workspaceFolder}/services/java-services/.env.dev" + }, + { + "type": "java", + "name": "MongoQueryEntrypoint", + "request": "launch", + "mainClass": "uom.services.MongoQueryEntrypoint", + "projectName": "mongo_sandbox", + "envFile": "${workspaceFolder}/services/java-services/.env.dev" + }, + { + "type": "java", + "name": "Neo4jQueryEntrypoint", + "request": "launch", + "mainClass": "uom.services.Neo4jQueryEntrypoint", + "projectName": "neo4j_sandbox", + "envFile": "${workspaceFolder}/services/java-services/.env.dev" + }, + { + "name": "[orchestrator] Python Debugger: Remote Attach", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + }, + "justMyCode": false, + "pathMappings": [ + { + "localRoot": "${workspaceFolder}/services/orchestrator", + "remoteRoot": "." + } + ] + }, + { + "name": "C#: Debug Active File", + "type": "dotnet", + "request": "launch", + "projectPath": "${file}", + "linux": { + "args": [ + "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + ], + "env": { + "CONNECTION_STRING": "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True", + "EFCORE_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/efcore-sandbox/results", + "NHIBERNATE_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/nhibernate-sandbox/results", + "DAPPER_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/dapper-sandbox/results" + } + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..7e71970 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,65 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.configuration.updateBuildConfiguration": "automatic", + "python-envs.pythonProjects": [ + { + "path": "./services/orchestrator", + "envManager": "ms-python.python:venv", + "packageManager": "ms-python.python:pip" + } + ], + "python.analysis.useNearestConfiguration": true, + "python.analysis.include": ["./services/orchestrator/[src,tests]/**"], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "pythonTestExplorer.testFramework": "pytest", + "python.envFile": "${workspaceFolder}/services/orchestrator/.env.dev", + "python.testing.cwd": "${workspaceFolder}/services/orchestrator", + "python.testing.autoTestDiscoverOnSaveEnabled": true, + "python.testing.autoTestDiscoverOnSavePattern": "${workspaceFolder}/services/orchestrator/tests/**/*.py", + "python.terminal.useEnvFile": true, + "python.testing.pytestPath": "${workspaceFolder}/services/orchestrator/.venv/bin/pytest", + "python.testing.pytestArgs": [ + "-v", + "${workspaceFolder}/services/orchestrator/tests" + ], + "python.analysis.inlayHints.pytestParameters": true, + // "python.languageServer": "Pylance", + // if using Pylance in VSCode + "ty.disableLanguageServices": false, + "sqltools.connections": [ + { + "ssh": "Disabled", + "previewLimit": 50, + "server": "localhost", + "port": 5433, + "driver": "PostgreSQL", + "name": "langgraph-postgres", + "database": "postgres", + "username": "postgres", + "password": "postgres" + }, + { + "connectionTimeout": 15, + "mssqlOptions": { + "appName": "SQLTools", + "useUTC": true, + "encrypt": true, + "trustServerCertificate": true + }, + "ssh": "Disabled", + "previewLimit": 50, + "server": "localhost", + "port": 1444, + "driver": "MSSQL", + "name": "uom-mssql", + "username": "sa", + "password": "Testingorms123" + } + ], + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "maven.view": "flat", + "java.sources.organizeImports.staticStarThreshold": 1 +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..bff5000 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,59 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/services/dotnet-service/dotnet-service.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/services/dotnet-service/dotnet-service.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/services/dotnet-service/dotnet-service.sln" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "fix /run/user/1000", + "type": "shell", + "command": "sudo mkdir /run/user/1000 && sudo chown -R $USER:$USER /run/user/1000", + "problemMatcher": [] + }, + { + "label": "Setup Windows proxy DNS for Daytona", + "type": "shell", + "command": "sudo.exe powershell -ExecutionPolicy Bypass -File ./services/orchestrator/scripts/daytona-setup-proxy-dns.ps1", + "problemMatcher": [] + }, + { + "label": "Reset Windows proxy DNS for Daytona", + "type": "shell", + "command": "sudo.exe powershell -ExecutionPolicy Bypass -File ./services/orchestrator/scripts/daytona-reset-proxy-dns.ps1", + "problemMatcher": [] + }, + ] +} \ No newline at end of file diff --git a/ORMConvertor/README.md b/ORMConvertor/README.md index b97c17e..779e4a6 100644 --- a/ORMConvertor/README.md +++ b/ORMConvertor/README.md @@ -1,27 +1,36 @@ +# ORMConvertor + A web-based tool for translating accross different .NET ORMs. # Deployment + The application can be run in two main ways. The more user-friendly approach is to open the solution file `ORMConvertor.sln` in Visual Studio. `ORMConvertorAPI` has to be set as the startup project, if not selected by default. The app can be launched in `Debug` configuration for development or compiled in `Release` configuration for production. Only the `http` launch profile defined in `ORMConvertorAPI/Properties/launchSettings.json` file has been configured and tested. The application can be started with `CTRL + F5` to run without debugging or `F5` to launch with the debugger. A browser window should open automatically. Alternatively, the application can be started using .NET CLI using the following commands: + ```sh dotnet run --configuration Release --launch-profile http --project ORMConvertorAPI/ORMConvertorAPI.csproj ``` + This approach does not open a browser automatically. Instead, the local URL is printed to the console (typically [http://localhost:5072/orm/](http://localhost:5072/orm/)). Tests can be executed via the Visual Studio's Test Explorer window or with a .NET CLI command: + ```sh dotnet test Tests/Tests.csproj --configuration Release ``` + The output displays the results of succeeded and failed tests. Tests are also run automatically on each commit using a GitHub Actions pipeline. The pipeline configuration is located in the `.github` folder at the root of the repository. The Angular frontend is precompiled and served by the ASP.NET web server running the API. To prepare the frontend, its source files must be compiled and copied to the `wwwroot` directory, from which they are served as static files. This process is performed by executing the following commands in the `ORMConvertorAPI/frontend` directory. + ```sh npm install ng build --configuration "production" --base-href "/orm/" --deploy-url "/orm/" && rmdir /s /q "..\wwwroot" && mkdir "..\wwwroot" && xcopy /s /e /y "dist\browser\*" "..\wwwroot\" ``` For Linux: + ```sh npm install ng build --configuration "production" --base-href "/orm/" --deploy-url "/orm/" && rm -rf "../wwwroot" && mkdir "../wwwroot" && cp -r dist/browser/* ../wwwroot/ diff --git a/README.md b/README.md index dbda17f..e893c20 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,55 @@ -# Universal Object Mapping +# Universal Object Mapping (UOM) [![ORMConvertor tests](https://github.com/corovcam/Universal-Object-Mapping/actions/workflows/ormconvertor-tests.yml/badge.svg)](https://github.com/corovcam/Universal-Object-Mapping/actions/workflows/ormconvertor-tests.yml) -## Repository structure +Universal Object Mapping (UOM) is a research project focused on automating the translation and optimization of database schemas and queries across diverse Object-Relational/Document/Graph Mapping (ORM/ODM/OGM) frameworks. -The repository is structured into several directories. The `diagrams` directory holds diagrams created using [draw.io](https://www.drawio.com/). Experimental comparisons, including unit tests and benchmarks, are located in the `benchmarks` directory. The `ORMConvertor` folder includes a prototype tool for translating between different .NET ORM frameworks. +The project leverages an **LLM-driven iterative translation architecture** to bridge the gap between relational, document, and graph databases. It extends the earlier work of ORMorpher, replacing its heuristic rule engine with an LLM-driven pipeline. -## ORMConvertor +## Key Features -The translation and advisor tool are currently hosted at [http://116.203.208.55/orm/home](http://116.203.208.55/orm/home). +- **Automated Translation**: Migrate .NET (EF Core, Dapper, NHibernate) entity mappings and LINQ queries to Java Spring Data (MongoDB, Neo4j). +- **LLM Orchestration**: Uses a ReAct agent powered by LangGraph for autonomous code generation, validation, and correction with tool calls. +- **Isolated Benchmarking**: Measures ORM overhead (mapping/translation) independently of database latency via a specialized mocking layer. +- **Intelligent Advisor**: Suggests optimal ORM/ODM configurations using Integer Linear Programming (ILP) based on benchmark data. +- **Multi-Database Support**: MS SQL Server (relational), MongoDB (document), and Neo4j (graph) integration. +- **ETL Pipelines**: Pre-configured ETL processes to map and load data from relational schemas to NoSQL targets. -## Frameworks and ORM/OGM/ODMs compared +## Architecture -The compared ORMs are: +The system is built as a set of modular microservices and tools coordinated by a Python-based orchestrator, managed via Docker Compose. -- [Dapper](https://github.com/DapperLib/Dapper) -- [NHibernate](https://github.com/nhibernate) -- [Entity Framework Core](https://github.com/dotnet/efcore) +- **Orchestrator (`services/orchestrator`)**: Python/LangGraph service driving the translation workflow using a "Council of Models" and a ReAct agent. +- **Service Layer**: + - **[.NET Service](services/dotnet-service)**: Compilation and validation for C# (EF Core, NHibernate, Dapper). + - **[Java Service](services/java-services)**: Compilation and validation for Java (Spring Data MongoDB, Spring Data Neo4j). +- **[ORMConvertor](ORMConvertor/)**: The original .NET-based tool providing the web UI, heuristic reference logic, and the ILP-based Advisor. +- **[Benchmarks](benchmarks/)**: A comprehensive suite for measuring ORM performance. -The compared OGMs are: +Detailed architectural documentation can be found in [**notes/architecture.md**](notes/architecture.md). -- [Spring Data Neo4j](https://github.com/spring-projects/spring-data-neo4j) +## Getting Started -The compared ODMs are: +### Prerequisites -- [Spring Data MongoDB](https://github.com/spring-projects/spring-data-mongodb) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine with Compose +- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (or 10.0) +- [Python 3.11+](https://www.python.org/) (managed with [uv](https://astral.sh/uv/)) +- [OpenJDK 25+](https://jdk.java.net/25/) and [Maven](https://maven.apache.org/download.cgi) (for Java services) +- [Node.js 24](https://nodejs.org/en/download/) (for Frontend) + +### Development Setup -## Acknowledgements +1. **Spin up the stack**: + ```bash + docker compose up -d --build + ``` +2. **Configure Environment**: + Copy `.env.example` to `.env` and configure your LLM provider keys (Ollama or OpenAI-compatible like E-Infra vLLM). + +3. **Explore Documentation**: + - [Project Specification (LaTeX)](notes/project-specification/specification-llm-advisor.tex) + - [C4 Architecture Diagrams](notes/architecture) +## Acknowledgements Part of the `ORMConvertor` and the `benchmarks` source code, including some workflows and diagrams, were developed by Milan Abrahám as part of his Master thesis titled _Framework-Agnostic Query Adaptation: Ensuring SQL Compatibility Across .NET Database Frameworks_. The thesis is available at http://hdl.handle.net/20.500.11956/203083, and the source code is available at https://github.com/milan252525/orm-convertor. \ No newline at end of file diff --git a/benchmarks/Common/Common.csproj b/benchmarks/Common/Common.csproj index b855a78..6bded8b 100644 --- a/benchmarks/Common/Common.csproj +++ b/benchmarks/Common/Common.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/benchmarks/Common/Mock/AmbientScopeBase.cs b/benchmarks/Common/Mock/AmbientScopeBase.cs new file mode 100644 index 0000000..3930aaa --- /dev/null +++ b/benchmarks/Common/Mock/AmbientScopeBase.cs @@ -0,0 +1,33 @@ +using System.Threading; + +namespace Common.Mock; + +public abstract class AmbientScopeBase : IDisposable where T : AmbientScopeBase +{ + private static readonly AsyncLocal _current = new(); + + private readonly T? _previousScope; + private bool _disposed; + + public static T? Current => _current.Value; + + protected AmbientScopeBase() + { + _previousScope = _current.Value; + } + + protected static T StartNew(T instance) + { + _current.Value = instance; + return instance; + } + + public void Dispose() + { + if (!_disposed) + { + _disposed = true; + _current.Value = _previousScope; + } + } +} diff --git a/benchmarks/Common/Mock/BenchmarkCommandExecutor.cs b/benchmarks/Common/Mock/BenchmarkCommandExecutor.cs new file mode 100644 index 0000000..ddedbd2 --- /dev/null +++ b/benchmarks/Common/Mock/BenchmarkCommandExecutor.cs @@ -0,0 +1,91 @@ +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; + +namespace Common.Mock; + +/// +/// A command executor that returns mock DataTableReader instances populated with +/// default values based on query plan metadata (estimated rows and column schema). +/// Designed for benchmarking ORM overhead without actual database round-trips. +/// +public class BenchmarkCommandExecutor +{ + private int _estimatedRows = 1; + private QueryOutputInfoHelper.QueryInfo _queryInfo = new(); + private bool _useUniqueColumnNames = false; + + public static BenchmarkCommandExecutor Instance { get; } = new(); + + private BenchmarkCommandExecutor() + { + } + + /// + /// Configures the executor with query plan metadata for the next benchmark iteration. + /// + public void Configure(QueryOutputInfoHelper.QueryInfo queryInfo, bool useUniqueColumnNames = false) + { + _estimatedRows = queryInfo.EstimatedRows; + _queryInfo = queryInfo; + _useUniqueColumnNames = useUniqueColumnNames; + } + + public void UseUniqueColumnNames(bool useUniqueColumnNames) + { + _useUniqueColumnNames = useUniqueColumnNames; + } + + public virtual int ExecuteNonQuery(DbCommand command) + => -1; + + public virtual object ExecuteScalar(DbCommand command) + => 1.00m; + + public virtual DbDataReader ExecuteReader(DbCommand command, CommandBehavior behavior) + => BuildMockReader(); + + public virtual Task ExecuteNonQueryAsync(DbCommand command, CancellationToken cancellationToken) + => Task.FromResult(-1); + + public virtual Task ExecuteScalarAsync(DbCommand command, CancellationToken cancellationToken) + => Task.FromResult(1.00m); + + public virtual Task ExecuteReaderAsync(DbCommand command, CommandBehavior behavior, + CancellationToken cancellationToken) + => Task.FromResult(BuildMockReader()); + + private DbDataReader BuildMockReader() + { + if (_queryInfo.OutputColumns.Count > 0) + { + return CreateReader(_queryInfo, _estimatedRows, _useUniqueColumnNames); + } + + // Last resort: single column reader with a scalar value + return CreateScalarReader(1.00m); + } + + /// + /// Builds a BenchmarkDbDataReader populated with + /// default values. + /// + public static DbDataReader CreateReader(QueryOutputInfoHelper.QueryInfo queryInfo, int rowCount, + bool useUniqueColumnNames = false) + { + return new BenchmarkDbDataReader(queryInfo, rowCount, useUniqueColumnNames); + } + + /// + /// Creates a BenchmarkDbDataReader returning a single scalar result. + /// + public static DbDataReader CreateScalarReader(object value) + { + return BenchmarkDbDataReader.CreateScalar(value); + } +} diff --git a/benchmarks/Common/Mock/QueryOutputInfoHelper.cs b/benchmarks/Common/Mock/QueryOutputInfoHelper.cs new file mode 100644 index 0000000..93d7aac --- /dev/null +++ b/benchmarks/Common/Mock/QueryOutputInfoHelper.cs @@ -0,0 +1,162 @@ +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.Linq; +using System.Xml.Linq; + +namespace Common.Mock; + +public static class QueryOutputInfoHelper +{ + private static readonly XNamespace ShowPlanNs = "http://schemas.microsoft.com/sqlserver/2004/07/showplan"; + + /// + /// Holds the query plan analysis result. + /// + public sealed class QueryInfo + { + public string Sql { get; init; } = ""; + public int EstimatedRows { get; init; } + public List OutputColumns { get; init; } = []; + public bool IsGroupingQuery { get; init; } + } + + public sealed class ColumnInfo + { + public string Name { get; init; } = ""; + public int Ordinal { get; init; } + public Type ClrType { get; init; } = typeof(object); + } + + public static QueryInfo AnalyzeSqlCommand(DbCommand command) + { + var connection = command.Connection; + if (connection == null) return new QueryInfo { Sql = command.ToQueryString() }; + + bool wasClosed = connection.State == ConnectionState.Closed; + if (wasClosed) connection.Open(); + + string sql = command.ToQueryString(); + string planXml = GetShowPlanXml(command); + int estimatedRows = ParsePlan(planXml); + List outputColumns = GetOutputColumnsInfo(command); + + if (wasClosed) connection.Close(); + + bool isGroupingQuery = sql.Contains("GROUP BY", StringComparison.OrdinalIgnoreCase) || + sql.Contains("DISTINCT", StringComparison.OrdinalIgnoreCase); + + return new QueryInfo + { + Sql = sql, + EstimatedRows = estimatedRows, + OutputColumns = outputColumns, + IsGroupingQuery = isGroupingQuery + }; + } + + /// + /// Given a command, obtains the SHOWPLAN_XML from SQL Server. + /// + private static string GetShowPlanXml(DbCommand command) + { + using (var enableCmd = command.Connection!.CreateCommand()) + { + if (command.Transaction != null) enableCmd.Transaction = command.Transaction; + enableCmd.CommandText = "SET SHOWPLAN_XML ON"; + enableCmd.ExecuteNonQuery(); + } + + string planXml = (string)(command.ExecuteScalar() ?? ""); + + // Disable SHOWPLAN_XML + using (var disableCmd = command.Connection.CreateCommand()) + { + if (command.Transaction != null) disableCmd.Transaction = command.Transaction; + disableCmd.CommandText = "SET SHOWPLAN_XML OFF"; + disableCmd.ExecuteNonQuery(); + } + + return planXml; + } + + private static int ParsePlan(string planXml) + { + if (string.IsNullOrWhiteSpace(planXml)) return 1; + + var doc = XDocument.Parse(planXml); + + // Find the top-level StmtSimple (the actual SELECT statement) + var statement = doc.Descendants(ShowPlanNs + "StmtSimple") + .FirstOrDefault(e => e.Attribute("StatementType")?.Value == "SELECT"); + + int estimatedRows = 1; + if (statement != null) + { + var estRowsAttr = statement.Attribute("StatementEstRows"); + if (estRowsAttr != null && double.TryParse(estRowsAttr.Value, NumberStyles.Float, + CultureInfo.InvariantCulture, out var estRows)) + { + estimatedRows = Math.Max(1, (int)Math.Ceiling(estRows)); + } + } + + return estimatedRows; + } + + private static List GetOutputColumnsInfo(DbCommand command) + { + var columns = new List(); + using var reader = command.ExecuteReader(CommandBehavior.SchemaOnly); + var schemaTable = reader.GetSchemaTable(); + if (schemaTable != null) + { + foreach (DataRow row in schemaTable.Rows) + { + string columnName = row["ColumnName"] as string ?? ""; + int columnOrdinal = (int)row["ColumnOrdinal"]; + Type clrType = row["DataType"] as Type ?? typeof(object); + //if (clrType == typeof(DateTime)) + //{ + // // Map DateTime to DateOnly for better compatibility with EF Core's type mapping + // clrType = typeof(DateOnly); + //} + columns.Add(new ColumnInfo { Name = columnName, Ordinal = columnOrdinal, ClrType = clrType }); + } + } + + return columns; + } + + /// + /// Generates a default constant value for the given CLR type. + /// + public static object GetDefaultValue(Type type, int rowIndex = 0, bool isGrouping = false) + { + if (type == typeof(int)) return isGrouping ? 1 + rowIndex : 1; + if (type == typeof(long)) return isGrouping ? 1L + rowIndex : 1L; + if (type == typeof(short)) return isGrouping ? (short)(1 + rowIndex) : (short)1; + if (type == typeof(byte)) return isGrouping ? (byte)(1 + (rowIndex % 254)) : (byte)1; + if (type == typeof(bool)) return isGrouping ? (rowIndex % 2 == 0) : false; + if (type == typeof(decimal)) return isGrouping ? 1.00m + rowIndex : 1.00m; + if (type == typeof(double)) return isGrouping ? 1.0d + rowIndex : 1.0d; + if (type == typeof(float)) return isGrouping ? 1.0f + rowIndex : 1.0f; + if (type == typeof(DateTime)) + return isGrouping ? new DateTime(2000, 1, 1).AddDays(rowIndex) : new DateTime(2000, 1, 1); + if (type == typeof(DateOnly)) + return isGrouping ? new DateOnly(2000, 1, 1).AddDays(rowIndex) : new DateOnly(2000, 1, 1); + if (type == typeof(TimeSpan)) return isGrouping ? TimeSpan.FromMinutes(rowIndex) : TimeSpan.Zero; + if (type == typeof(DateTimeOffset)) + return isGrouping + ? new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero).AddDays(rowIndex) + : new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero); + if (type == typeof(Guid)) return isGrouping ? Guid.NewGuid() : Guid.Empty; + if (type == typeof(string)) return isGrouping ? $"\"{rowIndex}\"" : "null"; + if (type == typeof(byte[])) return isGrouping ? new byte[] { (byte)(rowIndex % 255) } : new byte[] { 0 }; + return isGrouping ? 1 + rowIndex : 1; + } +} diff --git a/benchmarks/Common/appsettings.json b/benchmarks/Common/appsettings.json index 5cf9285..7842289 100644 --- a/benchmarks/Common/appsettings.json +++ b/benchmarks/Common/appsettings.json @@ -1,13 +1,13 @@ { - "ConnectionStrings": { - "MSSQL": "Data Source=127.0.0.1,1444;Initial Catalog=WideWorldImporters;User ID=SA;Password=Testingorms123;TrustServerCertificate=True" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore.Database.Command": "Information" + "ConnectionStrings": { + "MSSQL": "Data Source=127.0.0.1,1444;Initial Catalog=WideWorldImporters;User ID=SA;Password=Testingorms123;TrustServerCertificate=True" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.EntityFrameworkCore.Database.Command": "Information" } - } + } } diff --git a/benchmarks/DapperPerformance/DapperBenchmark.cs b/benchmarks/DapperPerformance/DapperBenchmark.cs index 13e773e..84c25c9 100644 --- a/benchmarks/DapperPerformance/DapperBenchmark.cs +++ b/benchmarks/DapperPerformance/DapperBenchmark.cs @@ -4,9 +4,9 @@ using Dapper; using DapperEntities; using Microsoft.Data.SqlClient; -using System.Data.Common; -using Common.Mock; -using Common.Mock.Dapper; +using System.Data.Common; +using Common.Mock; +using Common.Mock.Dapper; namespace DapperPerformance; @@ -14,9 +14,9 @@ namespace DapperPerformance; [ExceptionDiagnoser] public class DapperBenchmark { - private DbConnection connection = null!; - private readonly BenchmarkCommandExecutor commandExecutor = BenchmarkCommandExecutor.Instance; - private readonly Dictionary queryInfoCache = new(); + private DbConnection connection = null!; + private readonly BenchmarkCommandExecutor commandExecutor = BenchmarkCommandExecutor.Instance; + private readonly Dictionary queryInfoCache = new(); [GlobalSetup] public void GlobalSetup() @@ -24,78 +24,78 @@ public void GlobalSetup() /// Stored procedure result has spaces in column names which we can't map onto C# properties SqlMapper.SetTypeMap( typeof(PurchaseOrderUpdate), - new CustomPropertyTypeMap( + new CustomPropertyTypeMap( typeof(PurchaseOrderUpdate), (type, columnName) => type.GetProperty(columnName.Replace(" ", "").Replace("WWI", ""))! ) ); - ExtractQueryInfo(); - - connection = new FakeDbConnection(DatabaseConfig.MSSQLConnectionString, commandExecutor); + ExtractQueryInfo(); + + connection = new FakeDbConnection(DatabaseConfig.MSSQLConnectionString, commandExecutor); connection.Open(); } - private void ExtractQueryInfo() - { - Console.WriteLine("\n\n======== Extracting Dapper Query Info =========="); - using var realConnection = new SqlConnection(DatabaseConfig.MSSQLConnectionString); - realConnection.Open(); - - using var analyzerConnection = new DapperAnalyzerConnection(realConnection); - var originalConnection = connection; - connection = analyzerConnection; - - var methods = new (string Name, Action Action)[] - { - (nameof(A1_EntityIdenticalToTable), () => A1_EntityIdenticalToTable()), - (nameof(A2_LimitedEntity), () => A2_LimitedEntity()), - (nameof(A3_MultipleEntitiesFromOneResult), () => A3_MultipleEntitiesFromOneResult()), - (nameof(A4_StoredProcedureToEntity), () => A4_StoredProcedureToEntity()), - (nameof(B1_SelectionOverIndexedColumn), () => B1_SelectionOverIndexedColumn()), - (nameof(B2_SelectionOverNonIndexedColumn), () => B2_SelectionOverNonIndexedColumn()), - (nameof(B3_RangeQuery), () => B3_RangeQuery()), (nameof(B4_InQuery), () => B4_InQuery()), - (nameof(B5_TextSearch), () => B5_TextSearch()), (nameof(B6_PagingQuery), () => B6_PagingQuery()), - (nameof(C1_AggregationCount), () => C1_AggregationCount()), - (nameof(C2_AggregationMax), () => C2_AggregationMax()), - (nameof(C3_AggregationSum), () => C3_AggregationSum()), - (nameof(D1_OneToManyRelationship), () => D1_OneToManyRelationship()), - (nameof(D2_ManyToManyRelationship), () => D2_ManyToManyRelationship()), - (nameof(D3_OptionalRelationship), () => D3_OptionalRelationship()), - (nameof(E1_ColumnSorting), () => E1_ColumnSorting()), (nameof(E2_Distinct), () => E2_Distinct()), - (nameof(F1_JSONObjectQuery), () => F1_JSONObjectQuery()), - (nameof(F2_JSONArrayQuery), () => F2_JSONArrayQuery()), (nameof(G1_Union), () => G1_Union()), - (nameof(G2_Intersection), () => G2_Intersection()), (nameof(H1_Metadata), () => H1_Metadata()) - }; - - foreach (var method in methods) - { - using (var scope = RecordQueryInfoScope.StartNew()) - { - try - { - method.Action(); - } - catch (Exception ex) - { - Console.WriteLine($"Error extracting query info for {method.Name}: {ex.Message}"); - } - - var index = 1; - foreach (var queryInfo in scope.RecordedQueryInfos) - { - var cacheKey = scope.RecordedQueryInfos.Count > 1 ? method.Name + "_" + index++ : method.Name; - queryInfoCache[cacheKey] = queryInfo; - Console.WriteLine( - $"Analyzed {cacheKey}: {queryInfo.OutputColumns.Count} columns, ~{queryInfo.EstimatedRows} rows"); - } - } - } - - connection = originalConnection; - Console.WriteLine("================================================\n\n"); - } - + private void ExtractQueryInfo() + { + Console.WriteLine("\n\n======== Extracting Dapper Query Info =========="); + using var realConnection = new SqlConnection(DatabaseConfig.MSSQLConnectionString); + realConnection.Open(); + + using var analyzerConnection = new DapperAnalyzerConnection(realConnection); + var originalConnection = connection; + connection = analyzerConnection; + + var methods = new (string Name, Action Action)[] + { + (nameof(A1_EntityIdenticalToTable), () => A1_EntityIdenticalToTable()), + (nameof(A2_LimitedEntity), () => A2_LimitedEntity()), + (nameof(A3_MultipleEntitiesFromOneResult), () => A3_MultipleEntitiesFromOneResult()), + (nameof(A4_StoredProcedureToEntity), () => A4_StoredProcedureToEntity()), + (nameof(B1_SelectionOverIndexedColumn), () => B1_SelectionOverIndexedColumn()), + (nameof(B2_SelectionOverNonIndexedColumn), () => B2_SelectionOverNonIndexedColumn()), + (nameof(B3_RangeQuery), () => B3_RangeQuery()), (nameof(B4_InQuery), () => B4_InQuery()), + (nameof(B5_TextSearch), () => B5_TextSearch()), (nameof(B6_PagingQuery), () => B6_PagingQuery()), + (nameof(C1_AggregationCount), () => C1_AggregationCount()), + (nameof(C2_AggregationMax), () => C2_AggregationMax()), + (nameof(C3_AggregationSum), () => C3_AggregationSum()), + (nameof(D1_OneToManyRelationship), () => D1_OneToManyRelationship()), + (nameof(D2_ManyToManyRelationship), () => D2_ManyToManyRelationship()), + (nameof(D3_OptionalRelationship), () => D3_OptionalRelationship()), + (nameof(E1_ColumnSorting), () => E1_ColumnSorting()), (nameof(E2_Distinct), () => E2_Distinct()), + (nameof(F1_JSONObjectQuery), () => F1_JSONObjectQuery()), + (nameof(F2_JSONArrayQuery), () => F2_JSONArrayQuery()), (nameof(G1_Union), () => G1_Union()), + (nameof(G2_Intersection), () => G2_Intersection()), (nameof(H1_Metadata), () => H1_Metadata()) + }; + + foreach (var method in methods) + { + using (var scope = RecordQueryInfoScope.StartNew()) + { + try + { + method.Action(); + } + catch (Exception ex) + { + Console.WriteLine($"Error extracting query info for {method.Name}: {ex.Message}"); + } + + var index = 1; + foreach (var queryInfo in scope.RecordedQueryInfos) + { + var cacheKey = scope.RecordedQueryInfos.Count > 1 ? method.Name + "_" + index++ : method.Name; + queryInfoCache[cacheKey] = queryInfo; + Console.WriteLine( + $"Analyzed {cacheKey}: {queryInfo.OutputColumns.Count} columns, ~{queryInfo.EstimatedRows} rows"); + } + } + } + + connection = originalConnection; + Console.WriteLine("================================================\n\n"); + } + [GlobalCleanup] public void GlobalCleanup() { @@ -105,11 +105,11 @@ public void GlobalCleanup() [Benchmark] public PurchaseOrder A1_EntityIdenticalToTable() { - if (queryInfoCache.ContainsKey(nameof(A1_EntityIdenticalToTable))) - { - commandExecutor.Configure(queryInfoCache[nameof(A1_EntityIdenticalToTable)]); - } - + if (queryInfoCache.ContainsKey(nameof(A1_EntityIdenticalToTable))) + { + commandExecutor.Configure(queryInfoCache[nameof(A1_EntityIdenticalToTable)]); + } + var order = connection.QuerySingle( "SELECT * FROM WideWorldImporters.Purchasing.PurchaseOrders WHERE PurchaseOrderId = @PurchaseOrderId", new { PurchaseOrderId = 25 } @@ -121,11 +121,11 @@ public PurchaseOrder A1_EntityIdenticalToTable() [Benchmark] public SupplierContactInfo A2_LimitedEntity() { - if (queryInfoCache.ContainsKey(nameof(A2_LimitedEntity))) - { - commandExecutor.Configure(queryInfoCache[nameof(A2_LimitedEntity)]); - } - + if (queryInfoCache.ContainsKey(nameof(A2_LimitedEntity))) + { + commandExecutor.Configure(queryInfoCache[nameof(A2_LimitedEntity)]); + } + var contactInfo = connection.QuerySingle( "SELECT SupplierID, SupplierName, PhoneNumber, FaxNumber, WebsiteURL, ValidFrom, ValidTo FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID = @SupplierID", new { SupplierID = 10 } @@ -137,23 +137,23 @@ public SupplierContactInfo A2_LimitedEntity() [Benchmark] public (SupplierContactInfo, SupplierBankAccount) A3_MultipleEntitiesFromOneResult() { - if (queryInfoCache.ContainsKey(nameof(A3_MultipleEntitiesFromOneResult))) - { - commandExecutor.Configure(queryInfoCache[nameof(A3_MultipleEntitiesFromOneResult)]); - } - - var result = connection - .Query( - """ + if (queryInfoCache.ContainsKey(nameof(A3_MultipleEntitiesFromOneResult))) + { + commandExecutor.Configure(queryInfoCache[nameof(A3_MultipleEntitiesFromOneResult)]); + } + + var result = connection + .Query( + """ SELECT SupplierId, SupplierName, PhoneNumber, FaxNumber, WebsiteURL, ValidFrom, ValidTo, SupplierId, BankAccountName, BankAccountBranch, BankAccountCode, BankAccountNumber, BankInternationalCode FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID = @SupplierID """, - (contactInfo, bankAccount) => (contactInfo, bankAccount), - new { SupplierID = 10 }, - splitOn: nameof(SupplierBankAccount.SupplierID) - ).Single(); + (contactInfo, bankAccount) => (contactInfo, bankAccount), + new { SupplierID = 10 }, + splitOn: nameof(SupplierBankAccount.SupplierID) + ).Single(); return result; } @@ -161,11 +161,11 @@ public SupplierContactInfo A2_LimitedEntity() [Benchmark] public List A4_StoredProcedureToEntity() { - if (queryInfoCache.ContainsKey(nameof(A4_StoredProcedureToEntity))) - { - commandExecutor.Configure(queryInfoCache[nameof(A4_StoredProcedureToEntity)]); - } - + if (queryInfoCache.ContainsKey(nameof(A4_StoredProcedureToEntity))) + { + commandExecutor.Configure(queryInfoCache[nameof(A4_StoredProcedureToEntity)]); + } + var orders = connection.Query( "WideWorldImporters.Integration.GetOrderUpdates", new { LastCutoff = new DateTime(2014, 1, 1), NewCutoff = new DateTime(2015, 1, 1) }, @@ -180,11 +180,11 @@ public List B1_SelectionOverIndexedColumn() { int orderId = 26866; - if (queryInfoCache.ContainsKey(nameof(B1_SelectionOverIndexedColumn))) - { - commandExecutor.Configure(queryInfoCache[nameof(B1_SelectionOverIndexedColumn)]); - } - + if (queryInfoCache.ContainsKey(nameof(B1_SelectionOverIndexedColumn))) + { + commandExecutor.Configure(queryInfoCache[nameof(B1_SelectionOverIndexedColumn)]); + } + var orderLines = connection.Query( "SELECT * FROM WideWorldImporters.Sales.OrderLines WHERE OrderID = @OrderID", new { OrderID = orderId } @@ -198,11 +198,11 @@ public List B2_SelectionOverNonIndexedColumn() { decimal unitPrice = 25m; - if (queryInfoCache.ContainsKey(nameof(B2_SelectionOverNonIndexedColumn))) - { - commandExecutor.Configure(queryInfoCache[nameof(B2_SelectionOverNonIndexedColumn)]); - } - + if (queryInfoCache.ContainsKey(nameof(B2_SelectionOverNonIndexedColumn))) + { + commandExecutor.Configure(queryInfoCache[nameof(B2_SelectionOverNonIndexedColumn)]); + } + var orderLines = connection.Query( "SELECT * FROM WideWorldImporters.Sales.OrderLines WHERE UnitPrice = @UnitPrice", new { UnitPrice = unitPrice } @@ -216,12 +216,12 @@ public List B3_RangeQuery() { var from = new DateTime(2014, 12, 20); var to = new DateTime(2014, 12, 31); - - if (queryInfoCache.ContainsKey(nameof(B3_RangeQuery))) - { - commandExecutor.Configure(queryInfoCache[nameof(B3_RangeQuery)]); - } - + + if (queryInfoCache.ContainsKey(nameof(B3_RangeQuery))) + { + commandExecutor.Configure(queryInfoCache[nameof(B3_RangeQuery)]); + } + var orderLines = connection.Query( "SELECT * FROM WideWorldImporters.Sales.OrderLines WHERE PickingCompletedWhen BETWEEN @Start AND @End", new { Start = from, End = to } @@ -235,11 +235,11 @@ public List B4_InQuery() { var orderIds = new[] { 1, 10, 100, 1000, 10000 }; - if (queryInfoCache.ContainsKey(nameof(B4_InQuery))) - { - commandExecutor.Configure(queryInfoCache[nameof(B4_InQuery)]); - } - + if (queryInfoCache.ContainsKey(nameof(B4_InQuery))) + { + commandExecutor.Configure(queryInfoCache[nameof(B4_InQuery)]); + } + var orderLines = connection.Query( "SELECT * FROM WideWorldImporters.Sales.OrderLines WHERE OrderID IN @OrderIds", new { OrderIds = orderIds } @@ -253,11 +253,11 @@ public List B5_TextSearch() { string text = "C++"; - if (queryInfoCache.ContainsKey(nameof(B5_TextSearch))) - { - commandExecutor.Configure(queryInfoCache[nameof(B5_TextSearch)]); - } - + if (queryInfoCache.ContainsKey(nameof(B5_TextSearch))) + { + commandExecutor.Configure(queryInfoCache[nameof(B5_TextSearch)]); + } + var orderLines = connection.Query( "SELECT * FROM WideWorldImporters.Sales.OrderLines WHERE Description LIKE @Text", new { Text = $"%{text}%" } @@ -272,11 +272,11 @@ public List B6_PagingQuery() int skip = 1000; int take = 50; - if (queryInfoCache.ContainsKey(nameof(B6_PagingQuery))) - { - commandExecutor.Configure(queryInfoCache[nameof(B6_PagingQuery)]); - } - + if (queryInfoCache.ContainsKey(nameof(B6_PagingQuery))) + { + commandExecutor.Configure(queryInfoCache[nameof(B6_PagingQuery)]); + } + var orderLines = connection.Query( "SELECT * FROM WideWorldImporters.Sales.OrderLines ORDER BY OrderLineID OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY", new { Skip = skip, Take = take } @@ -288,11 +288,11 @@ public List B6_PagingQuery() [Benchmark] public Dictionary C1_AggregationCount() { - if (queryInfoCache.ContainsKey(nameof(C1_AggregationCount))) - { - commandExecutor.Configure(queryInfoCache[nameof(C1_AggregationCount)]); - } - + if (queryInfoCache.ContainsKey(nameof(C1_AggregationCount))) + { + commandExecutor.Configure(queryInfoCache[nameof(C1_AggregationCount)]); + } + var taxRates = connection.Query<(decimal, int)>( """ SELECT TaxRate, COUNT(TaxRate) as Count @@ -308,11 +308,11 @@ ORDER BY Count DESC [Benchmark] public decimal C2_AggregationMax() { - if (queryInfoCache.ContainsKey(nameof(C2_AggregationMax))) - { - commandExecutor.Configure(queryInfoCache[nameof(C2_AggregationMax)]); - } - + if (queryInfoCache.ContainsKey(nameof(C2_AggregationMax))) + { + commandExecutor.Configure(queryInfoCache[nameof(C2_AggregationMax)]); + } + var maxUnitPrice = connection.Query( "SELECT MAX(UnitPrice) FROM WideWorldImporters.Sales.OrderLines" ).Single(); @@ -323,11 +323,11 @@ public decimal C2_AggregationMax() [Benchmark] public decimal C3_AggregationSum() { - if (queryInfoCache.ContainsKey(nameof(C3_AggregationSum))) - { - commandExecutor.Configure(queryInfoCache[nameof(C3_AggregationSum)]); - } - + if (queryInfoCache.ContainsKey(nameof(C3_AggregationSum))) + { + commandExecutor.Configure(queryInfoCache[nameof(C3_AggregationSum)]); + } + var totalSales = connection.Query( "SELECT SUM(Quantity * UnitPrice) FROM WideWorldImporters.Sales.OrderLines" ).Single(); @@ -339,35 +339,35 @@ public decimal C3_AggregationSum() public Order D1_OneToManyRelationship() { string sql = """ - SELECT o.*, ol.* FROM WideWorldImporters.Sales.Orders o - LEFT JOIN WideWorldImporters.Sales.OrderLines ol - ON ol.OrderID = o.OrderID - WHERE o.OrderID = @OrderID - """; - - if (queryInfoCache.ContainsKey(nameof(D1_OneToManyRelationship))) - { - commandExecutor.Configure(queryInfoCache[nameof(D1_OneToManyRelationship)]); - } + SELECT o.*, ol.* FROM WideWorldImporters.Sales.Orders o + LEFT JOIN WideWorldImporters.Sales.OrderLines ol + ON ol.OrderID = o.OrderID + WHERE o.OrderID = @OrderID + """; + + if (queryInfoCache.ContainsKey(nameof(D1_OneToManyRelationship))) + { + commandExecutor.Configure(queryInfoCache[nameof(D1_OneToManyRelationship)]); + } var order = connection.Query( - sql, - (order, orderLine) => - { - order.OrderLines.Add(orderLine); - return order; - }, - new { OrderID = 530 }, - splitOn: "OrderLineID" - ) - .GroupBy(o => o.OrderID) - .Select(g => + sql, + (order, orderLine) => + { + order.OrderLines.Add(orderLine); + return order; + }, + new { OrderID = 530 }, + splitOn: "OrderLineID" + ) + .GroupBy(o => o.OrderID) + .Select(g => { - var groupedOrder = g.First(); - groupedOrder.OrderLines = g.Select(o => o.OrderLines.Single()).ToList(); - return groupedOrder; - }) - .Single(); + var groupedOrder = g.First(); + groupedOrder.OrderLines = g.Select(o => o.OrderLines.Single()).ToList(); + return groupedOrder; + }) + .Single(); return order; } @@ -376,29 +376,29 @@ LEFT JOIN WideWorldImporters.Sales.OrderLines ol public (List stockItems, List stockGroups) D2_ManyToManyRelationship() { string sqlItems = """ - SELECT - si.StockItemID, - si.StockItemName, - si.SupplierID, - si.Brand, - si.Size, - sg.StockGroupID, - sg.StockGroupName - FROM WideWorldImporters.Warehouse.StockItems si - LEFT JOIN WideWorldImporters.Warehouse.StockItemStockGroups sisg - ON si.StockItemID = sisg.StockItemID - LEFT JOIN WideWorldImporters.Warehouse.StockGroups sg - ON sisg.StockGroupID = sg.StockGroupID - ORDER BY si.StockItemID - """; + SELECT + si.StockItemID, + si.StockItemName, + si.SupplierID, + si.Brand, + si.Size, + sg.StockGroupID, + sg.StockGroupName + FROM WideWorldImporters.Warehouse.StockItems si + LEFT JOIN WideWorldImporters.Warehouse.StockItemStockGroups sisg + ON si.StockItemID = sisg.StockItemID + LEFT JOIN WideWorldImporters.Warehouse.StockGroups sg + ON sisg.StockGroupID = sg.StockGroupID + ORDER BY si.StockItemID + """; var stockItemsById = new Dictionary(); - if (queryInfoCache.ContainsKey(nameof(D2_ManyToManyRelationship) + "_1")) - { - commandExecutor.Configure(queryInfoCache[nameof(D2_ManyToManyRelationship) + "_1"]); - } - + if (queryInfoCache.ContainsKey(nameof(D2_ManyToManyRelationship) + "_1")) + { + commandExecutor.Configure(queryInfoCache[nameof(D2_ManyToManyRelationship) + "_1"]); + } + connection.Query( sqlItems, (stockItem, stockGroup) => @@ -419,29 +419,29 @@ ORDER BY si.StockItemID var stockItems = stockItemsById.Values.ToList(); string sqlGroups = """ - SELECT - sg.StockGroupID, - sg.StockGroupName, - si.StockItemID, - si.StockItemName, - si.SupplierID, - si.Brand, - si.Size - FROM WideWorldImporters.Warehouse.StockGroups sg - LEFT JOIN WideWorldImporters.Warehouse.StockItemStockGroups sisg - ON sg.StockGroupID = sisg.StockGroupID - LEFT JOIN WideWorldImporters.Warehouse.StockItems si - ON sisg.StockItemID = si.StockItemID - ORDER BY sg.StockGroupID - """; + SELECT + sg.StockGroupID, + sg.StockGroupName, + si.StockItemID, + si.StockItemName, + si.SupplierID, + si.Brand, + si.Size + FROM WideWorldImporters.Warehouse.StockGroups sg + LEFT JOIN WideWorldImporters.Warehouse.StockItemStockGroups sisg + ON sg.StockGroupID = sisg.StockGroupID + LEFT JOIN WideWorldImporters.Warehouse.StockItems si + ON sisg.StockItemID = si.StockItemID + ORDER BY sg.StockGroupID + """; var stockGroupsById = new Dictionary(); - if (queryInfoCache.ContainsKey(nameof(D2_ManyToManyRelationship) + "_2")) - { - commandExecutor.Configure(queryInfoCache[nameof(D2_ManyToManyRelationship) + "_2"]); - } - + if (queryInfoCache.ContainsKey(nameof(D2_ManyToManyRelationship) + "_2")) + { + commandExecutor.Configure(queryInfoCache[nameof(D2_ManyToManyRelationship) + "_2"]); + } + connection.Query( sqlGroups, (stockGroup, stockItem) => @@ -468,22 +468,22 @@ ORDER BY sg.StockGroupID public List D3_OptionalRelationship() { string sql = """ - SELECT - c.CustomerID, c.CustomerName, c.AccountOpenedDate, c.CreditLimit, - ct.CustomerTransactionID, ct.CustomerID, ct.TransactionDate, ct.TransactionAmount - FROM WideWorldImporters.Sales.Customers c - LEFT OUTER JOIN WideWorldImporters.Sales.CustomerTransactions ct - ON c.CustomerID = ct.CustomerID - ORDER BY c.CustomerID - """; + SELECT + c.CustomerID, c.CustomerName, c.AccountOpenedDate, c.CreditLimit, + ct.CustomerTransactionID, ct.CustomerID, ct.TransactionDate, ct.TransactionAmount + FROM WideWorldImporters.Sales.Customers c + LEFT OUTER JOIN WideWorldImporters.Sales.CustomerTransactions ct + ON c.CustomerID = ct.CustomerID + ORDER BY c.CustomerID + """; var customers = new Dictionary(); - if (queryInfoCache.ContainsKey(nameof(D3_OptionalRelationship))) - { - commandExecutor.Configure(queryInfoCache[nameof(D3_OptionalRelationship)]); - } - + if (queryInfoCache.ContainsKey(nameof(D3_OptionalRelationship))) + { + commandExecutor.Configure(queryInfoCache[nameof(D3_OptionalRelationship)]); + } + connection.Query( sql, (customer, transaction) => @@ -511,11 +511,11 @@ ORDER BY c.CustomerID [Benchmark] public List E1_ColumnSorting() { - if (queryInfoCache.ContainsKey(nameof(E1_ColumnSorting))) - { - commandExecutor.Configure(queryInfoCache[nameof(E1_ColumnSorting)]); - } - + if (queryInfoCache.ContainsKey(nameof(E1_ColumnSorting))) + { + commandExecutor.Configure(queryInfoCache[nameof(E1_ColumnSorting)]); + } + var orders = connection.Query( "SELECT TOP (1000) * FROM WideWorldImporters.Purchasing.PurchaseOrders ORDER BY ExpectedDeliveryDate ASC" ).ToList(); @@ -526,11 +526,11 @@ public List E1_ColumnSorting() [Benchmark] public List E2_Distinct() { - if (queryInfoCache.ContainsKey(nameof(E2_Distinct))) - { - commandExecutor.Configure(queryInfoCache[nameof(E2_Distinct)]); - } - + if (queryInfoCache.ContainsKey(nameof(E2_Distinct))) + { + commandExecutor.Configure(queryInfoCache[nameof(E2_Distinct)]); + } + var supplierReferences = connection.Query( "SELECT DISTINCT SupplierReference FROM WideWorldImporters.Purchasing.PurchaseOrders" ).ToList(); @@ -542,16 +542,16 @@ public List E2_Distinct() public List F1_JSONObjectQuery() { var sql = """ - SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages - FROM WideWorldImporters.Application.People - WHERE JSON_VALUE(CustomFields, '$.Title') = @Title - ORDER BY PersonID - """; - - if (queryInfoCache.ContainsKey(nameof(F1_JSONObjectQuery))) - { - commandExecutor.Configure(queryInfoCache[nameof(F1_JSONObjectQuery)]); - } + SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages + FROM WideWorldImporters.Application.People + WHERE JSON_VALUE(CustomFields, '$.Title') = @Title + ORDER BY PersonID + """; + + if (queryInfoCache.ContainsKey(nameof(F1_JSONObjectQuery))) + { + commandExecutor.Configure(queryInfoCache[nameof(F1_JSONObjectQuery)]); + } var people = connection.Query(sql, new { Title = "Team Member" }).ToList(); @@ -565,19 +565,19 @@ ORDER BY PersonID public List F2_JSONArrayQuery() { var sql = """ - SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages - FROM WideWorldImporters.Application.People - WHERE EXISTS ( - SELECT 1 - FROM OPENJSON(OtherLanguages) - WHERE value = @Language - ) - """; - - if (queryInfoCache.ContainsKey(nameof(F2_JSONArrayQuery))) - { - commandExecutor.Configure(queryInfoCache[nameof(F2_JSONArrayQuery)]); - } + SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages + FROM WideWorldImporters.Application.People + WHERE EXISTS ( + SELECT 1 + FROM OPENJSON(OtherLanguages) + WHERE value = @Language + ) + """; + + if (queryInfoCache.ContainsKey(nameof(F2_JSONArrayQuery))) + { + commandExecutor.Configure(queryInfoCache[nameof(F2_JSONArrayQuery)]); + } var people = connection.Query(sql, new { Language = "Slovak" }).ToList(); @@ -590,17 +590,17 @@ FROM OPENJSON(OtherLanguages) [Benchmark] public List G1_Union() { - if (queryInfoCache.ContainsKey(nameof(G1_Union))) - { - commandExecutor.Configure(queryInfoCache[nameof(G1_Union)]); - } - + if (queryInfoCache.ContainsKey(nameof(G1_Union))) + { + commandExecutor.Configure(queryInfoCache[nameof(G1_Union)]); + } + var suppliers = connection.Query(""" - SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID < 5 - UNION - SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID BETWEEN 5 AND 10 - ORDER BY SupplierID - """ + SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID < 5 + UNION + SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID BETWEEN 5 AND 10 + ORDER BY SupplierID + """ ).ToList(); return suppliers; @@ -609,17 +609,17 @@ ORDER BY SupplierID [Benchmark] public List G2_Intersection() { - if (queryInfoCache.ContainsKey(nameof(G2_Intersection))) - { - commandExecutor.Configure(queryInfoCache[nameof(G2_Intersection)]); - } - + if (queryInfoCache.ContainsKey(nameof(G2_Intersection))) + { + commandExecutor.Configure(queryInfoCache[nameof(G2_Intersection)]); + } + var suppliers = connection.Query(""" - SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID < 10 - INTERSECT - SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID BETWEEN 5 AND 15 - ORDER BY SupplierID - """ + SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID < 10 + INTERSECT + SELECT SupplierID FROM WideWorldImporters.Purchasing.Suppliers WHERE SupplierID BETWEEN 5 AND 15 + ORDER BY SupplierID + """ ).ToList(); return suppliers; @@ -628,17 +628,17 @@ ORDER BY SupplierID [Benchmark] public string H1_Metadata() { - if (queryInfoCache.ContainsKey(nameof(H1_Metadata))) - { - commandExecutor.Configure(queryInfoCache[nameof(H1_Metadata)]); - } - + if (queryInfoCache.ContainsKey(nameof(H1_Metadata))) + { + commandExecutor.Configure(queryInfoCache[nameof(H1_Metadata)]); + } + var datatype = connection.Query(""" - SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = 'Purchasing' - AND TABLE_NAME = 'Suppliers' - AND COLUMN_NAME = 'SupplierReference' - """ + SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'Purchasing' + AND TABLE_NAME = 'Suppliers' + AND COLUMN_NAME = 'SupplierReference' + """ ).Single(); return datatype; diff --git a/benchmarks/EFCorePerformance/EFCoreBenchmarks.cs b/benchmarks/EFCorePerformance/EFCoreBenchmarks.cs index 82b65b5..0a8987c 100644 --- a/benchmarks/EFCorePerformance/EFCoreBenchmarks.cs +++ b/benchmarks/EFCorePerformance/EFCoreBenchmarks.cs @@ -1,695 +1,695 @@ -using BenchmarkDotNet.Attributes; -using Common; -using Common.Mock; -using Common.Mock.EFCore; -using EFCoreEntities; -using EFCoreEntities.Models; -using Microsoft.Data.SqlClient; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.VSDiagnostics; - -namespace EFCorePerformance -{ - [MemoryDiagnoser] - [ExceptionDiagnoser] - public class EFCoreBenchmarks - { - private PooledDbContextFactory contextFactory = null!; - private readonly BenchmarkCommandExecutor commandExecutor = BenchmarkCommandExecutor.Instance; - private Dictionary queryInfoCache = new(); - - [GlobalSetup] - public void GlobalSetup() - { - var options = new DbContextOptionsBuilder() - .UseSqlServer(DatabaseConfig.MSSQLConnectionString) - // We only test read queries, tracking is not needed and might slow down the tests - .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) - .ConfigureWarnings(warnings => - { - warnings.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning); - // we only read the values, so we don't need to worry about precision when saving - warnings.Ignore(SqlServerEventId.DecimalTypeDefaultWarning); - }) - .AddInterceptors(RecordCommandsInterceptor.Instance) - .LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }) - .Options; - - contextFactory = new(options); - - ExtractQueryInfo(); - - var benchmarkOptions = new DbContextOptionsBuilder() - .UseSqlServer(DatabaseConfig.MSSQLConnectionString) - // We only test read queries, tracking is not needed and might slow down the tests - .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) - .ConfigureWarnings(warnings => - { - warnings.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning); - // we only read the values, so we don't need to worry about precision when saving - warnings.Ignore(SqlServerEventId.DecimalTypeDefaultWarning); - }) - .AddInterceptors(new BenchmarkCommandInterceptor(commandExecutor)) - //.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }) - .Options; - - contextFactory = new(benchmarkOptions); - commandExecutor.UseUniqueColumnNames(true); - } - - private void ExtractQueryInfo() - { - using var context = contextFactory.CreateDbContext(); - - queryInfoCache.Add("A1_EntityIdenticalToTable", - context.RecordQueryInfos(ctx => ctx.PurchaseOrders.Find(25) - ).First()); - - queryInfoCache.Add("A2_LimitedEntity", - context.RecordQueryInfos(ctx => ctx.Suppliers - .Where(s => s.SupplierID == 10) - .Select(s => new SupplierContactInfo - { - SupplierID = s.SupplierID, - SupplierName = s.SupplierName, - PhoneNumber = s.PhoneNumber, - FaxNumber = s.FaxNumber, - WebsiteURL = s.WebsiteURL, - ValidFrom = s.ValidFrom, - ValidTo = s.ValidTo - }) - .SingleOrDefault() - ).First()); - - queryInfoCache.Add("A3_MultipleEntitiesFromOneResult", - context.RecordQueryInfos(ctx => ctx.Suppliers - .Where(s => s.SupplierID == 10) - .Select(s => new - { - ContactInfo = - new SupplierContactInfo - { - SupplierID = s.SupplierID, - SupplierName = s.SupplierName, - PhoneNumber = s.PhoneNumber, - FaxNumber = s.FaxNumber, - WebsiteURL = s.WebsiteURL, - ValidFrom = s.ValidFrom, - ValidTo = s.ValidTo - }, - BankAccount = new SupplierBankAccount - { - SupplierID = s.SupplierID, - BankAccountName = s.BankAccountName, - BankAccountBranch = s.BankAccountBranch, - BankAccountCode = s.BankAccountCode, - BankAccountNumber = s.BankAccountNumber, - BankInternationalCode = s.BankInternationalCode - } - }) - .Select(result => new { result.ContactInfo, result.BankAccount }) - .SingleOrDefault() - ).First()); - - var spFrom = new SqlParameter("@p0", new DateTime(2014, 1, 1)); - var spTo = new SqlParameter("@p1", new DateTime(2015, 1, 1)); - queryInfoCache.Add("A4_StoredProcedureToEntity", - context.RecordQueryInfos(ctx => ctx.Set() - .FromSqlInterpolated( - $"EXEC WideWorldImporters.Integration.GetOrderUpdates @LastCutoff = {spFrom}, @NewCutoff = {spTo}") - .ToList() - ).First()); - - int orderId = 26866; - queryInfoCache.Add("B1_SelectionOverIndexedColumn", - context.RecordQueryInfos(ctx => ctx.OrderLines - .Where(ol => ol.OrderID == orderId) - .ToList() - ).First()); - - decimal unitPrice = 25m; - queryInfoCache.Add("B2_SelectionOverNonIndexedColumn", - context.RecordQueryInfos(ctx => ctx.OrderLines - .Where(ol => ol.UnitPrice == unitPrice) - .ToList() - ).First()); - - var rangeFrom = new DateTime(2014, 12, 20); - var rangeTo = new DateTime(2014, 12, 31); - queryInfoCache.Add("B3_RangeQuery", - context.RecordQueryInfos(ctx => ctx.OrderLines - .Where(ol => ol.PickingCompletedWhen >= rangeFrom && ol.PickingCompletedWhen <= rangeTo) - .ToList() - ).First()); - - var orderIds = new[] { 1, 10, 100, 1000, 10000 }; - queryInfoCache.Add("B4_InQuery", - context.RecordQueryInfos(ctx => ctx.OrderLines - .Where(ol => orderIds.Contains(ol.OrderID)) - .ToList() - ).First()); - - string text = "C++"; - queryInfoCache.Add("B5_TextSearch", - context.RecordQueryInfos(ctx => ctx.OrderLines - .Where(ol => ol.Description.Contains(text)) - .ToList() - ).First()); - - int skip = 1000; - int take = 50; - queryInfoCache.Add("B6_PagingQuery", - context.RecordQueryInfos(ctx => ctx.OrderLines - .OrderBy(ol => ol.OrderLineID) - .Skip(skip) - .Take(take) - .ToList() - ).First()); - - queryInfoCache.Add("C1_AggregationCount", - context.RecordQueryInfos(ctx => ctx.OrderLines - .GroupBy(ol => ol.TaxRate) - .Select(g => new { TaxRate = g.Key, Count = g.Count() }) - .OrderByDescending(x => x.Count) - .ToDictionary(x => x.TaxRate, x => x.Count) - ).First()); - - queryInfoCache.Add("C2_AggregationMax", - context.RecordQueryInfos(ctx => ctx.OrderLines.Max(ol => ol.UnitPrice) - ).First()); - - queryInfoCache.Add("C3_AggregationSum", - context.RecordQueryInfos(ctx => ctx.OrderLines.Sum(ol => ol.Quantity * ol.UnitPrice) - ).First()); - - queryInfoCache.Add("D1_OneToManyRelationship", - context.RecordQueryInfos(ctx => ctx.Orders - .Include(o => o.OrderLines) - .SingleOrDefault(o => o.OrderID == 530) - ).First()); - - queryInfoCache.Add("D2_StockItems", - context.RecordQueryInfos(ctx => ctx.StockItems - .Include(si => si.StockGroups) - .OrderBy(si => si.StockItemID) - .ToList() - ).First()); - - queryInfoCache.Add("D2_StockGroups", - context.RecordQueryInfos(ctx => ctx.StockGroups - .Include(sg => sg.StockItems) - .OrderBy(sg => sg.StockGroupID) - .ToList() - ).First()); - - queryInfoCache.Add("D3_OptionalRelationship", - context.RecordQueryInfos(ctx => ctx.Customers - .Include(c => c.Transactions) - .OrderBy(c => c.CustomerID) - .ToList() - ).First()); - - queryInfoCache.Add("E1_ColumnSorting", - context.RecordQueryInfos(ctx => ctx.PurchaseOrders - .OrderBy(po => po.ExpectedDeliveryDate) - .Take(1000) - .ToList() - ).First()); - - queryInfoCache.Add("E2_Distinct", - context.RecordQueryInfos(ctx => ctx.PurchaseOrders - .Select(po => po.SupplierReference) - .Distinct() - .ToList() - ).First()); - - queryInfoCache.Add("F1_JSONObjectQuery", - context.RecordQueryInfos(ctx => ctx.People - .Where(p => p.CustomFields!.Title == "Team Member") - .OrderBy(p => p.PersonID) - .ToList() - ).First()); - - queryInfoCache.Add("F2_JSONArrayQuery", - context.RecordQueryInfos(ctx => ctx.People - .Where(p => p.OtherLanguages!.Contains("Slovak")) - .OrderBy(p => p.PersonID) - .ToList() - ).First()); - - var g1SqlStrings = context.RecordQueryInfos(ctx => - { - var first = context.Suppliers - .Where(s => s.SupplierID < 5) - .Select(s => s.SupplierID) - .ToList(); - - var last = context.Suppliers - .Where(s => s.SupplierID >= 5 && s.SupplierID <= 10) - .Select(s => s.SupplierID) - .ToList(); - - var suppliers = first - .Union(last) - .OrderBy(s => s) - .ToList(); - - return suppliers; - } - ); - queryInfoCache.Add("G1_Union_1", g1SqlStrings.ElementAt(0)); - queryInfoCache.Add("G1_Union_2", g1SqlStrings.ElementAt(1)); - - var g2SqlStrings = context.RecordQueryInfos(ctx => - { - var first = context.Suppliers - .Where(s => s.SupplierID < 10) - .Select(s => s.SupplierID) - .ToList(); - - var last = context.Suppliers - .Where(s => s.SupplierID >= 5 && s.SupplierID <= 15) - .Select(s => s.SupplierID) - .ToList(); - - var suppliers = first - .Intersect(last) - .OrderBy(s => s) - .ToList(); - - return suppliers; - } - ); - queryInfoCache.Add("G2_Intersection_1", g2SqlStrings.ElementAt(0)); - queryInfoCache.Add("G2_Intersection_2", g2SqlStrings.ElementAt(1)); - - queryInfoCache.Add("H1_Metadata", - context.RecordQueryInfos(ctx => ctx.Database.SqlQueryRaw( - """ - SELECT DATA_TYPE AS Value FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = 'Purchasing' - AND TABLE_NAME = 'Suppliers' - AND COLUMN_NAME = 'SupplierReference' - """ - ).SingleOrDefault() - ).First()); - } - - [GlobalCleanup] - public void GlobalCleanup() { } - - [Benchmark] - public PurchaseOrder? A1_EntityIdenticalToTable() - { - commandExecutor.Configure(queryInfoCache["A1_EntityIdenticalToTable"]); - using var context = contextFactory.CreateDbContext(); - - var order = context.PurchaseOrders.Find(25); - - return order; - } - - [Benchmark] - public SupplierContactInfo A2_LimitedEntity() - { - commandExecutor.Configure(queryInfoCache["A2_LimitedEntity"]); - using var context = contextFactory.CreateDbContext(); - - var contactInfo = context.Suppliers - .Where(s => s.SupplierID == 10) - .Select(s => new SupplierContactInfo - { - SupplierID = s.SupplierID, - SupplierName = s.SupplierName, - PhoneNumber = s.PhoneNumber, - FaxNumber = s.FaxNumber, - WebsiteURL = s.WebsiteURL, - ValidFrom = s.ValidFrom, - ValidTo = s.ValidTo - }) - .Single(); - - return contactInfo; - } - - [Benchmark] - public (SupplierContactInfo ContactInfo, SupplierBankAccount BankAccount) A3_MultipleEntitiesFromOneResult() - { - commandExecutor.Configure(queryInfoCache["A3_MultipleEntitiesFromOneResult"]); - using var context = contextFactory.CreateDbContext(); - - var result = context.Suppliers - .Where(s => s.SupplierID == 10) - .Select(s => new - { - ContactInfo = - new SupplierContactInfo - { - SupplierID = s.SupplierID, - SupplierName = s.SupplierName, - PhoneNumber = s.PhoneNumber, - FaxNumber = s.FaxNumber, - WebsiteURL = s.WebsiteURL, - ValidFrom = s.ValidFrom, - ValidTo = s.ValidTo - }, - BankAccount = new SupplierBankAccount - { - SupplierID = s.SupplierID, - BankAccountName = s.BankAccountName, - BankAccountBranch = s.BankAccountBranch, - BankAccountCode = s.BankAccountCode, - BankAccountNumber = s.BankAccountNumber, - BankInternationalCode = s.BankInternationalCode - } - }) - .Select(result => new { result.ContactInfo, result.BankAccount }) - .Single(); - - return (result.ContactInfo, result.BankAccount); - } - - [Benchmark] - public List A4_StoredProcedureToEntity() - { - commandExecutor.Configure(queryInfoCache["A4_StoredProcedureToEntity"]); - using var context = contextFactory.CreateDbContext(); - - var from = new DateTime(2014, 1, 1); - var to = new DateTime(2015, 1, 1); - - var orders = context.Set() - .FromSqlInterpolated( - $"EXEC WideWorldImporters.Integration.GetOrderUpdates @LastCutoff = {from}, @NewCutoff = {to}") - .ToList(); - - return orders; - } - - [Benchmark] - public List B1_SelectionOverIndexedColumn() - { - commandExecutor.Configure(queryInfoCache["B1_SelectionOverIndexedColumn"]); - using var context = contextFactory.CreateDbContext(); - - int orderId = 26866; - - var orderLines = context.OrderLines - .Where(ol => ol.OrderID == orderId) - .ToList(); - - return orderLines; - } - - [Benchmark] - public List B2_SelectionOverNonIndexedColumn() - { - commandExecutor.Configure(queryInfoCache["B2_SelectionOverNonIndexedColumn"]); - using var context = contextFactory.CreateDbContext(); - - decimal unitPrice = 25m; - - var orderLines = context.OrderLines - .Where(ol => ol.UnitPrice == unitPrice) - .ToList(); - - return orderLines; - } - - [Benchmark] - public List B3_RangeQuery() - { - commandExecutor.Configure(queryInfoCache["B3_RangeQuery"]); - using var context = contextFactory.CreateDbContext(); - - var from = new DateTime(2014, 12, 20); - var to = new DateTime(2014, 12, 31); - - var orderLines = context.OrderLines - .Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to) - .ToList(); - - return orderLines; - } - - [Benchmark] - public List B4_InQuery() - { - commandExecutor.Configure(queryInfoCache["B4_InQuery"]); - using var context = contextFactory.CreateDbContext(); - - var orderIds = new[] { 1, 10, 100, 1000, 10000 }; - - var orderLines = context.OrderLines - .Where(ol => orderIds.Contains(ol.OrderID)) - .ToList(); - - return orderLines; - } - - [Benchmark] - public List B5_TextSearch() - { - commandExecutor.Configure(queryInfoCache["B5_TextSearch"]); - using var context = contextFactory.CreateDbContext(); - - string text = "C++"; - - var orderLines = context.OrderLines - //.Where(ol => EF.Functions.Like(ol.Description, $"%{text}%")) - .Where(ol => ol.Description.Contains(text)) - .ToList(); - - return orderLines; - } - - [Benchmark] - public List B6_PagingQuery() - { - commandExecutor.Configure(queryInfoCache["B6_PagingQuery"]); - using var context = contextFactory.CreateDbContext(); - - int skip = 1000; - int take = 50; - - var orderLines = context.OrderLines - .OrderBy(ol => ol.OrderLineID) - .Skip(skip) - .Take(take) - .ToList(); - - return orderLines; - } - - [Benchmark] - public Dictionary C1_AggregationCount() - { - commandExecutor.Configure(queryInfoCache["C1_AggregationCount"]); - using var context = contextFactory.CreateDbContext(); - - var taxRates = context.OrderLines - .GroupBy(ol => ol.TaxRate) - .Select(g => new { TaxRate = g.Key, Count = g.Count() }) - .OrderByDescending(x => x.Count) - .ToDictionary(x => x.TaxRate, x => x.Count); - - return taxRates; - } - - [Benchmark] - public decimal? C2_AggregationMax() - { - commandExecutor.Configure(queryInfoCache["C2_AggregationMax"]); - using var context = contextFactory.CreateDbContext(); - - var maxUnitPrice = context.OrderLines.Max(ol => ol.UnitPrice); - - return maxUnitPrice; - } - - [Benchmark] - public decimal? C3_AggregationSum() - { - commandExecutor.Configure(queryInfoCache["C3_AggregationSum"]); - using var context = contextFactory.CreateDbContext(); - - var totalSales = context.OrderLines - .Sum(ol => ol.Quantity * ol.UnitPrice); - - return totalSales; - } - - [Benchmark] - public Order D1_OneToManyRelationship() - { - commandExecutor.Configure(queryInfoCache["D1_OneToManyRelationship"]); - using var context = contextFactory.CreateDbContext(); - - var order = context.Orders - .Include(o => o.OrderLines) - .Single(o => o.OrderID == 530); - - return order; - } - - [Benchmark] - public (List stockItems, List stockGroups) D2_ManyToManyRelationship() - { - using var context = contextFactory.CreateDbContext(); - - commandExecutor.Configure(queryInfoCache["D2_StockItems"]); - var stockItems = context.StockItems - .Include(si => si.StockGroups) - .OrderBy(si => si.StockItemID) - .ToList(); - - commandExecutor.Configure(queryInfoCache["D2_StockGroups"]); - var stockGroups = context.StockGroups - .Include(sg => sg.StockItems) - .OrderBy(si => si.StockGroupID) - .ToList(); - - return (stockItems, stockGroups); - } - - [Benchmark] - public List D3_OptionalRelationship() - { - commandExecutor.Configure(queryInfoCache["D3_OptionalRelationship"]); - using var context = contextFactory.CreateDbContext(); - - var result = context.Customers - .Include(c => c.Transactions) - .OrderBy(c => c.CustomerID) - .ToList(); - - return result; - } - - [Benchmark] - public List E1_ColumnSorting() - { - commandExecutor.Configure(queryInfoCache["E1_ColumnSorting"]); - using var context = contextFactory.CreateDbContext(); - - var orders = context.PurchaseOrders - .OrderBy(po => po.ExpectedDeliveryDate) - .Take(1000) - .ToList(); - - return orders; - } - - [Benchmark] - public List E2_Distinct() - { - commandExecutor.Configure(queryInfoCache["E2_Distinct"]); - using var context = contextFactory.CreateDbContext(); - - var supplierReferences = context.PurchaseOrders - .Select(po => po.SupplierReference) - .Distinct() - .ToList(); - - return supplierReferences; - } - - [Benchmark] - public List F1_JSONObjectQuery() - { - commandExecutor.Configure(queryInfoCache["F1_JSONObjectQuery"]); - using var context = contextFactory.CreateDbContext(); - - var people = context.People - .Where(p => p.CustomFields!.Title == "Team Member") - .OrderBy(p => p.PersonID) - .ToList(); - - return people; - } - - [Benchmark] - public List F2_JSONArrayQuery() - { - commandExecutor.Configure(queryInfoCache["F2_JSONArrayQuery"]); - using var context = contextFactory.CreateDbContext(); - - var people = context.People - .Where(p => p.OtherLanguages!.Contains("Slovak")) - .OrderBy(p => p.PersonID) - .ToList(); - - return people; - } - - [Benchmark] - public List G1_Union() - { - using var context = contextFactory.CreateDbContext(); - - commandExecutor.Configure(queryInfoCache["G1_Union_1"]); - var first = context.Suppliers - .Where(s => s.SupplierID < 5) - .Select(s => s.SupplierID) - .ToList(); - - commandExecutor.Configure(queryInfoCache["G1_Union_2"]); - var last = context.Suppliers - .Where(s => s.SupplierID >= 5 && s.SupplierID <= 10) - .Select(s => s.SupplierID) - .ToList(); - - var suppliers = first - .Union(last) - .OrderBy(s => s) - .ToList(); - - return suppliers; - } - - [Benchmark] - public List G2_Intersection() - { - using var context = contextFactory.CreateDbContext(); - - commandExecutor.Configure(queryInfoCache["G2_Intersection_1"]); - var first = context.Suppliers - .Where(s => s.SupplierID < 10) - .Select(s => s.SupplierID) - .ToList(); - - commandExecutor.Configure(queryInfoCache["G2_Intersection_2"]); - var last = context.Suppliers - .Where(s => s.SupplierID >= 5 && s.SupplierID <= 15) - .Select(s => s.SupplierID) - .ToList(); - - var suppliers = first - .Intersect(last) - .OrderBy(s => s) - .ToList(); - - return suppliers; - } - - [Benchmark] - public string H1_Metadata() - { - commandExecutor.Configure(queryInfoCache["H1_Metadata"]); - using var context = contextFactory.CreateDbContext(); - - var datatype = context.Database.SqlQueryRaw( - """ - SELECT DATA_TYPE AS Value FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = 'Purchasing' - AND TABLE_NAME = 'Suppliers' - AND COLUMN_NAME = 'SupplierReference' - """ - ).Single(); - - return datatype; - } - } +using BenchmarkDotNet.Attributes; +using Common; +using Common.Mock; +using Common.Mock.EFCore; +using EFCoreEntities; +using EFCoreEntities.Models; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.VSDiagnostics; + +namespace EFCorePerformance +{ + [MemoryDiagnoser] + [ExceptionDiagnoser] + public class EFCoreBenchmarks + { + private PooledDbContextFactory contextFactory = null!; + private readonly BenchmarkCommandExecutor commandExecutor = BenchmarkCommandExecutor.Instance; + private Dictionary queryInfoCache = new(); + + [GlobalSetup] + public void GlobalSetup() + { + var options = new DbContextOptionsBuilder() + .UseSqlServer(DatabaseConfig.MSSQLConnectionString) + // We only test read queries, tracking is not needed and might slow down the tests + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .ConfigureWarnings(warnings => + { + warnings.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning); + // we only read the values, so we don't need to worry about precision when saving + warnings.Ignore(SqlServerEventId.DecimalTypeDefaultWarning); + }) + .AddInterceptors(RecordCommandsInterceptor.Instance) + .LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }) + .Options; + + contextFactory = new(options); + + ExtractQueryInfo(); + + var benchmarkOptions = new DbContextOptionsBuilder() + .UseSqlServer(DatabaseConfig.MSSQLConnectionString) + // We only test read queries, tracking is not needed and might slow down the tests + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .ConfigureWarnings(warnings => + { + warnings.Ignore(CoreEventId.SensitiveDataLoggingEnabledWarning); + // we only read the values, so we don't need to worry about precision when saving + warnings.Ignore(SqlServerEventId.DecimalTypeDefaultWarning); + }) + .AddInterceptors(new BenchmarkCommandInterceptor(commandExecutor)) + //.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuted }) + .Options; + + contextFactory = new(benchmarkOptions); + commandExecutor.UseUniqueColumnNames(true); + } + + private void ExtractQueryInfo() + { + using var context = contextFactory.CreateDbContext(); + + queryInfoCache.Add("A1_EntityIdenticalToTable", + context.RecordQueryInfos(ctx => ctx.PurchaseOrders.Find(25) + ).First()); + + queryInfoCache.Add("A2_LimitedEntity", + context.RecordQueryInfos(ctx => ctx.Suppliers + .Where(s => s.SupplierID == 10) + .Select(s => new SupplierContactInfo + { + SupplierID = s.SupplierID, + SupplierName = s.SupplierName, + PhoneNumber = s.PhoneNumber, + FaxNumber = s.FaxNumber, + WebsiteURL = s.WebsiteURL, + ValidFrom = s.ValidFrom, + ValidTo = s.ValidTo + }) + .SingleOrDefault() + ).First()); + + queryInfoCache.Add("A3_MultipleEntitiesFromOneResult", + context.RecordQueryInfos(ctx => ctx.Suppliers + .Where(s => s.SupplierID == 10) + .Select(s => new + { + ContactInfo = + new SupplierContactInfo + { + SupplierID = s.SupplierID, + SupplierName = s.SupplierName, + PhoneNumber = s.PhoneNumber, + FaxNumber = s.FaxNumber, + WebsiteURL = s.WebsiteURL, + ValidFrom = s.ValidFrom, + ValidTo = s.ValidTo + }, + BankAccount = new SupplierBankAccount + { + SupplierID = s.SupplierID, + BankAccountName = s.BankAccountName, + BankAccountBranch = s.BankAccountBranch, + BankAccountCode = s.BankAccountCode, + BankAccountNumber = s.BankAccountNumber, + BankInternationalCode = s.BankInternationalCode + } + }) + .Select(result => new { result.ContactInfo, result.BankAccount }) + .SingleOrDefault() + ).First()); + + var spFrom = new SqlParameter("@p0", new DateTime(2014, 1, 1)); + var spTo = new SqlParameter("@p1", new DateTime(2015, 1, 1)); + queryInfoCache.Add("A4_StoredProcedureToEntity", + context.RecordQueryInfos(ctx => ctx.Set() + .FromSqlInterpolated( + $"EXEC WideWorldImporters.Integration.GetOrderUpdates @LastCutoff = {spFrom}, @NewCutoff = {spTo}") + .ToList() + ).First()); + + int orderId = 26866; + queryInfoCache.Add("B1_SelectionOverIndexedColumn", + context.RecordQueryInfos(ctx => ctx.OrderLines + .Where(ol => ol.OrderID == orderId) + .ToList() + ).First()); + + decimal unitPrice = 25m; + queryInfoCache.Add("B2_SelectionOverNonIndexedColumn", + context.RecordQueryInfos(ctx => ctx.OrderLines + .Where(ol => ol.UnitPrice == unitPrice) + .ToList() + ).First()); + + var rangeFrom = new DateTime(2014, 12, 20); + var rangeTo = new DateTime(2014, 12, 31); + queryInfoCache.Add("B3_RangeQuery", + context.RecordQueryInfos(ctx => ctx.OrderLines + .Where(ol => ol.PickingCompletedWhen >= rangeFrom && ol.PickingCompletedWhen <= rangeTo) + .ToList() + ).First()); + + var orderIds = new[] { 1, 10, 100, 1000, 10000 }; + queryInfoCache.Add("B4_InQuery", + context.RecordQueryInfos(ctx => ctx.OrderLines + .Where(ol => orderIds.Contains(ol.OrderID)) + .ToList() + ).First()); + + string text = "C++"; + queryInfoCache.Add("B5_TextSearch", + context.RecordQueryInfos(ctx => ctx.OrderLines + .Where(ol => ol.Description.Contains(text)) + .ToList() + ).First()); + + int skip = 1000; + int take = 50; + queryInfoCache.Add("B6_PagingQuery", + context.RecordQueryInfos(ctx => ctx.OrderLines + .OrderBy(ol => ol.OrderLineID) + .Skip(skip) + .Take(take) + .ToList() + ).First()); + + queryInfoCache.Add("C1_AggregationCount", + context.RecordQueryInfos(ctx => ctx.OrderLines + .GroupBy(ol => ol.TaxRate) + .Select(g => new { TaxRate = g.Key, Count = g.Count() }) + .OrderByDescending(x => x.Count) + .ToDictionary(x => x.TaxRate, x => x.Count) + ).First()); + + queryInfoCache.Add("C2_AggregationMax", + context.RecordQueryInfos(ctx => ctx.OrderLines.Max(ol => ol.UnitPrice) + ).First()); + + queryInfoCache.Add("C3_AggregationSum", + context.RecordQueryInfos(ctx => ctx.OrderLines.Sum(ol => ol.Quantity * ol.UnitPrice) + ).First()); + + queryInfoCache.Add("D1_OneToManyRelationship", + context.RecordQueryInfos(ctx => ctx.Orders + .Include(o => o.OrderLines) + .SingleOrDefault(o => o.OrderID == 530) + ).First()); + + queryInfoCache.Add("D2_StockItems", + context.RecordQueryInfos(ctx => ctx.StockItems + .Include(si => si.StockGroups) + .OrderBy(si => si.StockItemID) + .ToList() + ).First()); + + queryInfoCache.Add("D2_StockGroups", + context.RecordQueryInfos(ctx => ctx.StockGroups + .Include(sg => sg.StockItems) + .OrderBy(sg => sg.StockGroupID) + .ToList() + ).First()); + + queryInfoCache.Add("D3_OptionalRelationship", + context.RecordQueryInfos(ctx => ctx.Customers + .Include(c => c.Transactions) + .OrderBy(c => c.CustomerID) + .ToList() + ).First()); + + queryInfoCache.Add("E1_ColumnSorting", + context.RecordQueryInfos(ctx => ctx.PurchaseOrders + .OrderBy(po => po.ExpectedDeliveryDate) + .Take(1000) + .ToList() + ).First()); + + queryInfoCache.Add("E2_Distinct", + context.RecordQueryInfos(ctx => ctx.PurchaseOrders + .Select(po => po.SupplierReference) + .Distinct() + .ToList() + ).First()); + + queryInfoCache.Add("F1_JSONObjectQuery", + context.RecordQueryInfos(ctx => ctx.People + .Where(p => p.CustomFields!.Title == "Team Member") + .OrderBy(p => p.PersonID) + .ToList() + ).First()); + + queryInfoCache.Add("F2_JSONArrayQuery", + context.RecordQueryInfos(ctx => ctx.People + .Where(p => p.OtherLanguages!.Contains("Slovak")) + .OrderBy(p => p.PersonID) + .ToList() + ).First()); + + var g1SqlStrings = context.RecordQueryInfos(ctx => + { + var first = context.Suppliers + .Where(s => s.SupplierID < 5) + .Select(s => s.SupplierID) + .ToList(); + + var last = context.Suppliers + .Where(s => s.SupplierID >= 5 && s.SupplierID <= 10) + .Select(s => s.SupplierID) + .ToList(); + + var suppliers = first + .Union(last) + .OrderBy(s => s) + .ToList(); + + return suppliers; + } + ); + queryInfoCache.Add("G1_Union_1", g1SqlStrings.ElementAt(0)); + queryInfoCache.Add("G1_Union_2", g1SqlStrings.ElementAt(1)); + + var g2SqlStrings = context.RecordQueryInfos(ctx => + { + var first = context.Suppliers + .Where(s => s.SupplierID < 10) + .Select(s => s.SupplierID) + .ToList(); + + var last = context.Suppliers + .Where(s => s.SupplierID >= 5 && s.SupplierID <= 15) + .Select(s => s.SupplierID) + .ToList(); + + var suppliers = first + .Intersect(last) + .OrderBy(s => s) + .ToList(); + + return suppliers; + } + ); + queryInfoCache.Add("G2_Intersection_1", g2SqlStrings.ElementAt(0)); + queryInfoCache.Add("G2_Intersection_2", g2SqlStrings.ElementAt(1)); + + queryInfoCache.Add("H1_Metadata", + context.RecordQueryInfos(ctx => ctx.Database.SqlQueryRaw( + """ + SELECT DATA_TYPE AS Value FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'Purchasing' + AND TABLE_NAME = 'Suppliers' + AND COLUMN_NAME = 'SupplierReference' + """ + ).SingleOrDefault() + ).First()); + } + + [GlobalCleanup] + public void GlobalCleanup() { } + + [Benchmark] + public PurchaseOrder? A1_EntityIdenticalToTable() + { + commandExecutor.Configure(queryInfoCache["A1_EntityIdenticalToTable"]); + using var context = contextFactory.CreateDbContext(); + + var order = context.PurchaseOrders.Find(25); + + return order; + } + + [Benchmark] + public SupplierContactInfo A2_LimitedEntity() + { + commandExecutor.Configure(queryInfoCache["A2_LimitedEntity"]); + using var context = contextFactory.CreateDbContext(); + + var contactInfo = context.Suppliers + .Where(s => s.SupplierID == 10) + .Select(s => new SupplierContactInfo + { + SupplierID = s.SupplierID, + SupplierName = s.SupplierName, + PhoneNumber = s.PhoneNumber, + FaxNumber = s.FaxNumber, + WebsiteURL = s.WebsiteURL, + ValidFrom = s.ValidFrom, + ValidTo = s.ValidTo + }) + .Single(); + + return contactInfo; + } + + [Benchmark] + public (SupplierContactInfo ContactInfo, SupplierBankAccount BankAccount) A3_MultipleEntitiesFromOneResult() + { + commandExecutor.Configure(queryInfoCache["A3_MultipleEntitiesFromOneResult"]); + using var context = contextFactory.CreateDbContext(); + + var result = context.Suppliers + .Where(s => s.SupplierID == 10) + .Select(s => new + { + ContactInfo = + new SupplierContactInfo + { + SupplierID = s.SupplierID, + SupplierName = s.SupplierName, + PhoneNumber = s.PhoneNumber, + FaxNumber = s.FaxNumber, + WebsiteURL = s.WebsiteURL, + ValidFrom = s.ValidFrom, + ValidTo = s.ValidTo + }, + BankAccount = new SupplierBankAccount + { + SupplierID = s.SupplierID, + BankAccountName = s.BankAccountName, + BankAccountBranch = s.BankAccountBranch, + BankAccountCode = s.BankAccountCode, + BankAccountNumber = s.BankAccountNumber, + BankInternationalCode = s.BankInternationalCode + } + }) + .Select(result => new { result.ContactInfo, result.BankAccount }) + .Single(); + + return (result.ContactInfo, result.BankAccount); + } + + [Benchmark] + public List A4_StoredProcedureToEntity() + { + commandExecutor.Configure(queryInfoCache["A4_StoredProcedureToEntity"]); + using var context = contextFactory.CreateDbContext(); + + var from = new DateTime(2014, 1, 1); + var to = new DateTime(2015, 1, 1); + + var orders = context.Set() + .FromSqlInterpolated( + $"EXEC WideWorldImporters.Integration.GetOrderUpdates @LastCutoff = {from}, @NewCutoff = {to}") + .ToList(); + + return orders; + } + + [Benchmark] + public List B1_SelectionOverIndexedColumn() + { + commandExecutor.Configure(queryInfoCache["B1_SelectionOverIndexedColumn"]); + using var context = contextFactory.CreateDbContext(); + + int orderId = 26866; + + var orderLines = context.OrderLines + .Where(ol => ol.OrderID == orderId) + .ToList(); + + return orderLines; + } + + [Benchmark] + public List B2_SelectionOverNonIndexedColumn() + { + commandExecutor.Configure(queryInfoCache["B2_SelectionOverNonIndexedColumn"]); + using var context = contextFactory.CreateDbContext(); + + decimal unitPrice = 25m; + + var orderLines = context.OrderLines + .Where(ol => ol.UnitPrice == unitPrice) + .ToList(); + + return orderLines; + } + + [Benchmark] + public List B3_RangeQuery() + { + commandExecutor.Configure(queryInfoCache["B3_RangeQuery"]); + using var context = contextFactory.CreateDbContext(); + + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + + var orderLines = context.OrderLines + .Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to) + .ToList(); + + return orderLines; + } + + [Benchmark] + public List B4_InQuery() + { + commandExecutor.Configure(queryInfoCache["B4_InQuery"]); + using var context = contextFactory.CreateDbContext(); + + var orderIds = new[] { 1, 10, 100, 1000, 10000 }; + + var orderLines = context.OrderLines + .Where(ol => orderIds.Contains(ol.OrderID)) + .ToList(); + + return orderLines; + } + + [Benchmark] + public List B5_TextSearch() + { + commandExecutor.Configure(queryInfoCache["B5_TextSearch"]); + using var context = contextFactory.CreateDbContext(); + + string text = "C++"; + + var orderLines = context.OrderLines + //.Where(ol => EF.Functions.Like(ol.Description, $"%{text}%")) + .Where(ol => ol.Description.Contains(text)) + .ToList(); + + return orderLines; + } + + [Benchmark] + public List B6_PagingQuery() + { + commandExecutor.Configure(queryInfoCache["B6_PagingQuery"]); + using var context = contextFactory.CreateDbContext(); + + int skip = 1000; + int take = 50; + + var orderLines = context.OrderLines + .OrderBy(ol => ol.OrderLineID) + .Skip(skip) + .Take(take) + .ToList(); + + return orderLines; + } + + [Benchmark] + public Dictionary C1_AggregationCount() + { + commandExecutor.Configure(queryInfoCache["C1_AggregationCount"]); + using var context = contextFactory.CreateDbContext(); + + var taxRates = context.OrderLines + .GroupBy(ol => ol.TaxRate) + .Select(g => new { TaxRate = g.Key, Count = g.Count() }) + .OrderByDescending(x => x.Count) + .ToDictionary(x => x.TaxRate, x => x.Count); + + return taxRates; + } + + [Benchmark] + public decimal? C2_AggregationMax() + { + commandExecutor.Configure(queryInfoCache["C2_AggregationMax"]); + using var context = contextFactory.CreateDbContext(); + + var maxUnitPrice = context.OrderLines.Max(ol => ol.UnitPrice); + + return maxUnitPrice; + } + + [Benchmark] + public decimal? C3_AggregationSum() + { + commandExecutor.Configure(queryInfoCache["C3_AggregationSum"]); + using var context = contextFactory.CreateDbContext(); + + var totalSales = context.OrderLines + .Sum(ol => ol.Quantity * ol.UnitPrice); + + return totalSales; + } + + [Benchmark] + public Order D1_OneToManyRelationship() + { + commandExecutor.Configure(queryInfoCache["D1_OneToManyRelationship"]); + using var context = contextFactory.CreateDbContext(); + + var order = context.Orders + .Include(o => o.OrderLines) + .Single(o => o.OrderID == 530); + + return order; + } + + [Benchmark] + public (List stockItems, List stockGroups) D2_ManyToManyRelationship() + { + using var context = contextFactory.CreateDbContext(); + + commandExecutor.Configure(queryInfoCache["D2_StockItems"]); + var stockItems = context.StockItems + .Include(si => si.StockGroups) + .OrderBy(si => si.StockItemID) + .ToList(); + + commandExecutor.Configure(queryInfoCache["D2_StockGroups"]); + var stockGroups = context.StockGroups + .Include(sg => sg.StockItems) + .OrderBy(si => si.StockGroupID) + .ToList(); + + return (stockItems, stockGroups); + } + + [Benchmark] + public List D3_OptionalRelationship() + { + commandExecutor.Configure(queryInfoCache["D3_OptionalRelationship"]); + using var context = contextFactory.CreateDbContext(); + + var result = context.Customers + .Include(c => c.Transactions) + .OrderBy(c => c.CustomerID) + .ToList(); + + return result; + } + + [Benchmark] + public List E1_ColumnSorting() + { + commandExecutor.Configure(queryInfoCache["E1_ColumnSorting"]); + using var context = contextFactory.CreateDbContext(); + + var orders = context.PurchaseOrders + .OrderBy(po => po.ExpectedDeliveryDate) + .Take(1000) + .ToList(); + + return orders; + } + + [Benchmark] + public List E2_Distinct() + { + commandExecutor.Configure(queryInfoCache["E2_Distinct"]); + using var context = contextFactory.CreateDbContext(); + + var supplierReferences = context.PurchaseOrders + .Select(po => po.SupplierReference) + .Distinct() + .ToList(); + + return supplierReferences; + } + + [Benchmark] + public List F1_JSONObjectQuery() + { + commandExecutor.Configure(queryInfoCache["F1_JSONObjectQuery"]); + using var context = contextFactory.CreateDbContext(); + + var people = context.People + .Where(p => p.CustomFields!.Title == "Team Member") + .OrderBy(p => p.PersonID) + .ToList(); + + return people; + } + + [Benchmark] + public List F2_JSONArrayQuery() + { + commandExecutor.Configure(queryInfoCache["F2_JSONArrayQuery"]); + using var context = contextFactory.CreateDbContext(); + + var people = context.People + .Where(p => p.OtherLanguages!.Contains("Slovak")) + .OrderBy(p => p.PersonID) + .ToList(); + + return people; + } + + [Benchmark] + public List G1_Union() + { + using var context = contextFactory.CreateDbContext(); + + commandExecutor.Configure(queryInfoCache["G1_Union_1"]); + var first = context.Suppliers + .Where(s => s.SupplierID < 5) + .Select(s => s.SupplierID) + .ToList(); + + commandExecutor.Configure(queryInfoCache["G1_Union_2"]); + var last = context.Suppliers + .Where(s => s.SupplierID >= 5 && s.SupplierID <= 10) + .Select(s => s.SupplierID) + .ToList(); + + var suppliers = first + .Union(last) + .OrderBy(s => s) + .ToList(); + + return suppliers; + } + + [Benchmark] + public List G2_Intersection() + { + using var context = contextFactory.CreateDbContext(); + + commandExecutor.Configure(queryInfoCache["G2_Intersection_1"]); + var first = context.Suppliers + .Where(s => s.SupplierID < 10) + .Select(s => s.SupplierID) + .ToList(); + + commandExecutor.Configure(queryInfoCache["G2_Intersection_2"]); + var last = context.Suppliers + .Where(s => s.SupplierID >= 5 && s.SupplierID <= 15) + .Select(s => s.SupplierID) + .ToList(); + + var suppliers = first + .Intersect(last) + .OrderBy(s => s) + .ToList(); + + return suppliers; + } + + [Benchmark] + public string H1_Metadata() + { + commandExecutor.Configure(queryInfoCache["H1_Metadata"]); + using var context = contextFactory.CreateDbContext(); + + var datatype = context.Database.SqlQueryRaw( + """ + SELECT DATA_TYPE AS Value FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'Purchasing' + AND TABLE_NAME = 'Suppliers' + AND COLUMN_NAME = 'SupplierReference' + """ + ).Single(); + + return datatype; + } + } } \ No newline at end of file diff --git a/benchmarks/NHibernatePerformance/NHibernateBenchmarks.cs b/benchmarks/NHibernatePerformance/NHibernateBenchmarks.cs index 29c539a..1a16d7a 100644 --- a/benchmarks/NHibernatePerformance/NHibernateBenchmarks.cs +++ b/benchmarks/NHibernatePerformance/NHibernateBenchmarks.cs @@ -1,8 +1,8 @@ -using System.Data.Common; +using System.Data.Common; using BenchmarkDotNet.Attributes; using Common; using Common.Mock; -using Common.Mock.NHibernate; +using Common.Mock.NHibernate; using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore.Internal; using NHibernate; @@ -21,8 +21,8 @@ namespace NHibernatePerformance public class NHibernateBenchmarks { private ISessionFactory sessionFactory = null!; - private readonly BenchmarkCommandExecutor commandExecutor = BenchmarkCommandExecutor.Instance; - private Dictionary queryInfoCache = new(); + private readonly BenchmarkCommandExecutor commandExecutor = BenchmarkCommandExecutor.Instance; + private Dictionary queryInfoCache = new(); [GlobalSetup] public void GlobalSetup() @@ -38,231 +38,231 @@ public void GlobalSetup() .AddAssembly(typeof(PurchaseOrder).Assembly); sessionFactory = configuration.BuildSessionFactory(); - ExtractQueryInfo(); + ExtractQueryInfo(); + } + + private void ExtractQueryInfo() + { + using var session = sessionFactory.OpenSession(); + using var sqlConn = new SqlConnection(DatabaseConfig.MSSQLConnectionString); + sqlConn.Open(); + + queryInfoCache.Add("A1_EntityIdenticalToTable", + session.RecordQueryInfos(s => s.Get(25)).First()); + + queryInfoCache.Add("A2_LimitedEntity", + session.RecordQueryInfos(s => s.Query() + .Where(sup => sup.SupplierID == 10) + .Select(sup => new SupplierContactInfo + { + SupplierID = sup.SupplierID, + SupplierName = sup.SupplierName, + PhoneNumber = sup.PhoneNumber, + FaxNumber = sup.FaxNumber, + WebsiteURL = sup.WebsiteURL, + ValidFrom = sup.ValidFrom, + ValidTo = sup.ValidTo + }) + .SingleOrDefault()).First()); + + queryInfoCache.Add("A3_MultipleEntitiesFromOneResult", + session.RecordQueryInfos(s => s.Query() + .Where(sup => sup.SupplierID == 10) + .Select(sup => new + { + ContactInfo = new SupplierContactInfo + { + SupplierID = sup.SupplierID, + SupplierName = sup.SupplierName, + PhoneNumber = sup.PhoneNumber, + FaxNumber = sup.FaxNumber, + WebsiteURL = sup.WebsiteURL, + ValidFrom = sup.ValidFrom, + ValidTo = sup.ValidTo + }, + BankAccount = new SupplierBankAccount + { + SupplierID = sup.SupplierID, + BankAccountName = sup.BankAccountName, + BankAccountBranch = sup.BankAccountBranch, + BankAccountCode = sup.BankAccountCode, + BankAccountNumber = sup.BankAccountNumber, + BankInternationalCode = sup.BankInternationalCode + } + }) + .Select(result => new { result.ContactInfo, result.BankAccount }) + .SingleOrDefault()).First()); + + var from = new DateTime(2014, 1, 1); + var to = new DateTime(2015, 1, 1); + queryInfoCache.Add("A4_StoredProcedureToEntity", + session.RecordQueryInfos(s => s.GetNamedQuery("GetOrderUpdates") + .SetDateTime("from", from) + .SetDateTime("to", to) + .SetResultTransformer(Transformers.AliasToEntityMap) + .List()).First()); + + int orderId = 26866; + queryInfoCache.Add("B1_SelectionOverIndexedColumn", + session.RecordQueryInfos(s => s.Query() + .Where(ol => ol.OrderID == orderId) + .ToList()).First()); + + decimal unitPrice = 25m; + queryInfoCache.Add("B2_SelectionOverNonIndexedColumn", + session.RecordQueryInfos(s => s.Query() + .Where(ol => ol.UnitPrice == unitPrice) + .ToList()).First()); + + var rangeFrom = new DateTime(2014, 12, 20); + var rangeTo = new DateTime(2014, 12, 31); + queryInfoCache.Add("B3_RangeQuery", + session.RecordQueryInfos(s => s.Query() + .Where(ol => ol.PickingCompletedWhen >= rangeFrom && ol.PickingCompletedWhen <= rangeTo) + .ToList()).First()); + + var orderIds = new[] { 1, 10, 100, 1000, 10000 }; + queryInfoCache.Add("B4_InQuery", + session.RecordQueryInfos(s => s.Query() + .Where(ol => orderIds.Contains(ol.OrderID)) + .ToList()).First()); + + string text = "C++"; + queryInfoCache.Add("B5_TextSearch", + session.RecordQueryInfos(s => s.Query() + .Where(ol => ol.Description.Contains(text)) + .ToList()).First()); + + int skip = 1000; + int take = 50; + queryInfoCache.Add("B6_PagingQuery", + session.RecordQueryInfos(s => s.Query() + .OrderBy(ol => ol.OrderLineID) + .Skip(skip) + .Take(take) + .ToList()).First()); + + queryInfoCache.Add("C1_AggregationCount", + session.RecordQueryInfos(s => s.Query() + .GroupBy(ol => ol.TaxRate) + .Select(g => new { TaxRate = g.Key, Count = g.Count() }) + .OrderByDescending(x => x.Count) + .ToList()).First()); + + queryInfoCache.Add("C2_AggregationMax", + session.RecordQueryInfos(s => s.CreateQuery("SELECT MAX(ol.UnitPrice) FROM OrderLine ol") + .UniqueResult()).First()); + + queryInfoCache.Add("C3_AggregationSum", + session.RecordQueryInfos(s => s + .CreateQuery("SELECT SUM(ol.Quantity * ol.UnitPrice) FROM OrderLine ol") + .UniqueResult()).First()); + + queryInfoCache.Add("D1_OneToManyRelationship", + session.RecordQueryInfos(s => s.Query() + .Fetch(o => o.OrderLines) + .SingleOrDefault(o => o.OrderID == 530)).First()); + + queryInfoCache.Add("D2_StockItems", + session.RecordQueryInfos(s => s.Query() + .Fetch(si => si.StockGroups) + .OrderBy(si => si.StockItemID) + .ToList()).First()); + + queryInfoCache.Add("D2_StockGroups", + session.RecordQueryInfos(s => s.Query() + .Fetch(sg => sg.StockItems) + .OrderBy(sg => sg.StockGroupID) + .ToList()).First()); + + queryInfoCache.Add("D3_OptionalRelationship", + session.RecordQueryInfos(s => s.Query() + .Fetch(c => c.Transactions) + .OrderBy(c => c.CustomerID) + .ToList()).First()); + + queryInfoCache.Add("E1_ColumnSorting", + session.RecordQueryInfos(s => s.Query() + .OrderBy(po => po.ExpectedDeliveryDate) + .Take(1000) + .ToList()).First()); + + queryInfoCache.Add("E2_Distinct", + session.RecordQueryInfos(s => s.Query() + .Select(po => po.SupplierReference) + .Distinct() + .ToList()).First()); + + var f1Sql = """ + SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages + FROM WideWorldImporters.Application.People + WHERE JSON_VALUE(CustomFields, '$.Title') = :title + """; + queryInfoCache.Add("F1_JSONObjectQuery", + session.RecordQueryInfos(s => s.CreateSQLQuery(f1Sql) + .SetParameter("title", "Team Member") + .SetResultTransformer(Transformers.AliasToBean()) + .List()).First()); + + var f2Sql = """ + SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages + FROM WideWorldImporters.Application.People + WHERE EXISTS ( + SELECT 1 FROM OPENJSON(OtherLanguages) + WHERE value = :lang + ) + """; + queryInfoCache.Add("F2_JSONArrayQuery", + session.RecordQueryInfos(s => s.CreateSQLQuery(f2Sql) + .SetParameter("lang", "Slovak") + .SetResultTransformer(Transformers.AliasToBean()) + .List()).First()); + + var g1SqlStrings = session.RecordQueryInfos(s => + { + var first = s.Query() + .Where(sup => sup.SupplierID < 5) + .Select(sup => sup.SupplierID) + .ToList(); + + var last = s.Query() + .Where(sup => sup.SupplierID >= 5 && sup.SupplierID <= 10) + .Select(sup => sup.SupplierID) + .ToList(); + + return first.Union(last).OrderBy(sup => sup).ToList(); + }); + queryInfoCache.Add("G1_Union_1", g1SqlStrings.ElementAt(0)); + queryInfoCache.Add("G1_Union_2", g1SqlStrings.ElementAt(1)); + + var g2SqlStrings = session.RecordQueryInfos(s => + { + var first = s.Query() + .Where(sup => sup.SupplierID < 10) + .Select(sup => sup.SupplierID) + .ToList(); + + var last = s.Query() + .Where(sup => sup.SupplierID >= 5 && sup.SupplierID <= 15) + .Select(sup => sup.SupplierID) + .ToList(); + + return first.Intersect(last).OrderBy(sup => sup).ToList(); + }); + queryInfoCache.Add("G2_Intersection_1", g2SqlStrings.ElementAt(0)); + queryInfoCache.Add("G2_Intersection_2", g2SqlStrings.ElementAt(1)); + + queryInfoCache.Add("H1_Metadata", + session.RecordQueryInfos(s => s.CreateSQLQuery( + """ + SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'Purchasing' + AND TABLE_NAME = 'Suppliers' + AND COLUMN_NAME = 'SupplierReference' + """ + ).UniqueResult()).First()); } - private void ExtractQueryInfo() - { - using var session = sessionFactory.OpenSession(); - using var sqlConn = new SqlConnection(DatabaseConfig.MSSQLConnectionString); - sqlConn.Open(); - - queryInfoCache.Add("A1_EntityIdenticalToTable", - session.RecordQueryInfos(s => s.Get(25)).First()); - - queryInfoCache.Add("A2_LimitedEntity", - session.RecordQueryInfos(s => s.Query() - .Where(sup => sup.SupplierID == 10) - .Select(sup => new SupplierContactInfo - { - SupplierID = sup.SupplierID, - SupplierName = sup.SupplierName, - PhoneNumber = sup.PhoneNumber, - FaxNumber = sup.FaxNumber, - WebsiteURL = sup.WebsiteURL, - ValidFrom = sup.ValidFrom, - ValidTo = sup.ValidTo - }) - .SingleOrDefault()).First()); - - queryInfoCache.Add("A3_MultipleEntitiesFromOneResult", - session.RecordQueryInfos(s => s.Query() - .Where(sup => sup.SupplierID == 10) - .Select(sup => new - { - ContactInfo = new SupplierContactInfo - { - SupplierID = sup.SupplierID, - SupplierName = sup.SupplierName, - PhoneNumber = sup.PhoneNumber, - FaxNumber = sup.FaxNumber, - WebsiteURL = sup.WebsiteURL, - ValidFrom = sup.ValidFrom, - ValidTo = sup.ValidTo - }, - BankAccount = new SupplierBankAccount - { - SupplierID = sup.SupplierID, - BankAccountName = sup.BankAccountName, - BankAccountBranch = sup.BankAccountBranch, - BankAccountCode = sup.BankAccountCode, - BankAccountNumber = sup.BankAccountNumber, - BankInternationalCode = sup.BankInternationalCode - } - }) - .Select(result => new { result.ContactInfo, result.BankAccount }) - .SingleOrDefault()).First()); - - var from = new DateTime(2014, 1, 1); - var to = new DateTime(2015, 1, 1); - queryInfoCache.Add("A4_StoredProcedureToEntity", - session.RecordQueryInfos(s => s.GetNamedQuery("GetOrderUpdates") - .SetDateTime("from", from) - .SetDateTime("to", to) - .SetResultTransformer(Transformers.AliasToEntityMap) - .List()).First()); - - int orderId = 26866; - queryInfoCache.Add("B1_SelectionOverIndexedColumn", - session.RecordQueryInfos(s => s.Query() - .Where(ol => ol.OrderID == orderId) - .ToList()).First()); - - decimal unitPrice = 25m; - queryInfoCache.Add("B2_SelectionOverNonIndexedColumn", - session.RecordQueryInfos(s => s.Query() - .Where(ol => ol.UnitPrice == unitPrice) - .ToList()).First()); - - var rangeFrom = new DateTime(2014, 12, 20); - var rangeTo = new DateTime(2014, 12, 31); - queryInfoCache.Add("B3_RangeQuery", - session.RecordQueryInfos(s => s.Query() - .Where(ol => ol.PickingCompletedWhen >= rangeFrom && ol.PickingCompletedWhen <= rangeTo) - .ToList()).First()); - - var orderIds = new[] { 1, 10, 100, 1000, 10000 }; - queryInfoCache.Add("B4_InQuery", - session.RecordQueryInfos(s => s.Query() - .Where(ol => orderIds.Contains(ol.OrderID)) - .ToList()).First()); - - string text = "C++"; - queryInfoCache.Add("B5_TextSearch", - session.RecordQueryInfos(s => s.Query() - .Where(ol => ol.Description.Contains(text)) - .ToList()).First()); - - int skip = 1000; - int take = 50; - queryInfoCache.Add("B6_PagingQuery", - session.RecordQueryInfos(s => s.Query() - .OrderBy(ol => ol.OrderLineID) - .Skip(skip) - .Take(take) - .ToList()).First()); - - queryInfoCache.Add("C1_AggregationCount", - session.RecordQueryInfos(s => s.Query() - .GroupBy(ol => ol.TaxRate) - .Select(g => new { TaxRate = g.Key, Count = g.Count() }) - .OrderByDescending(x => x.Count) - .ToList()).First()); - - queryInfoCache.Add("C2_AggregationMax", - session.RecordQueryInfos(s => s.CreateQuery("SELECT MAX(ol.UnitPrice) FROM OrderLine ol") - .UniqueResult()).First()); - - queryInfoCache.Add("C3_AggregationSum", - session.RecordQueryInfos(s => s - .CreateQuery("SELECT SUM(ol.Quantity * ol.UnitPrice) FROM OrderLine ol") - .UniqueResult()).First()); - - queryInfoCache.Add("D1_OneToManyRelationship", - session.RecordQueryInfos(s => s.Query() - .Fetch(o => o.OrderLines) - .SingleOrDefault(o => o.OrderID == 530)).First()); - - queryInfoCache.Add("D2_StockItems", - session.RecordQueryInfos(s => s.Query() - .Fetch(si => si.StockGroups) - .OrderBy(si => si.StockItemID) - .ToList()).First()); - - queryInfoCache.Add("D2_StockGroups", - session.RecordQueryInfos(s => s.Query() - .Fetch(sg => sg.StockItems) - .OrderBy(sg => sg.StockGroupID) - .ToList()).First()); - - queryInfoCache.Add("D3_OptionalRelationship", - session.RecordQueryInfos(s => s.Query() - .Fetch(c => c.Transactions) - .OrderBy(c => c.CustomerID) - .ToList()).First()); - - queryInfoCache.Add("E1_ColumnSorting", - session.RecordQueryInfos(s => s.Query() - .OrderBy(po => po.ExpectedDeliveryDate) - .Take(1000) - .ToList()).First()); - - queryInfoCache.Add("E2_Distinct", - session.RecordQueryInfos(s => s.Query() - .Select(po => po.SupplierReference) - .Distinct() - .ToList()).First()); - - var f1Sql = """ - SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages - FROM WideWorldImporters.Application.People - WHERE JSON_VALUE(CustomFields, '$.Title') = :title - """; - queryInfoCache.Add("F1_JSONObjectQuery", - session.RecordQueryInfos(s => s.CreateSQLQuery(f1Sql) - .SetParameter("title", "Team Member") - .SetResultTransformer(Transformers.AliasToBean()) - .List()).First()); - - var f2Sql = """ - SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages - FROM WideWorldImporters.Application.People - WHERE EXISTS ( - SELECT 1 FROM OPENJSON(OtherLanguages) - WHERE value = :lang - ) - """; - queryInfoCache.Add("F2_JSONArrayQuery", - session.RecordQueryInfos(s => s.CreateSQLQuery(f2Sql) - .SetParameter("lang", "Slovak") - .SetResultTransformer(Transformers.AliasToBean()) - .List()).First()); - - var g1SqlStrings = session.RecordQueryInfos(s => - { - var first = s.Query() - .Where(sup => sup.SupplierID < 5) - .Select(sup => sup.SupplierID) - .ToList(); - - var last = s.Query() - .Where(sup => sup.SupplierID >= 5 && sup.SupplierID <= 10) - .Select(sup => sup.SupplierID) - .ToList(); - - return first.Union(last).OrderBy(sup => sup).ToList(); - }); - queryInfoCache.Add("G1_Union_1", g1SqlStrings.ElementAt(0)); - queryInfoCache.Add("G1_Union_2", g1SqlStrings.ElementAt(1)); - - var g2SqlStrings = session.RecordQueryInfos(s => - { - var first = s.Query() - .Where(sup => sup.SupplierID < 10) - .Select(sup => sup.SupplierID) - .ToList(); - - var last = s.Query() - .Where(sup => sup.SupplierID >= 5 && sup.SupplierID <= 15) - .Select(sup => sup.SupplierID) - .ToList(); - - return first.Intersect(last).OrderBy(sup => sup).ToList(); - }); - queryInfoCache.Add("G2_Intersection_1", g2SqlStrings.ElementAt(0)); - queryInfoCache.Add("G2_Intersection_2", g2SqlStrings.ElementAt(1)); - - queryInfoCache.Add("H1_Metadata", - session.RecordQueryInfos(s => s.CreateSQLQuery( - """ - SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = 'Purchasing' - AND TABLE_NAME = 'Suppliers' - AND COLUMN_NAME = 'SupplierReference' - """ - ).UniqueResult()).First()); - } - [GlobalCleanup] public void GlobalCleanup() { @@ -272,7 +272,7 @@ public void GlobalCleanup() [Benchmark] public PurchaseOrder A1_EntityIdenticalToTable() { - commandExecutor.Configure(queryInfoCache["A1_EntityIdenticalToTable"]); + commandExecutor.Configure(queryInfoCache["A1_EntityIdenticalToTable"]); using var session = sessionFactory.OpenSession(); var order = session.Get(25); @@ -283,22 +283,22 @@ public PurchaseOrder A1_EntityIdenticalToTable() [Benchmark] public SupplierContactInfo A2_LimitedEntity() { - commandExecutor.Configure(queryInfoCache["A2_LimitedEntity"]); + commandExecutor.Configure(queryInfoCache["A2_LimitedEntity"]); using var session = sessionFactory.OpenSession(); var contactInfo = session.Query() - .Where(s => s.SupplierID == 10) - .Select(s => new SupplierContactInfo - { - SupplierID = s.SupplierID, - SupplierName = s.SupplierName, - PhoneNumber = s.PhoneNumber, - FaxNumber = s.FaxNumber, - WebsiteURL = s.WebsiteURL, - ValidFrom = s.ValidFrom, - ValidTo = s.ValidTo - }) - .Single(); + .Where(s => s.SupplierID == 10) + .Select(s => new SupplierContactInfo + { + SupplierID = s.SupplierID, + SupplierName = s.SupplierName, + PhoneNumber = s.PhoneNumber, + FaxNumber = s.FaxNumber, + WebsiteURL = s.WebsiteURL, + ValidFrom = s.ValidFrom, + ValidTo = s.ValidTo + }) + .Single(); return contactInfo; } @@ -306,36 +306,36 @@ public SupplierContactInfo A2_LimitedEntity() [Benchmark] public (SupplierContactInfo ContactInfo, SupplierBankAccount BankAccount) A3_MultipleEntitiesFromOneResult() { - commandExecutor.Configure(queryInfoCache["A3_MultipleEntitiesFromOneResult"]); + commandExecutor.Configure(queryInfoCache["A3_MultipleEntitiesFromOneResult"]); using var session = sessionFactory.OpenSession(); var result = session.Query() - .Where(s => s.SupplierID == 10) - .Select(s => new + .Where(s => s.SupplierID == 10) + .Select(s => new { - ContactInfo = - new SupplierContactInfo - { - SupplierID = s.SupplierID, - SupplierName = s.SupplierName, - PhoneNumber = s.PhoneNumber, - FaxNumber = s.FaxNumber, - WebsiteURL = s.WebsiteURL, - ValidFrom = s.ValidFrom, - ValidTo = s.ValidTo - }, - BankAccount = new SupplierBankAccount - { - SupplierID = s.SupplierID, - BankAccountName = s.BankAccountName, - BankAccountBranch = s.BankAccountBranch, - BankAccountCode = s.BankAccountCode, - BankAccountNumber = s.BankAccountNumber, - BankInternationalCode = s.BankInternationalCode - } - }) - .Select(result => new { result.ContactInfo, result.BankAccount }) - .Single(); + ContactInfo = + new SupplierContactInfo + { + SupplierID = s.SupplierID, + SupplierName = s.SupplierName, + PhoneNumber = s.PhoneNumber, + FaxNumber = s.FaxNumber, + WebsiteURL = s.WebsiteURL, + ValidFrom = s.ValidFrom, + ValidTo = s.ValidTo + }, + BankAccount = new SupplierBankAccount + { + SupplierID = s.SupplierID, + BankAccountName = s.BankAccountName, + BankAccountBranch = s.BankAccountBranch, + BankAccountCode = s.BankAccountCode, + BankAccountNumber = s.BankAccountNumber, + BankInternationalCode = s.BankInternationalCode + } + }) + .Select(result => new { result.ContactInfo, result.BankAccount }) + .Single(); return (result.ContactInfo, result.BankAccount); } @@ -343,7 +343,7 @@ public SupplierContactInfo A2_LimitedEntity() [Benchmark] public IList A4_StoredProcedureToEntity() { - commandExecutor.Configure(queryInfoCache["A4_StoredProcedureToEntity"]); + commandExecutor.Configure(queryInfoCache["A4_StoredProcedureToEntity"]); using var session = sessionFactory.OpenSession(); var from = new DateTime(2014, 1, 1); @@ -354,7 +354,7 @@ public SupplierContactInfo A2_LimitedEntity() var orders = session.GetNamedQuery("GetOrderUpdates") .SetDateTime("from", from) .SetDateTime("to", to) - .SetResultTransformer(Transformers.AliasToEntityMap) + .SetResultTransformer(Transformers.AliasToEntityMap) .List(); return orders; @@ -363,7 +363,7 @@ public SupplierContactInfo A2_LimitedEntity() [Benchmark] public List B1_SelectionOverIndexedColumn() { - commandExecutor.Configure(queryInfoCache["B1_SelectionOverIndexedColumn"]); + commandExecutor.Configure(queryInfoCache["B1_SelectionOverIndexedColumn"]); using var session = sessionFactory.OpenSession(); int orderId = 26866; @@ -378,7 +378,7 @@ public List B1_SelectionOverIndexedColumn() [Benchmark] public List B2_SelectionOverNonIndexedColumn() { - commandExecutor.Configure(queryInfoCache["B2_SelectionOverNonIndexedColumn"]); + commandExecutor.Configure(queryInfoCache["B2_SelectionOverNonIndexedColumn"]); using var session = sessionFactory.OpenSession(); decimal unitPrice = 25m; @@ -393,7 +393,7 @@ public List B2_SelectionOverNonIndexedColumn() [Benchmark] public List B3_RangeQuery() { - commandExecutor.Configure(queryInfoCache["B3_RangeQuery"]); + commandExecutor.Configure(queryInfoCache["B3_RangeQuery"]); using var session = sessionFactory.OpenSession(); var from = new DateTime(2014, 12, 20); @@ -409,7 +409,7 @@ public List B3_RangeQuery() [Benchmark] public List B4_InQuery() { - commandExecutor.Configure(queryInfoCache["B4_InQuery"]); + commandExecutor.Configure(queryInfoCache["B4_InQuery"]); using var session = sessionFactory.OpenSession(); var orderIds = new[] { 1, 10, 100, 1000, 10000 }; @@ -424,7 +424,7 @@ public List B4_InQuery() [Benchmark] public List B5_TextSearch() { - commandExecutor.Configure(queryInfoCache["B5_TextSearch"]); + commandExecutor.Configure(queryInfoCache["B5_TextSearch"]); using var session = sessionFactory.OpenSession(); string text = "C++"; @@ -439,7 +439,7 @@ public List B5_TextSearch() [Benchmark] public List B6_PagingQuery() { - commandExecutor.Configure(queryInfoCache["B6_PagingQuery"]); + commandExecutor.Configure(queryInfoCache["B6_PagingQuery"]); using var session = sessionFactory.OpenSession(); int skip = 1000; @@ -457,7 +457,7 @@ public List B6_PagingQuery() [Benchmark] public Dictionary C1_AggregationCount() { - commandExecutor.Configure(queryInfoCache["C1_AggregationCount"]); + commandExecutor.Configure(queryInfoCache["C1_AggregationCount"]); using var session = sessionFactory.OpenSession(); var taxRates = session.Query() @@ -472,7 +472,7 @@ public Dictionary C1_AggregationCount() [Benchmark] public decimal? C2_AggregationMax() { - commandExecutor.Configure(queryInfoCache["C2_AggregationMax"]); + commandExecutor.Configure(queryInfoCache["C2_AggregationMax"]); using var session = sessionFactory.OpenSession(); var maxUnitPrice = session.Query() @@ -484,7 +484,7 @@ public Dictionary C1_AggregationCount() [Benchmark] public decimal? C3_AggregationSum() { - commandExecutor.Configure(queryInfoCache["C3_AggregationSum"]); + commandExecutor.Configure(queryInfoCache["C3_AggregationSum"]); using var session = sessionFactory.OpenSession(); var totalSales = session.Query() @@ -496,7 +496,7 @@ public Dictionary C1_AggregationCount() [Benchmark] public Order D1_OneToManyRelationship() { - commandExecutor.Configure(queryInfoCache["D1_OneToManyRelationship"]); + commandExecutor.Configure(queryInfoCache["D1_OneToManyRelationship"]); using var session = sessionFactory.OpenSession(); var order = session.Query() @@ -511,13 +511,13 @@ public Order D1_OneToManyRelationship() { using var session = sessionFactory.OpenSession(); - commandExecutor.Configure(queryInfoCache["D2_StockItems"]); + commandExecutor.Configure(queryInfoCache["D2_StockItems"]); var stockItems = session.Query() .Fetch(si => si.StockGroups) .OrderBy(si => si.StockItemID) .ToList(); - commandExecutor.Configure(queryInfoCache["D2_StockGroups"]); + commandExecutor.Configure(queryInfoCache["D2_StockGroups"]); var stockGroups = session.Query() .Fetch(sg => sg.StockItems) .OrderBy(sg => sg.StockGroupID) @@ -529,13 +529,13 @@ public Order D1_OneToManyRelationship() [Benchmark] public List D3_OptionalRelationship() { - commandExecutor.Configure(queryInfoCache["D3_OptionalRelationship"]); + commandExecutor.Configure(queryInfoCache["D3_OptionalRelationship"]); using var session = sessionFactory.OpenSession(); var result = session.Query() - .Fetch(c => c.Transactions) - .OrderBy(c => c.CustomerID) - .ToList(); + .Fetch(c => c.Transactions) + .OrderBy(c => c.CustomerID) + .ToList(); return result; } @@ -543,7 +543,7 @@ public List D3_OptionalRelationship() [Benchmark] public List E1_ColumnSorting() { - commandExecutor.Configure(queryInfoCache["E1_ColumnSorting"]); + commandExecutor.Configure(queryInfoCache["E1_ColumnSorting"]); using var session = sessionFactory.OpenSession(); var orders = session.Query() @@ -557,7 +557,7 @@ public List E1_ColumnSorting() [Benchmark] public List E2_Distinct() { - commandExecutor.Configure(queryInfoCache["E2_Distinct"]); + commandExecutor.Configure(queryInfoCache["E2_Distinct"]); using var session = sessionFactory.OpenSession(); var supplierReferences = session.Query() @@ -571,14 +571,14 @@ public List E1_ColumnSorting() [Benchmark] public IList F1_JSONObjectQuery() { - commandExecutor.Configure(queryInfoCache["F1_JSONObjectQuery"]); + commandExecutor.Configure(queryInfoCache["F1_JSONObjectQuery"]); using var session = sessionFactory.OpenSession(); var sql = """ - SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages - FROM WideWorldImporters.Application.People - WHERE JSON_VALUE(CustomFields, '$.Title') = :title - """; + SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages + FROM WideWorldImporters.Application.People + WHERE JSON_VALUE(CustomFields, '$.Title') = :title + """; var people = session.CreateSQLQuery(sql) .SetParameter("title", "Team Member") @@ -591,17 +591,17 @@ WHERE JSON_VALUE(CustomFields, '$.Title') = :title [Benchmark] public IList F2_JSONArrayQuery() { - commandExecutor.Configure(queryInfoCache["F2_JSONArrayQuery"]); + commandExecutor.Configure(queryInfoCache["F2_JSONArrayQuery"]); using var session = sessionFactory.OpenSession(); var sql = """ - SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages - FROM WideWorldImporters.Application.People - WHERE EXISTS ( - SELECT 1 FROM OPENJSON(OtherLanguages) - WHERE value = :lang - ) - """; + SELECT PersonID, FullName, PreferredName, EmailAddress, CustomFields, OtherLanguages + FROM WideWorldImporters.Application.People + WHERE EXISTS ( + SELECT 1 FROM OPENJSON(OtherLanguages) + WHERE value = :lang + ) + """; var people = session.CreateSQLQuery(sql) .SetParameter("lang", "Slovak") @@ -616,13 +616,13 @@ public List G1_Union() { using var session = sessionFactory.OpenSession(); - commandExecutor.Configure(queryInfoCache["G1_Union_1"]); + commandExecutor.Configure(queryInfoCache["G1_Union_1"]); var first = session.Query() .Where(s => s.SupplierID < 5) .Select(s => s.SupplierID) .ToList(); - commandExecutor.Configure(queryInfoCache["G1_Union_2"]); + commandExecutor.Configure(queryInfoCache["G1_Union_2"]); var last = session.Query() .Where(s => s.SupplierID >= 5 && s.SupplierID <= 10) .Select(s => s.SupplierID) @@ -641,13 +641,13 @@ public List G2_Intersection() { using var session = sessionFactory.OpenSession(); - commandExecutor.Configure(queryInfoCache["G2_Intersection_1"]); + commandExecutor.Configure(queryInfoCache["G2_Intersection_1"]); var first = session.Query() .Where(s => s.SupplierID < 10) .Select(s => s.SupplierID) .ToList(); - commandExecutor.Configure(queryInfoCache["G2_Intersection_2"]); + commandExecutor.Configure(queryInfoCache["G2_Intersection_2"]); var last = session.Query() .Where(s => s.SupplierID >= 5 && s.SupplierID <= 15) .Select(s => s.SupplierID) @@ -664,18 +664,18 @@ public List G2_Intersection() [Benchmark] public string H1_Metadata() { - commandExecutor.Configure(queryInfoCache["H1_Metadata"]); + commandExecutor.Configure(queryInfoCache["H1_Metadata"]); using var session = sessionFactory.OpenSession(); var datatype = session.CreateSQLQuery( - """ - SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS - WHERE TABLE_SCHEMA = 'Purchasing' - AND TABLE_NAME = 'Suppliers' - AND COLUMN_NAME = 'SupplierReference' - """ - ) - .UniqueResult(); + """ + SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'Purchasing' + AND TABLE_NAME = 'Suppliers' + AND COLUMN_NAME = 'SupplierReference' + """ + ) + .UniqueResult(); return datatype; } diff --git a/benchmarks/README.md b/benchmarks/README.md index d28bf11..4c8d329 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -1,8 +1,10 @@ +# .NET ORM Benchmarks + Visual Studio projects containing unit and benchmark tests for 3 different .NET ORMs. Visual Studio Test Explorer window can be used to run unit tests. -To run performance benchmarks, set `BenchmarkMain` as a startup project. Configuration must be set to `Release`. Then start the project without debugging (CTRL + F5). A console window will appear. It contains instructions on how to target specific benchmarks. To run all, type in an asterisk (*) and press enter. This will trigger a full run, which takes approximately one hour to finish. You can switch to the test configuration which does only a few numbers of iterations in `BenchmarkMain/Program.cs` file. +To run performance benchmarks, set `BenchmarkMain` as a startup project. Configuration must be set to `Release`. Then start the project without debugging (CTRL + F5). A console window will appear. It contains instructions on how to target specific benchmarks. To run all, type in an asterisk (\*) and press enter. This will trigger a full run, which takes approximately one hour to finish. You can switch to the test configuration which does only a few numbers of iterations in `BenchmarkMain/Program.cs` file. Console command for running benchmarks is `dotnet run --configuration Release --project BenchmarkMain\BenchmarkMain.csproj`. To run with a shorter test configuration, append `-- --testb` at the end of the command. @@ -14,21 +16,23 @@ Tests will not execute without a database running locally. The instructions to s **EFCorePerformance/EFCoreBenchmarks.cs**, **NHibernatePerformance/NHibernateBenchmarks.cs**, and **DapperPerformance/DapperBenchmark.cs** contain updated benchmark code that uses the mocking mechanism to extract full SQL query command, query plan's estimated row count and query output schema information, namely column names, ordinals, and data types. -Then, during query benchmarking I removed the Query execution on the DBMS by mocking or intercepting the command, and then constructing the underlying DbDataReader directly. The idea is to measure each ORM's time and memory overhead without actually executing the possibly long-running query on the database server. I tried to mock the ORMs as low-level as possible to contain the most of the overhead (method calling, memory allocation, etc.) as possible. +Then, during query benchmarking Query execution the DBMS was removed by mocking/intercepting the command, and then constructing the underlying DbDataReader directly. The idea is to measure each ORM's time and memory overhead without actually executing the possibly long-running query on the database server. ORMs were mocked as low-level as possible to contain most of the overhead (method calling, memory allocation, etc.). ## Database setup + Dockerfile is provided to initialize Microsoft SQL Server database and fill it with data. Tested using Docker. Build image: `docker build -t orm-comparison .` -Run container: +Run container: `docker run -d --name orm-comparison -p 1444:1433 orm-comparison` Test connection: `sqlcmd -S 127.0.0.1,1444 -U SA -P Testingorms123 -Q "SELECT * FROM [WideWorldImporters].[Purchasing].[PurchaseOrders]"` ## Sources + - [Wide World Importers sample databases for Microsoft SQL](https://learn.microsoft.com/en-us/sql/samples/wide-world-importers-what-is?view=sql-server-ver16) - [BAK file with WWI database](https://github.com/microsoft/sql-server-samples/releases/download/wide-world-importers-v1.0/WideWorldImporters-Full.bak) - [Microsoft SQL Server image on DockerHuber](https://hub.docker.com/r/microsoft/mssql-server) diff --git a/diagrams/uom_diagrams.drawio b/diagrams/uom_diagrams.drawio new file mode 100644 index 0000000..91e0a65 --- /dev/null +++ b/diagrams/uom_diagrams.drawio @@ -0,0 +1,1981 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..3317c17 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,214 @@ +name: universal-object-mapping +include: + - path: + - ./services/orchestrator/docker-compose.yml + - ./langgraph-override.compose.yml +services: + ollama-autossh-tunnel: + image: jnovack/autossh:2.1.0 + environment: + - SSH_REMOTE_USER=${XRG13_SSH_USERNAME} + - SSH_REMOTE_HOST=xrg13.ms.mff.cuni.cz + - SSH_REMOTE_PORT=1302 + - SSH_TUNNEL_PORT=11434 + - SSH_BIND_IP=0.0.0.0 + - SSH_TARGET_HOST=localhost + - SSH_TARGET_PORT=11434 + - SSH_MODE=-L + - SSH_STRICT_HOST_IP_CHECK=false + ports: + - "${OLLAMA_PORT}:11434" + restart: unless-stopped + volumes: + - ${XRG13_SSH_PRIVATE_KEY_PATH}:/id_rsa + dns: + - 8.8.8.8 + - 4.2.2.4 + # networks: + # - ollama + + # dotnet-service: + # build: + # context: ./services/dotnet-service + # dockerfile: Dockerfile + # ports: + # - "${DOTNET_SERVICE_PORT}:5073" + # - "${DOTNET_SERVICE_SSH_PORT}:22" + # environment: + # - MSSQL_HOST=mssql_db + # - MSSQL_PORT=1433 + # - MSSQL_USER=${MSSQL_USER} + # - MSSQL_PASSWORD=${MSSQL_PASSWORD} + # restart: unless-stopped + # depends_on: + # - mssql_db + + # java-service: + # build: + # context: ./services/java-services + # dockerfile: Dockerfile + # ports: + # - "${JAVA_SERVICE_PORT}:8080" + # - "${JAVA_SERVICE_SSH_PORT}:22" + # environment: + # - SPRING_NEO4J_URI=bolt://neo4j:7687 + # - SPRING_NEO4J_AUTHENTICATION_USERNAME=${NEO4J_USERNAME} + # - SPRING_NEO4J_AUTHENTICATION_PASSWORD=${NEO4J_PASSWORD} + # - SPRING_DATA_MONGODB_URI=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@mongodb:27017/${MONGODB_DATABASE} + # restart: unless-stopped + # depends_on: + # - neo4j + # - mongodb + + mssql_db: + build: + context: ./ORMConvertor + dockerfile: database.Dockerfile + ports: + - "${MSSQL_PORT}:1433" + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=${MSSQL_PASSWORD} + - MSSQL_PID=Developer + restart: unless-stopped + volumes: + - mssql_data:/var/opt/mssql + networks: + - default + - daytona-uom + + mongodb: + image: mongo:8.2.6 + ports: + - "${MONGODB_PORT}:27017" + environment: + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=root + - MONGO_INITDB_DATABASE=${MONGODB_DATABASE} + - MONGO_INITDB_USERNAME=${MONGODB_USER} + - MONGO_INITDB_PASSWORD=${MONGODB_PASSWORD} + restart: unless-stopped + volumes: + - mongodb_data:/data/db + - ./services/etl/mongodb/init-mongodb.sh:/docker-entrypoint-initdb.d/init-mongodb.sh + networks: + - default + - daytona-uom + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + start_period: 10s + interval: 10s + timeout: 5s + retries: 5 + + neo4j: + image: neo4j:2026.02.2 + ports: + - "${NEO4J_BROWSER_PORT}:7474" + - "${NEO4J_PORT}:7687" + environment: + - NEO4J_AUTH=${NEO4J_USERNAME}/${NEO4J_PASSWORD} + - NEO4J_PLUGINS=["apoc"] + - NEO4J_dbms_security_procedures_unrestricted=apoc.* + - NEO4J_dbms_security_procedures_allowlist=apoc.* + - NEO4J_server_memory_heap_initial__size=7900m + - NEO4J_server_memory_heap_max__size=7900m + - NEO4J_server_memory_pagecache_size=10000m + restart: unless-stopped + volumes: + - neo4j_data:/data + - ./services/etl/neo4j:/neo4j-etl + networks: + - default + - daytona-uom + healthcheck: + test: + [ + "CMD-SHELL", + "echo RETURN 1 | cypher-shell -a bolt://localhost:7687 -u ${NEO4J_USERNAME} -p ${NEO4J_PASSWORD} || exit 1", + ] + start_period: 10s + interval: 10s + timeout: 5s + retries: 5 + + mongodb-relational-migrator: + image: public.ecr.aws/v4d7k6c9/relational-migrator:latest + ports: + - "${MONGODB_MIGRATOR_PORT}:8080" + volumes: + - mongodb_migrator_data:/root/Migrator + # MIGRATOR_PATH_DRIVER - optional - Path to a local JDBC driver to mount + - ${MIGRATOR_PATH_DRIVER:-/dev/null}:/opt/mongodb-relational-migrator/lib/app/lib/driver.jar + restart: unless-stopped + depends_on: + mongodb: + condition: service_healthy + mssql_db: + condition: service_healthy + + db-toolbox: + image: us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest + platform: linux/amd64 + ports: + - "${DB_TOOLBOX_PORT}:5000" + environment: + - MSSQL_HOST=mssql_db + - MSSQL_PORT=1433 + - MSSQL_USER=${MSSQL_USER} + - MSSQL_PASSWORD=${MSSQL_PASSWORD} + - MSSQL_DATABASE=WideWorldImporters + - MONGODB_URI=${MONGODB_URI} + - NEO4J_URI=${NEO4J_URI} + - NEO4J_USERNAME=${NEO4J_USERNAME} + - NEO4J_PASSWORD=${NEO4J_PASSWORD} + - NEO4J_DATABASE=neo4j + volumes: + - ./services/orchestrator/src/config:/config + restart: unless-stopped + command: + [ + "--tools-file", + "/config/database_tools.yaml", + "--prebuilt", + "mssql", + "--prebuilt", + "neo4j", + "--ui", + "--address", + "0.0.0.0", + ] + depends_on: + mssql_db: + condition: service_healthy + neo4j: + condition: service_healthy + + mongodb-mcp-server: + image: mongodb/mongodb-mcp-server:latest + ports: + - "3010:3000" + environment: + - MDB_MCP_TRANSPORT=http + - MDB_MCP_HTTP_HOST=0.0.0.0 + - MDB_MCP_HTTP_PORT=3000 + - MDB_MCP_LOGGERS=disk,mcp,stderr + - MDB_MCP_CONNECTION_STRING=${MONGODB_URI} + - MDB_MCP_READ_ONLY=true + - MDB_MCP_DISABLED_TOOLS=create,update,delete,switch-connection,atlas-local-list-deployments,atlas-local-create-deployment,atlas-local-connect-deployment,atlas-local-delete-deployment,list-knowledge-sources,search-knowledge,export,collection-storage-size,collection-indexes,db-stats,mongodb-logs,connect + restart: unless-stopped + depends_on: + mongodb: + condition: service_healthy + +volumes: + mssql_data: + mongodb_data: + neo4j_data: + postgres_data: + mongodb_migrator_data: + +networks: + daytona-uom: + external: true + name: daytona-uom diff --git a/langgraph-override.compose.yml b/langgraph-override.compose.yml new file mode 100644 index 0000000..562dd3a --- /dev/null +++ b/langgraph-override.compose.yml @@ -0,0 +1,30 @@ +services: + langgraph-api: + develop: + watch: + - action: rebuild + path: ./src + target: /deps/orchestrator/src + initial_sync: true + - action: sync + path: ./scripts + target: /deps/orchestrator/scripts + initial_sync: true + - action: rebuild + path: pyproject.toml + networks: + - default + - daytona-uom + # environment: + # - REDIS_URI=redis://langgraph-redis:6379 + # - POSTGRES_URI=postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable + # - NEO4J_DATABASE=neo4j + # - NEO4J_USERNAME=${NEO4J_USERNAME} + # - NEO4J_PASSWORD=${NEO4J_PASSWORD} + # - NEO4J_URI=neo4j://neo4j:7687 + # - MONGODB_URI=mongodb://${MONGODB_USER}:${MONGODB_PASSWORD}@mongodb:27017/${MONGODB_DATABASE} + # - MSSQL_HOST=mssql_db + # - MSSQL_PORT=1433 + # - MSSQL_USER=${MSSQL_USER} + # - MSSQL_PASSWORD=${MSSQL_PASSWORD} + # - DB_TOOLBOX_URI=http://db-toolbox:5000 diff --git a/notes/architecture.md b/notes/architecture.md new file mode 100644 index 0000000..eace073 --- /dev/null +++ b/notes/architecture.md @@ -0,0 +1,64 @@ +# Universal Object Mapping (UOM) Architecture + +## Overview + +Universal Object Mapping (UOM) is an advanced translation and optimization system designed to migrate database schemas (DDL/Code-First mappings) and queries across different programming languages and Object-Relational/Document/Graph Mapping (ORM/ODM/OGM) frameworks. + +The system replaces traditional heuristic-based parsing with a modern **LLM-based iterative translation architecture**, utilizing a "Council of Models" and a ReAct agent for autonomous validation and correction. + +--- + +## Core Components + +### 1. Orchestrator (`services/orchestrator`) +The brain of the system, implemented as a Python service using **LangGraph**. +- **Council of Models**: Parallel brainstorming using diverse LLMs: + - **Local**: Ollama (e.g., `qwen3-coder:30b`, `gpt-oss`). + - **Remote**: E-infra (vLLM OpenAI-compatible API). +- **Translation Agent (ReAct)**: An autonomous agent that takes the brainstormed strategies and iteratively performs: + - **Translation**: Generating target schema/queries. + - **Validation**: Calling external tools (Serena MCP, .NET/Java services) to check for errors. + - **Correction**: Fixing compilation or syntax errors based on tool feedback. + +### 2. Service Layer +Provides the environment for compilation, validation, and benchmarking. +- **.NET Service (`services/dotnet-service`)**: + - Compiles and validates C# (EF Core, Dapper, NHibernate). + - Executes benchmarks using **BenchmarkDotNet**. +- **Java Service (`services/java-services`)**: + - Compiles and validates Java (Spring Data JPA, MongoDB, Neo4j). + - Handles Spring-specific validation and benchmarking. + +### 3. Model Context Protocol (MCP) Integration +UOM leverages the MCP standard for seamless tool integration: +- **Serena MCP**: Performs semantic code analysis (AST manipulation, symbol tracking) without full microservice isolation. +- **Context7 / Database Toolbox MCP**: Connects directly to databases (MSSQL, MongoDB, Neo4j) to extract real schemas for LLM context and to evaluate query plans. + +### 4. Database Layer +Supports a hybrid polyglot environment: +- **Relational**: MS SQL Server (source), Postgres (orchestrator state/pgvector). +- **NoSQL**: MongoDB, Neo4j. +- **Migration**: Uses MongoDB Relational Migrator and Neo4j ETL tools for initial data movement. + +--- + +## Data Flow (Updated LLM Workflow) + +1. **Input Extraction**: The orchestrator extracts source code, target frameworks, and source/destination database types from the user request. +2. **Schema Context**: The **Database Toolbox MCP** extracts the current database schema (DDL) to provide ground truth to the LLMs. +3. **Brainstorming**: Multiple LLMs generate alternative translation strategies (e.g., how to map a SQL `JOIN` to a Cypher `MATCH`). +4. **Iterative Fix Loop**: + - The ReAct agent produces an initial translation. + - It calls the `.NET` or `Java` validation tool. + - If errors occur (e.g., missing imports, syntax errors), the agent uses the error messages to refine the code. +5. **Validation & Benchmarking**: Once valid, the code is benchmarked to measure ORM overhead (query translation, object mapping) using the isolated mocking mechanism. +6. **Optimization**: The ILP-based **Advisor** (from the original ORMConvertor project) suggests the optimal configuration based on benchmark results. + +--- + +## Legacy Integration +The original **ORMConvertor** project remains part of the repository, serving as: +- **Frontend Interface**: The web UI and API endpoints for user interaction. LLM orchestrator UI is integrated here using LangChain's React `assistant-ui` components. +- **Reference Implementation**: Ground truth for heuristic-based parsing. +- **Context Provider**: Roslyn-based parsers extract structured metadata to augment LLM prompts. +- **Advisor Logic**: The native C-based ILP solver is still used for the final optimization suggestion. diff --git a/notes/future_specification.md b/notes/future_specification.md new file mode 100644 index 0000000..607f954 --- /dev/null +++ b/notes/future_specification.md @@ -0,0 +1,54 @@ +# Universal Object Mapping (UOM) Project Specification + +## Stack + +- Ollama self-hosted or robust reasoning LLM + - Available models: gpt-oss:latest, qwen3-coder:30b, mistral-small3.2:latest + - Embeddings: qwen3-embedding:latest +- vLLM OpenAI-compatible API server: + - E-infra Chat AI service API: https://llm.ai.e-infra.cz/v1/ + - Docs: https://docs.cerit.io/en/docs/ai-as-a-service/ai-api +- Setup bash scripts setting up databases (MS SQL Server, MongoDB, Neo4J), setting up and calling ETL pipelines (see https://github.com/corovcam/Query-Languages-Analysis-Thesis) +- docker compose microservices - kubernetes? helm chat? + - LangGraph API Server - LLM orchestrator + - LangGraph (LLM orchestrator/translator) - Iterative LLM workflow + - Communicates with serena MCP server (or dotnet/java CLI tools as fallback) for compilation and semantic checks. Replaces dedicated microservices. + - Reference: See `orchestrator-architecture.md` for full graph and integration details. + - code first→migrate: + - LLM context: classes, mapping-files (relmig, neo4j-json), custom prompts, etc. + - reverse-engineered: + - LLM context: database schema extracted from DB, custom prompts, etc. + - Getting output from these services, parsing possible errors → iterating on that + - For queries, user writes in one query language or orm/odm/ogm-language and translates to the selected one + - queries are sent to respective services, compiled, and query plan, query output schema (data types, length,…) and possibly output rows/documents are streamed back to the orchestrator (all of them or subset?) + - orchestrator agent iteratively modifies queries up to certain timeout, retries + - Valid queries are benchmarked in respective services (like BenchmarkDotNet in .NET) → output to orchestrator + - .NET Compilation via Serena MCP (incorporating a C# language server) or fallback to `dotnet` CLI for compilation, benchmarking. + - Java Compilation via Serena MCP (incorporating a Java language server) or fallback to Java CLI. + - Database Toolbox MCP (googleapis/genai-toolbox) used via Context7 documentation for database communication (extracting schemas, executing queries). + - MS SQL Server + - MongoDB + - MongoDB Relational Migrator + - Neo4j + Neo4j ETL Tool GUI (to extract relational-graph mapping in json) + Neo4j ETL Tool CLI with [`neo4j-admin database import`](https://neo4j.com/docs/operations-manual/current/import/) + +## Old Flow + +1. Configuration scripts are run + 1. Databases setup + 2. ETL tools - similar processes as my https://github.com/corovcam/Query-Languages-Analysis-Thesis +2. Using ORMConvertor, user input ORM schema mapping in either EFCore, NHibernate, or Dapper that works with already instantiated databases. + 1. Possibly I could just build a CLI tool similar to ORMorpher, because I don’t really need to extend the UI since its a developer tool. +3. User input Queries he want translated and benchmarked in the previously selected ORM +4. ORMConvertor is run - ORM schema translation, query translation, advisor benchmarking, advisor ILP optimization, results table shown + +## Updated Flow + +5. Instead of ORMConvertor heuristic-based schema+query transformation, LLM-based iterative translation is used (described also in Stack section) +6. Relational ORM schema (which ORMs?) and queries (+ extracted SQL query strings) are serialized and sent to LLM Translator/Orchestrator via REST API +7. Translator also extracts relational DDL schema using database connector for context +8. Prompt are engineered and sent to create Spring Data Neo4j and Spring Data MongoDB schemas. Uses E-infra models for diversity (Council of Models) +9. Translator waits for response from the compilation tools (Serena MCP or CLI) +10. If errors are encountered, fix them iteratively. If beyond max tries, trigger Human-in-the-Loop. +11. Queries are processed similarly - iterative process, but with Query Plans, Output Schema, and possibly whole query execution (challenges with results data model differences and amount of data) +12. Valid queries are benchmarked using similar approach as ORMConvertor +13. Lastly, results are shown either in extended ORMorpher UI (or some CLI) diff --git a/notes/orchestrator-architecture.md b/notes/orchestrator-architecture.md new file mode 100644 index 0000000..b19945c --- /dev/null +++ b/notes/orchestrator-architecture.md @@ -0,0 +1,49 @@ +# LangGraph Orchestrator Architecture + +## Overview + +The Orchestrator Service is the core intelligence component of the Universal Object Mapping (UOM) project. It utilizes an iterative LangGraph workflow to translate and validate Database Schemas (DDL/Code-First mappings) and Queries across different languages and frameworks (.NET, Java, MS SQL, Neo4j, MongoDB). + +## Core Architecture Stack + +- **LangGraph API Server**: Provides the execution environment, state management, and API endpoints for the orchestrator graph. +- **LLM Providers**: + - Local: Ollama (`gpt-oss`, `qwen3`, `mistral`) + - Remote: E-infra OpenAI-compatible API + - Note: Models are configurable at runtime via LangGraph configuration parameters. +- **Tools / Capabilities (Model Context Protocol)**: + - **Database Toolbox MCP** (`googleapis/genai-toolbox`): Extracts schemas, connects to databases, and evaluates queries directly. + - **Serena MCP**: Semantic code analysis and manipulation agent. Wraps standard Language Servers (C#, Java) to analyze ASTs, validate syntax, and detect symbol references without requiring fully isolated microservices. + - **CLI Fallbacks**: Direct host invocation of `dotnet`, `java`, or `mvn` when deeper compilation or test running is necessary. + +## Graph Design + +The orchestrator operates over a [State](file:///c:/Users/marti/Documents/Uni/DP/Universal-Object-Mapping/services/orchestrator/src/react_agent/state.py) containing: + +- Initial inputs: `source_code` (e.g. C# EFCore), `source_target` (e.g., MS SQL), `destination_target` (e.g. Java Spring Data MongoDB) +- Iteration metadata: `council_responses` +- Messages: Full conversation history, including tool calls and outputs. + +### Node Workflow + +The graph follows a linear sequence, but the intelligence is concentrated in a **ReAct Agent** that performs internal iteration. + +1. `extract_input`: + - Analyzes recent messages to extract `source_code`, `source_target`, and `destination_target` using structured LLM output (`ExtractionOutput`). +2. `council_of_models`: + - Triggers parallel LLM generation from diverse models (e.g. 1x Ollama Qwen3-Coder, 1x E-infra GLM-4). + - Generates multiple translation strategies stored in `council_responses`. +3. `translation_agent`: + - A **ReAct Agent** implemented using `create_agent`. It takes the brainstormed strategies as part of its prompt. + - It performs the translation and iteratively uses tools to validate and fix the code. + - **Tools available**: + - `validate_java_code`: Compiles/validates Java Spring Data code. + - `validate_dotnet_code`: Compiles/validates .NET (EF Core, Dapper, etc.) code. + - `search`: Web search (Tavily) for documentation and examples. + - The agent continues its tool-use loop until it produces a valid `TranslationOutput` or hits internal retry limits. +4. `__end__`: + - The final state contains the full sequence of messages, including the validated translation. + +## Modularity Considerations + +Both the logic handling tools (`java_validator`, `dotnet_validator`) and the LLM configurations are designed to be explicitly modular. Rather than hard-coding EFCore, the `.NET` tool detects or requests the ORM type from context and adjusts its validation commands (e.g., MSBuild flags, dependent package checks) accordingly. diff --git a/notes/project-specification/.gitignore b/notes/project-specification/.gitignore new file mode 100644 index 0000000..2fb5a7f --- /dev/null +++ b/notes/project-specification/.gitignore @@ -0,0 +1,3 @@ +.output/ +project-specification*.tex +code/level3.plantuml diff --git a/notes/project-specification/code/level1.plantuml b/notes/project-specification/code/level1.plantuml new file mode 100644 index 0000000..49b970b --- /dev/null +++ b/notes/project-specification/code/level1.plantuml @@ -0,0 +1,29 @@ +@startuml C4_Context +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml + +!$ICONURL = "https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/v3.0.0/icons" +!include $ICONURL/common.puml + +!define DEVICONS2 $ICONURL/devicons2 +!define FONTAWESOME6 $ICONURL/font-awesome-6 +!include DEVICONS2/microsoftsqlserver.puml +!include DEVICONS2/mongodb.puml +!include DEVICONS2/neo4j.puml + +Person(dev, "Developer / DB Architect", "Migrates ORM schemas and queries across frameworks; evaluates performance.") + +System(orm_morpher, "ORMorpher Web App", $descr="Existing ASP.NET Core web app for ORM translations.") +System(uom, "Universal Object Mapping (UOM) System", "LLM-driven translation and validation pipeline for ORM/ODM/OGM migration.") + +System_Ext(ollama_ext, "MFF UK Ollama Server", "Faculty's local Ollama server", $type="Ollama Server") +System_Ext(einfra_ext, "E-Infra vLLM API", "Remote LLM API provided by Metacentrum", $type="OpenAI-Compatible API") + +Rel(dev, orm_morpher, "Creates/imports SQL Server database", "Docker / Scripts") +Rel(dev, orm_morpher, "Submits source code; selects frameworks; views results", "Web UI") +BiRel(orm_morpher, uom, "Submits source schema+queries; receives translated code", "HTTPS / Web UI") + +Rel(uom, ollama_ext, "Uses LLM", "SSH Tunnel") +Rel(uom, einfra_ext, "Uses LLM", "HTTPS") + +SHOW_LEGEND() +@enduml \ No newline at end of file diff --git a/notes/project-specification/code/level2.plantuml b/notes/project-specification/code/level2.plantuml new file mode 100644 index 0000000..2584b0f --- /dev/null +++ b/notes/project-specification/code/level2.plantuml @@ -0,0 +1,72 @@ +@startuml C4_Containers +!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml + +!$ICONURL = "https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/v3.0.0/icons" +!include $ICONURL/common.puml + +!define DEVICONS2 $ICONURL/devicons2 +!define FONTAWESOME6 $ICONURL/font-awesome-6 +!include DEVICONS2/microsoftsqlserver.puml +!include DEVICONS2/mongodb.puml +!include DEVICONS2/neo4j.puml +!include DEVICONS2/postgresql.puml +!include DEVICONS2/redis.puml +!include DEVICONS2/dot_net.puml +!include DEVICONS2/java.puml + +Person(dev, "Developer / DB Architect") + +System_Boundary(orm_morpher, "ORMorpher Web App", $descr="Existing ASP.NET Core web app for ORM translations.") { + Container(ormui, "ORMorpher Web UI", "Angular/React", "Web UI hosting the ORM translation workflow. Extended with LLM advisor widget. Exposes REST API.") +} + +System_Boundary(uom, "Universal Object Mapping (UOM) System") { + Container(orchestrator, "LangGraph Orchestrator", "Python, LangGraph API Server, FastAPI", "Core LLM orchestratation engine. Runs translation pipeline, manages graph state, and coordinates interactions between LLM agents and system components.") + + Container(dotnet_svc, ".NET Service", "ASP.NET Core, Roslyn", "C# schema/query compilation and validation. Frameworks: EF Core, NHibernate, Dapper.", $sprite="dot_net") + + Container(java_svc, "Java Service", "Spring Boot, Maven", "Java schema/query compilation and validation. Frameworks: Spring Data MongoDB, Spring Data Neo4j.", $sprite="java") + + Boundary(db, "Databases") { + ContainerDb(pg, "PostgreSQL", "PostgreSQL + pgvector", "LangGraph persistence store; vector embeddings for context retrieval.", $sprite="postgresql") + + ContainerDb(redis, "Redis", "Redis", "LangGraph in-memory cache store for LLM agents.", $sprite="redis") + + ContainerDb(mssql, "MS SQL Server", "SQL Server 2022", "Source relational database.", $sprite="microsoftsqlserver") + + ContainerDb(mongo, "MongoDB", "MongoDB 8", "Target document database.", $sprite="mongodb") + + ContainerDb(neo4j, "Neo4j", "Neo4j 2026", "Target graph database.", $sprite="neo4j") + } + + Boundary(etl, "ETL Tools") { + Container(etl_mongo, "MongoDB Relational Migrator", "Web UI", "Creates relational-document mapping and loads data into MongoDB.") + + Container(etl_neo4j, "Neo4j ETL", "Neo4j ETL Tool UI/CLI + Bash (neo4j-admin)", "Creates relational-graph mapping and loads data into Neo4j.") + } +} + +System_Ext(ollama_ext, "MFF UK Ollama Server", "Faculty's local Ollama server", $type="Ollama Server") +System_Ext(einfra_ext, "E-Infra vLLM API", "Remote LLM API provided by Metacentrum", $type="OpenAI-Compatible API") + +Rel(dev, orm_morpher, "Creates/imports SQL Server database", "Docker / Scripts") +Rel(orm_morpher, mssql, "ORM data source", "ADO.NET") + +Rel(dev, ormui, "Submits source code; selects frameworks; views results", "WEB UI") +BiRel(ormui, orchestrator, "Forwards translation request; receives streamed results", "REST / SSE") +Rel(orchestrator, dotnet_svc, "Compile & validate .NET", "REST / CLI") +Rel(orchestrator, java_svc, "Compile & validate Java", "REST / CLI") +Rel(orchestrator, pg, "Persist graph state; vector context", "SQL") +Rel(orchestrator, redis, "LangGraph caching", "Redis Protocol") +Rel(orchestrator, ollama_ext, "Uses LLM", "SSH Tunnel") +Rel(orchestrator, einfra_ext, "Uses LLM", "HTTPS") +Rel(dotnet_svc, mssql, "Execute benchmark queries", "ADO.NET") +Rel(java_svc, mongo, "Execute benchmark queries", "Spring Data MongoDB") +Rel(java_svc, neo4j, "Execute benchmark queries", "Spring Data Neo4j") + +Rel(dev, etl, "Creates mappping and loads data") +Rel(etl_mongo, mongo, "Create mapping; load documents") +Rel(etl_neo4j, neo4j, "Create mapping in UI; load data with bash script") + +SHOW_LEGEND() +@enduml \ No newline at end of file diff --git a/notes/project-specification/img/llm-widget-mockup.png b/notes/project-specification/img/llm-widget-mockup.png new file mode 100644 index 0000000..89972c0 Binary files /dev/null and b/notes/project-specification/img/llm-widget-mockup.png differ diff --git a/notes/project-specification/img/ormorpher-architecture.pdf b/notes/project-specification/img/ormorpher-architecture.pdf new file mode 100644 index 0000000..a95d431 Binary files /dev/null and b/notes/project-specification/img/ormorpher-architecture.pdf differ diff --git a/notes/project-specification/img/ormorpher-ui.png b/notes/project-specification/img/ormorpher-ui.png new file mode 100644 index 0000000..eeedd13 Binary files /dev/null and b/notes/project-specification/img/ormorpher-ui.png differ diff --git a/notes/project-specification/img/ormorpher-user-flow.pdf b/notes/project-specification/img/ormorpher-user-flow.pdf new file mode 100644 index 0000000..8f60326 Binary files /dev/null and b/notes/project-specification/img/ormorpher-user-flow.pdf differ diff --git a/notes/project-specification/img/ui-mockup.png b/notes/project-specification/img/ui-mockup.png new file mode 100644 index 0000000..d0fa136 Binary files /dev/null and b/notes/project-specification/img/ui-mockup.png differ diff --git a/notes/project-specification/img/uom-c4-architecture-level1.pdf b/notes/project-specification/img/uom-c4-architecture-level1.pdf new file mode 100644 index 0000000..8efdfc0 Binary files /dev/null and b/notes/project-specification/img/uom-c4-architecture-level1.pdf differ diff --git a/notes/project-specification/img/uom-c4-architecture-level2.pdf b/notes/project-specification/img/uom-c4-architecture-level2.pdf new file mode 100644 index 0000000..5c09d3e Binary files /dev/null and b/notes/project-specification/img/uom-c4-architecture-level2.pdf differ diff --git a/notes/project-specification/img/uom-workflow.pdf b/notes/project-specification/img/uom-workflow.pdf new file mode 100644 index 0000000..bbed36d Binary files /dev/null and b/notes/project-specification/img/uom-workflow.pdf differ diff --git a/notes/project-specification/specification-llm-advisor.tex b/notes/project-specification/specification-llm-advisor.tex new file mode 100644 index 0000000..d796aa9 --- /dev/null +++ b/notes/project-specification/specification-llm-advisor.tex @@ -0,0 +1,309 @@ +\documentclass{article} + +\usepackage[a-2u]{pdfx} +\usepackage[utf8]{inputenc} +\usepackage{array} +\usepackage[ddmmyyyy]{datetime} +\usepackage{geometry} +\usepackage{hyperref} +\usepackage{listings} +\usepackage{xcolor} +\usepackage{graphicx} +\usepackage{float} + +\renewcommand{\dateseparator}{.} +\renewcommand{\baselinestretch}{1.5} + +\geometry{margin=2cm} + +\def\ResearchProject{Research project} +\def\CompanyProject{Company project} +\def\SoftwareProject{Software project} + +% METADATA SECTION START +\def\StudentsFullName{Martin Čorovčák} +\def\StudyProgram{Computer Science --- Software and Data Engineering} +\def\ProjectTitle{LLM Advisor for Migration Between ORM/ODM/OGM Frameworks} +\def\ProjectType{\ResearchProject} +\def\SupervisorFullName{Ing. Pavel Koupil, Ph.D.} +\def\ConsultantsFullName{doc. RNDr. Irena Holubová, Ph.D.} +% METADATA SECTION END + +\title{\ProjectTitle} +\author{\StudentsFullName} + +\begin{document} + +\centerline{\Large \textbf{Specification of team software project}} +\centerline{ \textbf{Department of Software Engineering}} +\centerline{ \textbf{Faculty of Mathematics and Physics, Charles University}} +\bigskip +{\noindent\begin{tabular*}{\textwidth}{ >{\raggedleft}m{4cm} l} +\textbf{Solvers:} & \StudentsFullName \\ +\textbf{Study program:} & \StudyProgram \\[1em] +\textbf{Project title:} & \ProjectTitle \\ +\textbf{Project type:} & \ProjectType \\[1em] +\textbf{Supervisor:} & \SupervisorFullName \\ +\textbf{Consultants:} & \ConsultantsFullName \\ +\end{tabular*}} + +\section{Introduction} +Within the Adaptive Data Management (ADaM) Research Group at the Department of Software Engineering, Charles University, extensive research has been conducted on the unified management of multi-model data. This line of work has addressed several core aspects of heterogeneous data processing, including conceptual modeling, querying, evolution, and optimization across diverse database paradigms. As part of these efforts, earlier work by Milan Abrahám~\cite{ormorpherthesis} introduced the \textit{ORMorpher} tool, a rule-based system for translating and optimizing schemas and queries between .NET Object-Relational Mapping frameworks, namely Entity Framework Core\footnote{\url{https://learn.microsoft.com/en-us/ef/core/}}, NHibernate\footnote{\url{https://nhibernate.info/}}, and Dapper\footnote{\url{https://github.com/DapperLib/Dapper}}, using a unified intermediate representation. This line of research was subsequently formalized in a joint publication~\cite{ase2025}, which describes the parser-builder architecture and the ILP-based framework-selection advisor underlying ORMorpher. + +The objective of this project is to significantly extend the scope of ORMorpher within the broader \emph{Universal Object Mapping (UOM)} project~\cite{uomrepo}. In particular, the project targets cross-paradigm translation from .NET ORM frameworks to Java-based Object-Document Mapping frameworks, represented by Spring Data MongoDB\footnote{\url{https://docs.spring.io/spring-data/mongodb/reference/}}, and Object-Graph Mapping frameworks, represented by Spring Data Neo4j\footnote{\url{https://docs.spring.io/spring-data/neo4j/reference/}}. Rather than relying primarily on a heuristic rule engine, the project explores an LLM-driven iterative translation pipeline intended to support more flexible and semantically informed migration between heterogeneous framework ecosystems. + +To achieve this, the project introduces the \textbf{LLM Advisor} as its main architectural contribution. The proposed solution is based on a semi-deterministic, graph-structured workflow built using the open-source LangChain LangGraph framework\footnote{\url{https://www.langchain.com/langgraph}}. Within this workflow, autonomous reasoning-and-acting (ReAct) agents are orchestrated to iteratively generate, validate, and refine translation outputs. The process combines multiple forms of contextual information and external tooling, including database schemas, retrieval of relevant and up-to-date framework documentation, and compilation or validation against sandboxed backend services. In this way, the project aims to provide an intelligent translation advisor capable of addressing forms of structural and semantic heterogeneity that are too substantial to be handled robustly by deterministic rule-based translators alone. + +\subsection{Relation to other projects} + +This project builds directly on the \emph{ORMorpher} project~\cite{ormorpherthesis,ormorpherrepo}, which features a .NET Roslyn compiler-based translations from .NET ORM to Abstract Representation and vice-versa using heuristics and schema/query string parsers (see Figure~\ref{fig:ormorpher-architecture}), then ILP Advisor (benchmarking, optimization and subsequent framework selection), and lastly ASP.NET backend with Angular frontend (see Figure~\ref{fig:ormorpher-ui}). The future \textbf{LLM Advisor} can be integrated into ORMorpher's existing UI as a plugin widget, but also acts as a separate standalone service with its own REST API, allowing for future reuse in other contexts (e.g., publicly available MCP server\footnote{\url{https://modelcontextprotocol.io/docs/getting-started/intro}, \url{https://mcpservers.org/}}, MM-cat schema translations~\cite{mmcat}). + +\begin{figure}[ht] + \centering + \includegraphics[width=0.55\textwidth]{img/ormorpher-architecture.pdf} + \caption{ORMorpher architecture diagram. Extracted from ORMorpher thesis~\cite{ormorpherthesis}.} + \label{fig:ormorpher-architecture} +\end{figure} + +\begin{figure}[ht] + \centering + \includegraphics[width=0.8\textwidth]{img/ormorpher-ui.png} + \caption{ORMorpher UI. Lives on \url{http://116.203.208.55/orm/home}. Extracted from ORMorpher thesis~\cite{ormorpherthesis}.} + \label{fig:ormorpher-ui} +\end{figure} + +\section{Project Description} + +The main goal is to design and implement an \emph{LLM-driven translation pipeline} that accepts a source ORM schema (i.e. from EFCore, NHibernate, and Dapper) and a set of associated queries for one framework and produces equivalent schemas and queries for one or more target frameworks. The complete \emph{UOM} system includes database setup, defining and automating ETL processes, setup scripts, and providing a user-friendly interface for managing the translation process. Then, the advisor pipeline handles translation, iterative validation/error correction, and results presentation. + + +\begin{figure}[htbp] +\centering +\includegraphics[width=\textwidth, height=0.85\textheight, keepaspectratio]{img/uom-workflow.pdf} +\caption{User interaction flow example after integration of the LLM Advisor. Before: User Input (EF Core entity + LINQ query) $\to$ Intermediate Representation (Entity/PropertyMap, QueryInstructions) $\to$ Target ORM code (Dapper SQL). After integration: User Input (EF Core entity + LINQ query) $\to$ LLM Advisor UI widget $\to$ LangGraph API Server $\to$ Target OGM/ODM code (e.g. Spring Data MongoDB).} +\label{fig:uom-workflow} +\end{figure} + +The updated workflow (see Figure~\ref{fig:uom-workflow}) proceeds as follows: +\begin{enumerate} + \item ORMorpher and MS SQL Server are initialized with a sample dataset using Docker. + \item The user is asked to configure mapping for MongoDB in MongoDB Relational Migrator\footnote{\url{https://www.mongodb.com/products/tools/relational-migrator}}, and for Neo4j in Neo4j ETL Tool\footnote{\url{https://neo4j.com/labs/etl-tool/}}, and then the ETL pipelines are executed. This is a one-time semi-automatic setup to define how relational tables/columns map to document/graph structures. + \item The user provides source-framework schema and queries via the ORMorpher UI and the LLM Advisor UI plugin. + \item The \textbf{Orchestrator} extracts database schemas (MS SQL Server, MongoDB, Neo4j) to provide ground-truth schema context to the LLMs. + \item The LangGraph \textbf{Translation Agent} iteratively generates and validates code using various tools (e.g. JavaCompiler class, or Maven CLI) in sandboxed Docker environments. + \item Compilation/validation errors, query execution plans, query result schemas, etc. are fed back to the agent; it refines code up to a configurable retry limit. A manual user interaction is triggered if retries are exhausted. + \item Results are displayed in the UI. +\end{enumerate} + + +\subsection{Functional Requirements} + +\begin{itemize} + \item \textbf{F01 ETL Pipeline:} Provide scripts or workflows to initialize and populate MS SQL Server, MongoDB, and Neo4j from a sample dataset. + \item \textbf{F02 Schema Translation:} Given source entity classes for EF Core, NHibernate, or Dapper, produce equivalent (with respect to relational-document/graph mapping) entity classes for Spring Data MongoDB or Spring Data Neo4j. + \item \textbf{F04 Query Translation:} Translate LINQ / Dapper-SQL queries from a source .NET ORM to logically (with respect to data model mapping) equivalent\footnote{Equivalent queries output logically same results but in different formats, e.g. CSV tables vs. JSON documents, but in context of ORM/ODM/OGM frameworks we should get similar instances (i.e. logically equivalent properties) of entities in supported languages (e.g. C\# objects vs. Java objects)} queries for target Java Spring Data frameworks. + \item \textbf{F06 Iterative Refinement:} Translation workflow must iteratively correct errors based on compilation/validation feedback until code is valid or a retry/timeout limit is reached. + \item \textbf{F07 Manual Intervention:} When the retry limit is exhausted, stop and present the current state to the user for manual correction. + \item \textbf{F10 UI Plugin:} Integrate an LLM translation widget into the existing ORMorpher ASP.NET frontend; show translation logs, prompts, LLM outputs, and/or validation results. + \item \textbf{F11 REST API:} Expose a LangGraph API Server REST API for external triggering of translations and retrieval of results. + \item \textbf{F12 Configurable LLM Backend:} LLM providers (Ollama~\cite{ollama} server hosted by XRG at KSI MFF UK~\cite{xrg-mff}, CERIT-SC vLLM hosted by e-INFRA CZ (Metacentrum)~\cite{einfra}, or any other OpenAI-compatible API) must be swappable via environment configuration. +\end{itemize} + +\subsection{Non-Functional Requirements} + +\begin{itemize} + \item \textbf{N01 Performance:} End-to-end translation should complete within a reasonable time frame (e.g. under 5 minutes for simple one-entity schema, and simple select queries). + \item \textbf{N02 Observability:} All LLM calls, tool invocations, token counts, and retry chains must be traced via Observability tools (e.g. LangSmith\footnote{\url{https://docs.langchain.com/langsmith/home}}) for later analysis. + \item \textbf{N03 Deployment:} The entire system must be containerized using Docker Compose (services include ORMorpher, databases, LangGraph ecosystem, translation tools, etc.). + \item \textbf{N04 Security:} The system must be designed with security best practices in mind, especially when executing generated code in sandboxed environments. Proper isolation and resource limits must be enforced. + \item \textbf{N05 Open Source:} Source code must be publicly available under an open-source licence on GitHub~\cite{uomrepo}. +\end{itemize} + +\subsection{Mockups} + +The primary user interface for the \emph{LLM Advisor} is an embedded UI widget integrated into the existing ORMorpher web application, as shown in Figure~\ref{fig:ui-mockup}. The interface is divided into two main parts: + +\begin{itemize} + \item \textbf{Background App (ORMorpher UI):} Allows users to select source frameworks, input source schemas (e.g., C\# classes) and queries (e.g., LINQ), and view legacy heuristic translations. + \item \textbf{LLM Advisor Plugin:} A standalone modal assistant located in the lower right corner. Users can select target frameworks, trigger translations by sending custom prompts with source/target/code information (free form, or possibly through dropdowns), and trigger AI-driven translation workflows. Finally, it streams translated code natively into the assistant with optional error-handling/fallbacks and possible manual user input. +\end{itemize} + +This plugin can optionally be integrated into other web applications or accessed programmatically via its REST API. The design remains subject to change as development progresses. + +\begin{figure}[htb] + \centering + \includegraphics[width=0.95\textwidth]{img/ui-mockup.png} + \caption{UI after integration of the LLM Orchestrator. From existing ORMorpher two-panel layout (Source/Target ORM dropdowns, Entity C\# class input, LINQ/Dapper-SQL Query inputs, Convert button, translated C\# code), to the same UI but with the LLM Advisor plugin.} + \label{fig:ui-mockup} +\end{figure} + +\subsection{Architecture} +\label{subsec:architecture} + +The \emph{UOM} system uses Docker Compose for portable containerization. Its architecture, illustrated using C4 model diagrams (System Context Level 1 in Figure~\ref{fig:c4-level1} and Container Level 2 in Figure~\ref{fig:c4-level2}), consists of four primary components: + +\begin{itemize} + \item \textbf{ORMorpher Web UI:} The frontend interface capturing user inputs and displaying translated outputs via REST API or SSE. + \item \textbf{LangGraph Orchestrator:} The core Python LLM engine managing workflow agents, tool coordination, and state persistence (PostgreSQL/Redis). + \item \textbf{Validation Sandboxes:}\footnote{The sandbox services are initial proposals and may be refactored or replaced if more efficient, secure runtime evaluation alternatives are discovered during development.} Isolated execution environments (currently proposed as a .NET Service\footnote{This service may be represented by existing ORMorpher application alone, since the target translation frameworks don't include .NET ORMs. This functionality is already implemented in its original heuristic-based approach.} and Java Service) for verifying syntactic and functional code correctness through dynamic compilation (e.g., Roslyn/dotnet CLI, JavaCompiler/Maven). + \item \textbf{Databases \& ETL:} Source/target data stores (MS SQL Server, MongoDB, Neo4j) and ETL tools (MongoDB Relational Migrator, Neo4j ETL Tools) ensuring schema and sample data availability for translations. +\end{itemize} + + + +\begin{figure}[htb] + \centering + \includegraphics[scale=0.5]{img/uom-c4-architecture-level1.pdf} + \caption{System Context (Level 1) diagram showing the Universal Object Mapping (UOM) system as a central component interacting with the user, the existing ORMorpher web app, and external LLM providers (Ollama server and E-infra vLLM API~\cite{einfra}).} + \label{fig:c4-level1} +\end{figure} + +\begin{figure}[htb] + \centering + \includegraphics[scale=0.3]{img/uom-c4-architecture-level2.pdf} + \caption{Container (Level 2) diagram showing the internal structure of the Universal Object Mapping (UOM) system, including the LangGraph (LLM) Orchestrator, validation services, databases, ETL Tools, and external LLM providers.} + \label{fig:c4-level2} +\end{figure} + +\section{Platform and Technologies} + +The project relies on a diverse technology stack to handle the multi-language compilation, LLM orchestration, and data storage requirements. The chosen technologies are grouped as follows: + +\begin{itemize} + \item \textbf{Orchestrator:} Python, LangGraph API Server, LangChain, LangSmith (or other Observability platform), \texttt{uv}. Used to build the core AI orchestration layer, managing the stateful ReAct agents and their workflow. + \item \textbf{LLM Providers:} MFF UK Ollama Server (e.g. \texttt{qwen3-coder:30b}), E-infra vLLM OpenAI-compatible API~\cite{einfra} (e.g. \texttt{kimi-k2.5}), any OpenAI-compatible endpoint. Provides the necessary reasoning capabilities, supporting both local inference and high-performance remote models. + \item \textbf{.NET Service:} C\# / ASP.NET Core / .NET, Roslyn Scripting API, EF Core, NHibernate, Dapper. Provides a dedicated runtime environment for dynamically compiling, validating, and testing generated .NET framework code.\footnote{Possibly not implemented in favour of existing ORMorpher application. See note in Section \ref{subsec:architecture}.} + \item \textbf{Java Service:} Java, Spring Boot, Spring Data Neo4j, Spring Data MongoDB, Maven. Acts as a standalone sandbox for validating and compiling target schema and query code for Java-based NoSQL frameworks. + \item \textbf{Frontend:} ASP.NET Core, Angular / React, Node.js. Retains the existing ORMorpher stack to present a familiar user interface that integrates the new LLM Advisor plugin. + \item \textbf{Databases:} MS SQL Server, MongoDB, Neo4j, PostgreSQL, Redis. Combines source relational and target NoSQL databases for benchmarking, supplemented by PostgreSQL and Redis for orchestrator state persistence and caching. + \item \textbf{ETL:} Bash, MongoDB Relational Migrator, Neo4j ETL Tool + \texttt{neo4j-admin} CLI. Used to map and migrate relational schemas and sample datasets into NoSQL models prior to running translation pipeline. + \item \textbf{Infrastructure:} Docker, Docker Compose. Containerizes all system components to ensure portability, isolated sandboxing for code execution, and streamlined configuration. + \item \textbf{Dev OS:} Linux / Ubuntu 24.04+ or WSL2 on Windows; Windows for .NET. Ensures seamless compatibility with containerized environments and error-free development workflow. +\end{itemize} + +\section{Evaluation} + +For evaluation, we will choose a sample representative ORM schema and queries from the WideWorldImporters dataset\footnote{\url{https://learn.microsoft.com/en-us/sql/samples/wide-world-importers-what-is?view=sql-server-ver17}} (or some other relational dataset), covering various query types (simple selects, joins, aggregations). We will then run the translation pipeline for sample source-target framework pairs (EF Core $\to$ Spring Data MongoDB, EF Core $\to$ Spring Data Neo4j, NHibernate $\to$ Spring Data MongoDB, etc.). Afterwards, the evaluation of the results can be conducted along two dimensions: \emph{translation correctness} and \emph{LLM orchestrator metrics}, although the final realisation may differ depending on the observed data during experimentation. + +\textbf{Translation Correctness:} We will run the pipeline on example source schema/queries and compare generated output with expected target output. As a possible result, evaluation metrics (e.g. accuracy) can be computed from the fraction of translations that compile without errors and return results consistent with the source-framework baseline. + +\textbf{Orchestrator Metrics:} Using Observability platform, we will perform API tracing to obtain metrics, for example, token consumption, tool call counts, exception rates, or retry chain lengths. Optionally, statistical analysis of LLM Advisor performance can be performed using the obtained data points. + +\section{Risks} + +The following section outlines the principal risks associated with the project and presents potential mitigation strategies. These measures should be understood as indicative rather than mandatory in their entirety, as their actual application will depend on the specific issues that arise during the course of the project. + +\begin{itemize} + \item \textbf{LLM Output Quality:} Hallucinated code and framework APIs, knowledge caps, syntactically valid but semantically and logically wrong translations, weak models. + + \emph{Mitigation:} multiple models used, iterative compilation feedback, manual inspection, validation, minimum model quality (Kimi K2.5 / GLM-5 class or better). + \item \textbf{Context Explosion:} Long tool call histories may overflow LLM context windows. + + \emph{Mitigation:} multiple agents/nodes with separate contexts, LangGraph state summarization, configurable context pruning, retry limits. + \item \textbf{Framework/Data Model Heterogeneity:} Structural mismatches (embedded documents vs.\ references, graph traversal vs.\ SQL joins) may prevent exact semantic equivalence. Data model differences may lead to subjectively non-equivalent translations. Mappings may be inefficient, e.g. translating each relational table to a separate document collection, which may lead to suboptimal queries, performance issues, and user dissatisfaction. + + \emph{Mitigation:} careful prompt engineering, iterative refinement, manual user intervention, giving more freedom to LLM (e.g., allowing to explain their reasoning, suggest improvements, etc.), and automatic heuristic-based mappings pre-configured by used ETL tools. + \item \textbf{External Dependencies Maturity:} We will use external dependencies (e.g. MongoDB Relational Migrator, Neo4j ETL Tool) for the ETL process, relatively new MCP servers for agents (databases, docs, validation), CLI tools, Docker images, etc. which may have bugs or limitations. + + \emph{Mitigation:} careful selection, early testing, fallbacks, and clear documentation of used dependencies and their known limitations. + \item \textbf{LLM Provider Availability:} Remote LLM services may have rate limits or downtime. + + \emph{Mitigation:} two default LLM providers configured, with custom configurable OpenAI-compatible endpoint option. + \item \textbf{Security:} Executing generated code in sandboxed environments poses security risks. + + \emph{Mitigation:} strict resource limits, isolation, and monitoring of execution environments. +\end{itemize} + +\section{Milestones / Deliverables} + +\begin{itemize} + \item \textbf{D1 --- Architecture \& Design:} Design Docker Compose stack, networking, automation using scripts, design ETL processes -- all three databases initialized (MS SQL Server, MongoDB, Neo4j) with WideWorldImporters dataset in different data models with sample mappings. + \item \textbf{D2 --- Core Translation Pipeline:} LangGraph graph (extracting input, translation, validation) operational, including LLM providers, relevant tools/MCP servers, and external dependencies integrated. + \item \textbf{D4 --- UI Plugin:} LLM Advisor UI widget for translation between ORM/ODM/OGM frameworks. + \item \textbf{D3 --- Full E2E Pipeline:} User interaction flow: ETL, UI plugin interaction (setup, input), sample ORM $\rightarrow$ ODM/OGM translation, Advisor UI output. At least one end-to-end translation (EF Core $\to$ Spring Data Neo4j) verified by testing. + \item \textbf{D5 --- Evaluation:} Correctness metrics for sample framework pairs, observable LangGraph trace analysis, and potential conference demo paper. + \item \textbf{D6 --- Documentation:} User and programmer documentation, setup guide. +\end{itemize} + +A live demo will be presented during defense using local development setup. Source code will be available at \url{https://github.com/corovcam/Universal-Object-Mapping}. + +\section{Time Schedule} + +Although the project timeline is intentionally structured in weeks rather than months, the overall effort is estimated at approximately 320 hours (around 40 person-days). The work is being pursued in parallel with the solver’s diploma thesis, with the project concentrating mainly on the software implementation and experimental realization, while the diploma thesis addresses the associated theoretical foundations, methodology, and scholarly interpretation of the results. The Table \ref{tab:time-shedule} below presents the detailed milestones and time schedule. + +\begin{table}[H] +\centering +\begin{tabular}{ | m{10em} | m{23em} | m{3.5cm} | m{1.3cm} | } +\hline +Milestone & Activity & Time schedule & Man-days \\ +\hline +D1: Architecture \& Design & Design databases, Docker Compose, ETL, UI & Week 1--2 & 4 \\ + & Infrastructure setup, initialization, refinement & Weeks 1--2 & 3 \\ +\hline +D2: Core Translation Pipeline & Graph design, tools selection, initial setup & Weeks 2--3 & 4 \\ + & Graph setup, LLM/tools integration, testing & Weeks 3--5 & 9 \\ +\hline +D3: UI Plugin & UI package development & Weeks 5--6 & 4 \\ + & Integration & Weeks 5--6 & 2 \\ +\hline +D4: Full E2E Pipeline & Implement and polish full end-to-end user interaction flow & Weeks 6--8 & 8 \\ +\hline +D5: Evaluation & Correctness evaluation, tracing analysis & Weeks 8--9 & 4 \\ +\hline +D6: Documentation & User/programmer docs, deployment notes & Week 9 & 2 \\ +\hline +\end{tabular} +\label{tab:time-shedule} +\end{table} + +\section{Form of Collaboration} + +The project will be supervised by \textit{Pavel Koupil}, with whom the solver will regularly discuss the research framing, methodological choices, overall conceptualization of the proposed solution, and the formulation of outputs. He will also provide continuous feedback on the technical direction of the project, the evaluation design, and the interpretation of the obtained results. Close collaboration is also planned with \textit{Irena Holubová}, who will provide strategic oversight and ensure alignment of the project with the broader research agenda of the Adaptive Data Management Research Group (ADaM). In addition, other experts from the ADaM research group will contribute domain-specific consultation as needed, particularly in areas related to multi-model data management, framework interoperability, and the design of the translation pipeline. + +\textit{Martin Čorovčák}, as the solver, will be fully responsible for the implementation of the proposed solution and will also take primary responsibility for its practical realization, including integration, experimentation, and technical validation. + +\subsection{Consulting Plan} + +An initial meeting with \textit{Pavel Koupil} and \textit{Irena Holubová} has already taken place to define the project scope, clarify its objectives, and establish the overall collaboration framework. These discussions focused on the intended research direction, expected outcomes, and the broader positioning of the project within the ADaM research agenda. + +Following this, regular meetings with \textit{Pavel Koupil} will be held to monitor progress, discuss encountered challenges, refine the methodological and technical direction, and determine the next steps in the project. These meetings will also serve as a space for continuous feedback on implementation decisions, evaluation design, and the interpretation of intermediate results. + +Most meetings will take place either in person at the faculty building or online, depending on current needs and availability. They are expected to be held approximately once or twice a week and to last around sixty minutes. In addition, ad-hoc consultations will be arranged whenever necessary to address specific technical issues, discuss partial outputs, or obtain timely feedback on important decisions. + + +\subsection{Collaboration with Other Team Members} + +The solver will cooperate with several experts whose knowledge covers the main research dimensions of the project. In the area of relational databases, data modeling, and query processing, the primary consultations will be conducted with \textit{Pavel Koupil} and \textit{Irena Holubová}. Their expertise will be particularly relevant for issues related to schema interpretation, preservation of query semantics, and the database foundations of migration between mapping frameworks. Where appropriate, additional consultation may also be provided by \textit{Michal Kopecký} on selected database-related topics. +In the area of LLMs, NLP, and AI, the solver may consult researchers such as \textit{Roman Barták}, particularly with regard to the design of the LLM-based translation process, prompting strategies, agent reasoning, and validation of generated outputs. Such consultations will support the methodological quality of the proposed approach and help align it with current developments in AI-assisted software engineering. +Additional feedback may also be obtained from software engineering and database colleagues within the Adaptive Data Management Research Group (ADaM), especially in matters concerning implementation, integration, and evaluation. This collaborative background will contribute to both the quality of the resulting solution and its consistency with the broader research agenda of the group. + +\begin{thebibliography}{99} + +\bibitem{mmcat} +KOUPIL, Pavel, SVOBODA, Martin and HOLUBOVÁ, Irena, 2021. MM-cat: A Tool for Modeling and Transformation of Multi-Model Data using Category Theory. In: 2021 ACM/IEEE International Conference on Model Driven Engineering Languages and Systems Companion (MODELS-C). Online. October 2021. p.635–639. DOI 10.1109/MODELS-C53483.2021.00098. Available from: \url{https://doi.org/10.1109/MODELS-C53483.2021.00098} [Accessed 1 April 2026]. + +\bibitem{ormorpherthesis} +ABRAHÁM, Milan, 2025. Framework-Agnostic Query Adaptation: Ensuring SQL Compatibility Across .NET Database Frameworks. Online. 9 September 2025. Available from: \url{https://dspace.cuni.cz/handle/20.500.11956/203083} [Accessed 31 March 2026]. + +\bibitem{ormorpherrepo} +ORMConvertor repository. GitHub. Online. Available from: \url{https://github.com/milan252525/orm-convertor/tree/main/ORMConvertor} [Accessed 31 March 2026]. + +\bibitem{ase2025} +ABRAHÁM, Milan and KOUPIL, Pavel, 2026. ORMorpher: An Interactive Framework for ORM Translation and Optimization. In: 2025 40th IEEE/ACM International Conference on Automated Software Engineering (ASE). Online. Seoul, Korea, Republic of: IEEE Press. 28 January 2026. p. 4037–4040. DOI 10.1109/ASE63991.2025.00369. Available from: \url{https://doi.org/10.1109/ASE63991.2025.00369} [Accessed 31 March 2026]. + +\bibitem{uomrepo} +ČOROVČÁK, Martin, 2026. Universal Object Mapping repository [software]. Online. 8 March 2026. Available from: \url{https://github.com/corovcam/Universal-Object-Mapping} [Accessed 31 March 2026]. + +\bibitem{xrg-mff} +XRG - XML and Web Engineering Research Group at KSI MFF UK. Online. Available from: \url{https://xrg.cz/} [Accessed 1 April 2026]. + +\bibitem{ollama} +Ollama [software]. Online. 1 April 2026. Ollama. Available from: \url{https://github.com/ollama/ollama} [Accessed 1 April 2026]. + + +\bibitem{einfra} +e-INFRA CZ AIaaS Documentation. Online. Available from: \url{https://docs.cerit.io/en/docs/ai-as-a-service/introduction} [Accessed 31 March 2026]. + +\end{thebibliography} + +\end{document} diff --git a/services/daytona/scripts/daytona-reset-proxy-dns.ps1 b/services/daytona/scripts/daytona-reset-proxy-dns.ps1 new file mode 100644 index 0000000..e8311ae --- /dev/null +++ b/services/daytona/scripts/daytona-reset-proxy-dns.ps1 @@ -0,0 +1,19 @@ +# 1. Ensure the script is running as Administrator +if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Error "This script requires Administrator privileges." + exit 1 +} + +# 2. Get active internet-connected adapters +$ActiveAdapters = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null } + +Write-Host "`n=== RESTORING ORIGINAL DNS (DHCP) ===" -ForegroundColor Cyan + +# 3. Restore DNS to Automatic (DHCP) +foreach ($Adapter in $ActiveAdapters) { + Write-Host "Resetting $($Adapter.InterfaceAlias) to Automatic DNS..." + # -ResetServerAddresses removes the static IPs and defaults back to DHCP + Set-DnsClientServerAddress -InterfaceIndex $Adapter.InterfaceIndex -ResetServerAddresses +} + +Write-Host "Done. Your system is back to normal." -ForegroundColor Green diff --git a/services/daytona/scripts/daytona-setup-proxy-dns.ps1 b/services/daytona/scripts/daytona-setup-proxy-dns.ps1 new file mode 100644 index 0000000..93069f7 --- /dev/null +++ b/services/daytona/scripts/daytona-setup-proxy-dns.ps1 @@ -0,0 +1,71 @@ +# Copyright 2025 Daytona Platforms Inc. +# SPDX-License-Identifier: AGPL-3.0 + +<# +.SYNOPSIS +Setup wildcard DNS for *.proxy.localhost -> 127.0.0.1 on Windows. +Requires Acrylic DNS Proxy (installed automatically via winget if missing). +#> + +# 1. Ensure the script is running as Administrator +if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Error "This script requires Administrator privileges. Please run PowerShell as Admin." + exit 1 +} + +# Define paths early so we can check if it's already installed +$AcrylicDir = "${env:ProgramFiles(x86)}\Acrylic DNS Proxy" +$AcrylicHostsPath = "$AcrylicDir\AcrylicHosts.txt" + +# 2. Check and Install Acrylic DNS Proxy +if (-not (Test-Path $AcrylicDir)) { + Write-Host "Installing Acrylic DNS Proxy via Winget (Windows equivalent to dnsmasq)..." + winget install Mayakron.AcrylicDNS --accept-package-agreements --accept-source-agreements --silent + + # Give the installer a moment to flush files to disk + Start-Sleep -Seconds 5 +} else { + Write-Host "Acrylic DNS Proxy is already installed. Skipping installation." -ForegroundColor Cyan +} + +# Verify installation was successful / exists +if (Test-Path $AcrylicHostsPath) { + # 3. Configure the Wildcard + Write-Host "Configuring wildcard DNS (*.proxy.localhost -> 127.0.0.1)..." + $DnsEntry = "127.0.0.1 *.proxy.localhost" + + # Read current hosts to avoid appending duplicate entries on multiple runs + $CurrentHosts = Get-Content -Path $AcrylicHostsPath -Raw + if ($CurrentHosts -notmatch [regex]::Escape($DnsEntry)) { + # Append to Acrylic's custom hosts file + Add-Content -Path $AcrylicHostsPath -Value $DnsEntry + Write-Host " -> Added wildcard entry to AcrylicHosts.txt" + } else { + Write-Host " -> Wildcard entry already exists. Skipping." + } + + # 4. Restart the Service + Write-Host "Restarting Acrylic DNS Proxy service..." + Restart-Service -Name "AcrylicDNSProxySvc" -ErrorAction Stop + + # 5. Set Windows to use the local DNS server for BOTH IPv4 and IPv6 + Write-Host "Configuring active network adapters to use local DNS..." + + # Fetch only adapters actively connected to the internet (have a gateway) + $ActiveConfigs = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null } + + foreach ($Config in $ActiveConfigs) { + Write-Host " -> Setting DNS for adapter: $($Config.InterfaceAlias)" + + # Windows will automatically assign IPv4 strings to IPv4 and IPv6 strings to IPv6 + $DnsServers = @("127.0.0.1", "::1", "8.8.8.8", "2001:4860:4860::8888") + + Set-DnsClientServerAddress -InterfaceIndex $Config.InterfaceIndex -ServerAddresses $DnsServers + } + + Write-Host "`nDone. Test with:" + Write-Host "Resolve-DnsName 2280-test.proxy.localhost" -ForegroundColor Green +} else { + Write-Error "Failed to locate AcrylicHosts.txt at $AcrylicHostsPath. Installation may have failed." + exit 1 +} \ No newline at end of file diff --git a/services/daytona/scripts/daytona-setup-proxy-dns.sh b/services/daytona/scripts/daytona-setup-proxy-dns.sh new file mode 100755 index 0000000..72eff85 --- /dev/null +++ b/services/daytona/scripts/daytona-setup-proxy-dns.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2025 Daytona Platforms Inc. +# SPDX-License-Identifier: AGPL-3.0 + +# Setup DNS for *.proxy.localhost -> 127.0.0.1 + +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + command -v dnsmasq >/dev/null || brew install dnsmasq + sudo mkdir -p /usr/local/etc /etc/resolver + echo "address=/proxy.localhost/127.0.0.1" | sudo tee -a /usr/local/etc/dnsmasq.conf + echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/proxy.localhost + brew services start dnsmasq +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + command -v dnsmasq >/dev/null || { sudo apt update && sudo apt install -y dnsmasq; } + echo "address=/proxy.localhost/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf + sudo systemctl restart dnsmasq && sudo systemctl enable dnsmasq + echo -e "nameserver 127.0.0.1\nnameserver 8.8.8.8" | sudo tee /etc/resolv.conf +else + echo "Unsupported OS: $OSTYPE" && exit 1 +fi + +echo "Done. Test: dig 2280-test.proxy.localhost" \ No newline at end of file diff --git a/services/dotnet-service/.dockerignore b/services/dotnet-service/.dockerignore new file mode 100644 index 0000000..3f706ed --- /dev/null +++ b/services/dotnet-service/.dockerignore @@ -0,0 +1,27 @@ +bin +obj +TestResults +node_modules +**/node_modules +*.user +*.suo +*.vs +.vscode +*.dbmdl +*.db +*.sdf +*.sqlite +*.mdf +*.ldf +**/dist +**/bin +**/obj +logs +*.log +npm-debug.log +/.env +/.idea +.git +.gitignore +.dockerignore +Dockerfile diff --git a/services/dotnet-service/.editorconfig b/services/dotnet-service/.editorconfig new file mode 100644 index 0000000..fed0bf8 --- /dev/null +++ b/services/dotnet-service/.editorconfig @@ -0,0 +1,262 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Code Actions #### + +# Type members +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Symbol search +dotnet_search_reference_assemblies = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent \ No newline at end of file diff --git a/services/dotnet-service/.envrc b/services/dotnet-service/.envrc new file mode 100644 index 0000000..3d9d49b --- /dev/null +++ b/services/dotnet-service/.envrc @@ -0,0 +1,4 @@ +# shellcheck disable=SC2148 +[[ -f ~/.envrc.local ]] && source_env ~/.envrc.local + +dotenv_if_exists .env.dev diff --git a/services/dotnet-service/.gitattributes b/services/dotnet-service/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/services/dotnet-service/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/services/dotnet-service/.gitignore b/services/dotnet-service/.gitignore new file mode 100644 index 0000000..cf5fba3 --- /dev/null +++ b/services/dotnet-service/.gitignore @@ -0,0 +1,496 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp + + + + +# Custom +*.dll +.vs/ + +.env +.env.dev +sandbox/ +results/ diff --git a/services/dotnet-service/Controllers/CompilerController.cs b/services/dotnet-service/Controllers/CompilerController.cs new file mode 100644 index 0000000..752d204 --- /dev/null +++ b/services/dotnet-service/Controllers/CompilerController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace DotNetService.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CompilerController : ControllerBase +{ + private readonly ILogger _logger; + + public CompilerController(ILogger logger) + { + _logger = logger; + } +} diff --git a/services/dotnet-service/Dockerfile b/services/dotnet-service/Dockerfile new file mode 100644 index 0000000..d49d9eb --- /dev/null +++ b/services/dotnet-service/Dockerfile @@ -0,0 +1,24 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 + +WORKDIR /sandbox + +RUN apt-get update && \ + apt-get install -y --no-install-recommends openssh-server && \ + mkdir -p /var/run/sshd && \ + useradd -d /sandbox -s /bin/rbash sandbox && \ + echo 'sandbox:sandbox' | chpasswd && \ + echo 'root:root' | chpasswd && \ + sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \ + echo 'Match User sandbox' >> /etc/ssh/sshd_config && \ + echo ' ChrootDirectory none' >> /etc/ssh/sshd_config && \ + rm -rf /var/lib/apt/lists/* + +COPY ./sandbox.csproj . + +# Restore dependencies for dynamic compilation +RUN chmod 711 /sandbox && dotnet restore + +RUN chown -R sandbox:sandbox /sandbox + +EXPOSE 22 +CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"] diff --git a/services/dotnet-service/Program.cs b/services/dotnet-service/Program.cs new file mode 100644 index 0000000..cf22ab0 --- /dev/null +++ b/services/dotnet-service/Program.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi +builder.Services.AddOpenApi(); +builder.Services + .AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); +builder.Services.AddEndpointsApiExplorer(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/openapi/v1.json", "v1"); + }); +} + +app.UseHttpsRedirection(); +app.UseAuthorization(); +app.MapControllers(); + +app.Run(); diff --git a/services/dotnet-service/Properties/launchSettings.json b/services/dotnet-service/Properties/launchSettings.json new file mode 100644 index 0000000..1a67dcb --- /dev/null +++ b/services/dotnet-service/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://www.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5073", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7189;http://localhost:5073", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/services/dotnet-service/appsettings.Development.json b/services/dotnet-service/appsettings.Development.json new file mode 100644 index 0000000..a121808 --- /dev/null +++ b/services/dotnet-service/appsettings.Development.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug" + } + }, + "ConnectionStrings": { + "SqlServer": "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + }, + "Sandbox": { + "BaseDir": "./sandbox", + "TimeoutSeconds": 120 + } +} \ No newline at end of file diff --git a/services/dotnet-service/appsettings.json b/services/dotnet-service/appsettings.json new file mode 100644 index 0000000..48b1196 --- /dev/null +++ b/services/dotnet-service/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "SqlServer": "Server=mssql_db;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + }, + "Sandbox": { + "BaseDir": "/src/sandbox", + "TimeoutSeconds": 60 + } +} \ No newline at end of file diff --git a/services/dotnet-service/dapper-sandbox/DapperQueryEntrypoint.cs b/services/dotnet-service/dapper-sandbox/DapperQueryEntrypoint.cs new file mode 100644 index 0000000..fa95bd7 --- /dev/null +++ b/services/dotnet-service/dapper-sandbox/DapperQueryEntrypoint.cs @@ -0,0 +1,380 @@ +using System; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using System.Collections.Generic; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace dapper_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +public class Customer +{ + public required int CustomerID { get; set; } + public required string CustomerName { get; set; } + public required DateTime AccountOpenedDate { get; set; } + public decimal? CreditLimit { get; set; } + public List CustomerTransactions { get; set; } = []; +} + +public class CustomerTransaction +{ + public int CustomerTransactionID { get; set; } + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +public class Order +{ + public int OrderID { get; set; } + public int CustomerID { get; set; } + public Customer Customer { get; set; } = null!; + public List OrderLines { get; set; } = []; +} + +public class OrderLine +{ + public int OrderLineID { get; set; } + public int OrderID { get; set; } + public required string Description { get; set; } + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +// --- Queries --- + +public static class Query1 +{ + public static IEnumerable Query(SqlConnection conn) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + string sql = @"SELECT * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To"; + return conn.Query(sql, new { From = from, To = to }); + } + + public static object Harness(SqlConnection conn) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + var sql = @" + SELECT TOP 1 * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To ORDER BY OrderLineID ASC; + SELECT TOP 1 * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To ORDER BY OrderLineID DESC; + SELECT COUNT(*) FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To;"; + using var multi = conn.QueryMultiple(sql, new { From = from, To = to }); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +public static class Query2 +{ + static Order Query2mapRow(Order o, Customer c, CustomerTransaction t, OrderLine ol) + { + o.Customer = c; + o.OrderLines.Add(ol); + if (c != null) { + if (t != null) + { + c.CustomerTransactions.Add(t); + } + } + return o; + } + + public static IEnumerable Query(SqlConnection conn) + { + string sql = @" + SELECT o.*, c.*, t.*, ol.* + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.CustomerID = 1"; + var duplicates = conn.Query( + sql, Query2mapRow, splitOn: "CustomerID,CustomerTransactionID,OrderLineID"); + var result = duplicates.GroupBy(o => o.OrderID).Select(g => { + var order = g.First(); + order.OrderLines = g.SelectMany(o => o.OrderLines).Where(ol => ol != null).GroupBy(ol => ol.OrderLineID).Select(g2 => g2.First()).ToList(); + order.Customer?.CustomerTransactions = g.SelectMany(o => o.Customer.CustomerTransactions).Where(t => t != null).GroupBy(t => t.CustomerTransactionID).Select(g2 => g2.First()).ToList(); + return order; + }); + return result; + } + + public static object Harness(SqlConnection conn) + { + string sql = @" + SELECT TOP 1 o.*, c.*, t.*, ol.* + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.CustomerID = 1 + ORDER BY o.OrderID ASC; + SELECT TOP 1 o.*, c.*, t.*, ol.* + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.CustomerID = 1 + ORDER BY o.OrderID DESC; + SELECT COUNT(*) + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.OrderID = 1;"; + using var multi = conn.QueryMultiple(sql); + var firstSample = multi.Read(Query2mapRow, splitOn: "CustomerID,CustomerTransactionID,OrderLineID").FirstOrDefault(); + var lastSample = multi.Read(Query2mapRow, splitOn: "CustomerID,CustomerTransactionID,OrderLineID").FirstOrDefault(); + var rowCount = multi.ReadSingle(); + return new { firstSample, lastSample, count = rowCount }; + } +} + +public static class Query3 +{ + public static IEnumerable<(decimal TaxRate, int Count)> Query(SqlConnection conn) + { + string sql = @"SELECT TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate ORDER BY Count DESC"; + return conn.Query<(decimal TaxRate, int Count)>(sql); + } + + public static object Harness(SqlConnection conn) + { + var sql = @"SELECT TOP 1 TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate ORDER BY Count DESC; + SELECT TOP 1 TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate ORDER BY Count ASC; + SELECT COUNT(*) FROM (SELECT TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate) AS SubQuery;"; + using var multi = conn.QueryMultiple(sql); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +public static class Query4 +{ + public static IEnumerable Query(SqlConnection conn) + { + string sql = @"SELECT TOP 50 * FROM Sales.OrderLines ORDER BY Quantity DESC"; + return conn.Query(sql); + } + + public static object Harness(SqlConnection conn) + { + var sql = @"SELECT TOP 1 * FROM Sales.OrderLines ORDER BY Quantity DESC; + SELECT TOP 1 * FROM Sales.OrderLines ORDER BY Quantity ASC; + SELECT COUNT(*) FROM (SELECT TOP 50 * FROM Sales.OrderLines ORDER BY Quantity DESC) AS SubQuery;"; + using var multi = conn.QueryMultiple(sql); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +public static class Query5 +{ + public static IEnumerable<(int OrderLineID, int Quantity)> Query(SqlConnection conn) + { + string sql = @"SELECT OrderLineID, Quantity FROM Sales.OrderLines"; + return conn.Query<(int OrderLineID, int Quantity)>(sql); + } + + public static object Harness(SqlConnection conn) + { + var sql = @" + SELECT TOP 1 OrderLineID, Quantity FROM Sales.OrderLines ORDER BY OrderLineID ASC; + SELECT TOP 1 OrderLineID, Quantity FROM Sales.OrderLines ORDER BY OrderLineID DESC; + SELECT COUNT(*) FROM Sales.OrderLines;"; + using var multi = conn.QueryMultiple(sql); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +// --- Query Entrypoint --- + +public static class DapperQueryEntrypoint +{ + + public static void ValidateEntity(SqlConnection conn, string schema, string table) + { + Console.WriteLine($"Validating Dapper entity: {typeof(T).Name}"); + var sql = $"SELECT TOP 1 * FROM {schema}.{table}"; + Console.WriteLine(CustomJsonSerializer.Serialize(conn.QueryFirstOrDefault(sql))); + Console.WriteLine($"Successfully validated Dapper entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + string connectionString = args.ElementAtOrDefault(0) + ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + using var conn = new SqlConnection(connectionString); + conn.Open(); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + ValidateEntity(conn, "Sales", "Customers"); + ValidateEntity(conn, "Sales", "CustomerTransactions"); + ValidateEntity(conn, "Sales", "Orders"); + ValidateEntity(conn, "Sales", "OrderLines"); + + // Now create and execute the queries and capture results + var results = new Dictionary(); + var harnesses = new Func[] { + () => Query1.Harness(conn), + () => Query2.Harness(conn), + () => Query3.Harness(conn), + () => Query4.Harness(conn), + () => Query5.Harness(conn) + }; + + for (int i = 0; i < harnesses.Length; i++) { + var qid = i+1; + Console.WriteLine($"Running Query {qid}..."); + try { + results[$"query{qid}"] = harnesses[i](); + Console.WriteLine($"Successfully ran Query {qid}"); + } catch (Exception ex) { + results[$"query{qid}"] = new { error = ex.Message }; + Console.Error.WriteLine($"Error occurred while running Query{qid}: {ex}"); + } + } + + var resultsPath = Environment.GetEnvironmentVariable("DAPPER_RESULTS_PATH"); + if (!string.IsNullOrEmpty(resultsPath)) + { + System.IO.File.WriteAllText($"{resultsPath}/dapper_results_{DateTime.Now:yyyyMMdd_HHmmss}.json", CustomJsonSerializer.Serialize(results)); + } + else + { + Console.WriteLine(CustomJsonSerializer.Serialize(results)); + } + } +} diff --git a/services/dotnet-service/dapper-sandbox/DapperSchemaValidationEntrypoint.cs b/services/dotnet-service/dapper-sandbox/DapperSchemaValidationEntrypoint.cs new file mode 100644 index 0000000..6b39422 --- /dev/null +++ b/services/dotnet-service/dapper-sandbox/DapperSchemaValidationEntrypoint.cs @@ -0,0 +1,215 @@ +using System; +using System.Linq; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Collections.Generic; +using Dapper; +using Microsoft.Data.SqlClient; +using System.Text.Json.Serialization.Metadata; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Globalization; +using System.Text.Encodings.Web; + +namespace dapper_sandbox; + +// --- Schema and Related Settings --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +[Table("Customers", Schema = "Sales")] +public class Customer +{ + [Key] + public int CustomerID { get; set; } + public string CustomerName { get; set; } = string.Empty; + public DateTime AccountOpenedDate { get; set; } + public decimal? CreditLimit { get; set; } +} + +[Table("CustomerTransactions", Schema = "Sales")] +public class CustomerTransaction +{ + [Key] + public int CustomerTransactionID { get; set; } + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +[Table("Orders", Schema = "Sales")] +public class Order +{ + [Key] + public int OrderID { get; set; } + public int CustomerID { get; set; } +} + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{ + [Key] + public int OrderLineID { get; set; } + public int OrderID { get; set; } + public int StockItemID { get; set; } + public string Description { get; set; } = string.Empty; + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +// --- Schema Validation Entrypoint --- + +public static class DapperSchemaValidationEntrypoint +{ + public static void ValidateEntity(SqlConnection conn, string schema, string table) + { + Console.WriteLine($"Validating Dapper entity: {typeof(T).Name}"); + var sql = $"SELECT TOP 1 * FROM {schema}.{table}"; + Console.WriteLine(CustomJsonSerializer.Serialize(conn.QueryFirstOrDefault(sql))); + Console.WriteLine($"Successfully validated Dapper entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + var connectionString = args.ElementAtOrDefault(0) ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + using var conn = new SqlConnection(connectionString); + conn.Open(); + + var queries = new Action[] { + () => ValidateEntity(conn, "Sales", "Customers"), + () => ValidateEntity(conn, "Sales", "CustomerTransactions"), + () => ValidateEntity(conn, "Sales", "Orders"), + () => ValidateEntity(conn, "Sales", "OrderLines") + }; + + foreach (var query in queries) { + try { + query(); + } catch (Exception ex) { + Console.Error.WriteLine($"Error occurred while validating entity: {ex}"); + } + } + } +} diff --git a/services/dotnet-service/dapper-sandbox/Properties/launchSettings.json b/services/dotnet-service/dapper-sandbox/Properties/launchSettings.json new file mode 100644 index 0000000..ee354ac --- /dev/null +++ b/services/dotnet-service/dapper-sandbox/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://www.schemastore.org/launchsettings.json", + "profiles": { + "default": { + "commandName": "Project", + "environmentVariables": { + "CONNECTION_STRING": "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True", + "DAPPER_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/dapper-sandbox/results" + }, + "sqlDebugging": true + }, + } +} diff --git a/services/dotnet-service/dapper-sandbox/dapper-sandbox.csproj b/services/dotnet-service/dapper-sandbox/dapper-sandbox.csproj new file mode 100644 index 0000000..30bfae4 --- /dev/null +++ b/services/dotnet-service/dapper-sandbox/dapper-sandbox.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + dapper_sandbox + enable + enable + + + + + + diff --git a/services/dotnet-service/dotnet-service.csproj b/services/dotnet-service/dotnet-service.csproj new file mode 100644 index 0000000..51fab8d --- /dev/null +++ b/services/dotnet-service/dotnet-service.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + dotnet_service + + + + + + + + + + + + + + + + + + diff --git a/services/dotnet-service/dotnet-service.http b/services/dotnet-service/dotnet-service.http new file mode 100644 index 0000000..3c06ad9 --- /dev/null +++ b/services/dotnet-service/dotnet-service.http @@ -0,0 +1,10 @@ +@dotnet_service_HostAddress = http://localhost:5073 + +POST {{dotnet_service_HostAddress}}/api/compiler/compile +Content-Type: application/json + +{ + "sourceCode": "using System;\npublic class Test { public void Run() { Console.WriteLine(\"Hello\"); } }" +} + +### diff --git a/services/dotnet-service/dotnet-service.sln b/services/dotnet-service/dotnet-service.sln new file mode 100644 index 0000000..f189c5e --- /dev/null +++ b/services/dotnet-service/dotnet-service.sln @@ -0,0 +1,79 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.2.11430.68 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-service", "dotnet-service.csproj", "{65A88C51-5FFE-28A8-E760-38DE69B59A5C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "efcore-sandbox", "efcore-sandbox\efcore-sandbox.csproj", "{A9E2C0EE-31DB-4ACD-9AF0-63707458F478}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dapper-sandbox", "dapper-sandbox\dapper-sandbox.csproj", "{42E3C970-AB10-4D94-949E-80A8247522D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nhibernate-sandbox", "nhibernate-sandbox\nhibernate-sandbox.csproj", "{73426BB3-7BCB-4C08-B9E7-EC551A3681DB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Debug|x64.ActiveCfg = Debug|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Debug|x64.Build.0 = Debug|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Debug|x86.ActiveCfg = Debug|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Debug|x86.Build.0 = Debug|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Release|Any CPU.Build.0 = Release|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Release|x64.ActiveCfg = Release|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Release|x64.Build.0 = Release|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Release|x86.ActiveCfg = Release|Any CPU + {65A88C51-5FFE-28A8-E760-38DE69B59A5C}.Release|x86.Build.0 = Release|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Debug|x64.ActiveCfg = Debug|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Debug|x64.Build.0 = Debug|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Debug|x86.ActiveCfg = Debug|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Debug|x86.Build.0 = Debug|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Release|Any CPU.Build.0 = Release|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Release|x64.ActiveCfg = Release|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Release|x64.Build.0 = Release|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Release|x86.ActiveCfg = Release|Any CPU + {A9E2C0EE-31DB-4ACD-9AF0-63707458F478}.Release|x86.Build.0 = Release|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Debug|x64.ActiveCfg = Debug|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Debug|x64.Build.0 = Debug|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Debug|x86.ActiveCfg = Debug|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Debug|x86.Build.0 = Debug|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Release|Any CPU.Build.0 = Release|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Release|x64.ActiveCfg = Release|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Release|x64.Build.0 = Release|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Release|x86.ActiveCfg = Release|Any CPU + {42E3C970-AB10-4D94-949E-80A8247522D8}.Release|x86.Build.0 = Release|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Debug|x64.ActiveCfg = Debug|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Debug|x64.Build.0 = Debug|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Debug|x86.ActiveCfg = Debug|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Debug|x86.Build.0 = Debug|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Release|Any CPU.Build.0 = Release|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Release|x64.ActiveCfg = Release|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Release|x64.Build.0 = Release|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Release|x86.ActiveCfg = Release|Any CPU + {73426BB3-7BCB-4C08-B9E7-EC551A3681DB}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B599739E-F113-4971-B505-0ED7F9B137AF} + EndGlobalSection +EndGlobal diff --git a/services/dotnet-service/efcore-sandbox/EFCoreQueryEntrypoint.cs b/services/dotnet-service/efcore-sandbox/EFCoreQueryEntrypoint.cs new file mode 100644 index 0000000..311331c --- /dev/null +++ b/services/dotnet-service/efcore-sandbox/EFCoreQueryEntrypoint.cs @@ -0,0 +1,305 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace efcore_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +[Table("Customers", Schema = "Sales")] +public class Customer +{ + [Key] + [JsonPropertyName("customerId")] + public required int CustomerID { get; set; } + [MaxLength(200)] + public required string CustomerName { get; set; } + [Column(TypeName="datetime2")] + [Precision(7)] + public required DateTime AccountOpenedDate { get; set; } + [Column(TypeName="decimal")] + [Precision(18, 2)] + public decimal? CreditLimit { get; set; } + + public List CustomerTransactions { get; set; } = []; +} + +[Table("CustomerTransactions", Schema = "Sales")] +public class CustomerTransaction +{ + [Key] + [JsonPropertyName("customerTransactionId")] + public int CustomerTransactionID { get; set; } + [ForeignKey(nameof(Customer))] + [JsonPropertyName("customerId")] + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +[Table("Orders", Schema = "Sales")] +public class Order +{ + [Key] + [JsonPropertyName("orderId")] + public int OrderID { get; set; } + [ForeignKey(nameof(Customer))] + [JsonPropertyName("customerId")] + public int CustomerID { get; set; } + + public Customer Customer { get; set; } = null!; + public List OrderLines { get; set; } = []; +} + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{ + [Key] + [JsonPropertyName("orderLineId")] + public int OrderLineID { get; set; } + [ForeignKey(nameof(Order))] + [JsonPropertyName("orderId")] + public int OrderID { get; set; } + [JsonPropertyName("stockItemId")] + public int StockItemID { get; set; } + public required string Description { get; set; } + [JsonPropertyName("packageTypeId")] + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +public class SandboxDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Customers => Set(); + public DbSet Orders => Set(); + public DbSet CustomerTransactions => Set(); + public DbSet OrderLines => Set(); +} + +// --- Query Entrypoint --- + +public static class EFCoreQueryEntrypoint +{ + public static IQueryable Query1(SandboxDbContext ctx) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + return ctx.OrderLines.Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to); + } + + public static IQueryable Query2(SandboxDbContext ctx) + { + return ctx.Orders + .Include(o => o.Customer) + .ThenInclude(c => c.CustomerTransactions) + .Include(o => o.OrderLines) + .AsSplitQuery() + .Where(c => c.CustomerID == 1); + } + + public static IQueryable Query3(SandboxDbContext ctx) + { + return ctx.OrderLines.GroupBy(ol => ol.TaxRate).Select(g => new { TaxRate = g.Key, Count = g.Count() }).OrderByDescending(x => x.Count); + } + + public static IQueryable Query4(SandboxDbContext ctx) + { + return ctx.OrderLines.OrderByDescending(ol => ol.Quantity).Take(50); + } + + public static IQueryable Query5(SandboxDbContext ctx) + { + return ctx.OrderLines.Select(ol => new { ol.OrderLineID, ol.Quantity }); + } + + private static object RunQuery(Func> q, Func? orderBySelector = null) + { + var query = q(); + var count = query.Count(); + return new { sqlString = query.ToQueryString(), count, + firstSample = count > 0 ? (orderBySelector != null ? q().OrderBy(orderBySelector).FirstOrDefault() : query.FirstOrDefault()) : default, + lastSample = count > 1 ? (orderBySelector != null ? q().OrderByDescending(orderBySelector).FirstOrDefault() : query.LastOrDefault()) : default }; + } + + public static void ValidateEntity(SandboxDbContext ctx) where T : class + { + Console.WriteLine($"Validating EF Core entity: {typeof(T).Name}"); + Console.WriteLine(CustomJsonSerializer.Serialize(ctx.Set().FirstOrDefault())); + Console.WriteLine($"Successfully validated EF Core entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + using var context = new SandboxDbContext( + new DbContextOptionsBuilder() + .UseSqlServer( + args.ElementAtOrDefault(0) ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + ) + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .EnableSensitiveDataLogging() + .LogTo(Console.WriteLine, [DbLoggerCategory.Database.Command.Name], minimumLevel: LogLevel.Information, options: DbContextLoggerOptions.SingleLine).Options + ); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + ValidateEntity(context); + ValidateEntity(context); + ValidateEntity(context); + ValidateEntity(context); + + // Now create and execute the queries and capture results + var results = new Dictionary(); + var harnesses = new Func[] { + () => RunQuery(() => Query1(context), ol => ol.OrderLineID), + () => RunQuery(() => Query2(context), o => o.OrderID), + () => RunQuery(() => Query3(context), x => x.TaxRate), // sorting by TaxRate, since it's unique in a groupBy result + () => RunQuery(() => Query4(context)), // sorting by non-unique column with limiting (.Take) is present; top results may not be consistent accross DBMSs; sorting by unique-column is not possible + () => RunQuery(() => Query5(context), ol => ol.OrderLineID) + }; + + for (int i = 0; i < harnesses.Length; i++) { + var qid = i+1; + Console.WriteLine($"Running Query {qid}..."); + try { + results[$"query{qid}"] = harnesses[i](); + } catch (Exception ex) { + results[$"query{qid}"] = new { error = ex.Message }; + Console.Error.WriteLine($"Error occurred while running Query{qid}: {ex}"); + } + } + File.WriteAllText($"{Environment.GetEnvironmentVariable("EFCORE_RESULTS_PATH")}/efcore_results_{DateTime.Now:yyyyMMdd_HHmmss}.json", CustomJsonSerializer.Serialize(results)); + } +} diff --git a/services/dotnet-service/efcore-sandbox/EFCoreSchemaValidationEntrypoint.cs b/services/dotnet-service/efcore-sandbox/EFCoreSchemaValidationEntrypoint.cs new file mode 100644 index 0000000..f377d28 --- /dev/null +++ b/services/dotnet-service/efcore-sandbox/EFCoreSchemaValidationEntrypoint.cs @@ -0,0 +1,238 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace efcore_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +[Table("Customers", Schema = "Sales")] +public class Customer +{ + [Key] + public required int CustomerID { get; set; } + [MaxLength(200)] + public required string CustomerName { get; set; } + [Column(TypeName="datetime2")] + [Precision(7)] + public required DateTime AccountOpenedDate { get; set; } + [Column(TypeName="decimal")] + [Precision(18, 2)] + public decimal? CreditLimit { get; set; } + public List CustomerTransactions { get; set; } = []; +} + +[Table("CustomerTransactions", Schema = "Sales")] +public class CustomerTransaction +{ + [Key] + public int CustomerTransactionID { get; set; } + [ForeignKey(nameof(Customer))] + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +[Table("Orders", Schema = "Sales")] +public class Order +{ + [Key] + public int OrderID { get; set; } + [ForeignKey(nameof(Customer))] + public int CustomerID { get; set; } + public Customer Customer { get; set; } = null!; + public List OrderLines { get; set; } = []; +} + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{ + [Key] + public int OrderLineID { get; set; } + [ForeignKey(nameof(Order))] + public int OrderID { get; set; } + public int StockItemID { get; set; } + public required string Description { get; set; } + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +public class SandboxDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Customers => Set(); + public DbSet Orders => Set(); + public DbSet CustomerTransactions => Set(); + public DbSet OrderLines => Set(); +} + +public static class EFCoreSchemaValidationEntrypoint +{ + public static void ValidateEntity(SandboxDbContext ctx) where T : class + { + Console.WriteLine($"Validating EF Core entity: {typeof(T).Name}"); + Console.WriteLine(CustomJsonSerializer.Serialize(ctx.Set().FirstOrDefault())); + Console.WriteLine($"Successfully validated EF Core entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + using var context = new SandboxDbContext( + new DbContextOptionsBuilder() + .UseSqlServer( + args.ElementAtOrDefault(0) ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + ) + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .EnableSensitiveDataLogging() + .LogTo(Console.WriteLine, [DbLoggerCategory.Database.Command.Name], minimumLevel: LogLevel.Information, options: DbContextLoggerOptions.SingleLine).Options + ); + + var queries = new Action[] { + () => ValidateEntity(context), + () => ValidateEntity(context), + () => ValidateEntity(context), + () => ValidateEntity(context) + }; + + foreach (var query in queries) { + try { + query(); + } catch (Exception ex) { + Console.Error.WriteLine($"Error occurred while validating entity: {ex}"); + } + } + } +} \ No newline at end of file diff --git a/services/dotnet-service/efcore-sandbox/Properties/launchSettings.json b/services/dotnet-service/efcore-sandbox/Properties/launchSettings.json new file mode 100644 index 0000000..73150fc --- /dev/null +++ b/services/dotnet-service/efcore-sandbox/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://www.schemastore.org/launchsettings.json", + "profiles": { + "default": { + "commandName": "Project", + "environmentVariables": { + "CONNECTION_STRING": "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True", + "EFCORE_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/efcore-sandbox/results" + }, + "sqlDebugging": true + }, + } +} diff --git a/services/dotnet-service/efcore-sandbox/efcore-sandbox.csproj b/services/dotnet-service/efcore-sandbox/efcore-sandbox.csproj new file mode 100644 index 0000000..d348e95 --- /dev/null +++ b/services/dotnet-service/efcore-sandbox/efcore-sandbox.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + efcore_sandbox + enable + enable + + + + + + diff --git a/services/dotnet-service/nhibernate-sandbox/NHibernateQueryEntrypoint.cs b/services/dotnet-service/nhibernate-sandbox/NHibernateQueryEntrypoint.cs new file mode 100644 index 0000000..b234706 --- /dev/null +++ b/services/dotnet-service/nhibernate-sandbox/NHibernateQueryEntrypoint.cs @@ -0,0 +1,356 @@ +using NHibernate.Linq; +using System; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Driver; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; +using NHibernate.Dialect; + +namespace nhibernate_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +public class Customer +{ + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual required string CustomerName { get; set; } + public virtual DateTime AccountOpenedDate { get; set; } + public virtual decimal? CreditLimit { get; set; } + public virtual IList CustomerTransactions { get; set; } = []; +} + +public class CustomerTransaction +{ + [JsonPropertyName("customerTransactionId")] + public virtual int CustomerTransactionID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual DateTime TransactionDate { get; set; } + public virtual decimal TransactionAmount { get; set; } +} + +public class Order +{ + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual Customer Customer { get; set; } = null!; + public virtual IList OrderLines { get; set; } = []; +} + +public class OrderLine +{ + [JsonPropertyName("orderLineId")] + public virtual int OrderLineID { get; set; } + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("stockItemId")] + public virtual int StockItemID { get; set; } + public virtual required string Description { get; set; } + [JsonPropertyName("packageTypeId")] + public virtual int PackageTypeID { get; set; } + public virtual int Quantity { get; set; } + public virtual decimal? UnitPrice { get; set; } + public virtual decimal TaxRate { get; set; } + public virtual int PickedQuantity { get; set; } + public virtual DateTime? PickingCompletedWhen { get; set; } + public virtual int LastEditedBy { get; set; } + public virtual DateTime LastEditedWhen { get; set; } +} + +public class CustomerMap : ClassMapping { + public CustomerMap() { + Table("Customers"); Schema("Sales"); + Id(x => x.CustomerID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerName); + Property(x => x.AccountOpenedDate); + Property(x => x.CreditLimit); + Bag(x => x.CustomerTransactions, map => { map.Key(k => k.Column("CustomerID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} +public class CustomerTransactionMap : ClassMapping { + public CustomerTransactionMap() { + Table("CustomerTransactions"); Schema("Sales"); + Id(x => x.CustomerTransactionID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.TransactionDate); + Property(x => x.TransactionAmount); + } +} +public class OrderMap : ClassMapping { + public OrderMap() { + Table("Orders"); Schema("Sales"); + Id(x => x.OrderID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + ManyToOne(x => x.Customer, m => m.Column("CustomerID")); + Bag(x => x.OrderLines, map => { map.Key(k => k.Column("OrderID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} +public class OrderLineMap : ClassMapping { + public OrderLineMap() { + Table("OrderLines"); Schema("Sales"); + Id(x => x.OrderLineID, m => m.Generator(Generators.Identity)); + Property(x => x.OrderID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.StockItemID); + Property(x => x.Description); + Property(x => x.PackageTypeID); + Property(x => x.Quantity); + Property(x => x.UnitPrice); + Property(x => x.TaxRate); + Property(x => x.PickedQuantity); + Property(x => x.PickingCompletedWhen); + Property(x => x.LastEditedBy); + Property(x => x.LastEditedWhen); + } +} + +public record Query3Projection +{ + public decimal TaxRate { get; set; } + public long Count { get; set; } +} + +public record Query5Projection +{ + public int OrderLineID { get; set; } + public int Quantity { get; set; } +} + +// --- Query Entrypoint --- + +public static class NHibernateQueryEntrypoint +{ + public static IQueryable Query1(NHibernate.ISession session) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + return session.Query().Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to); + } + + public static IEnumerable Query2(NHibernate.ISession session) + { + // Cartesian product cannot be done NHibernate + var q = session.Query() + .Where(o => o.CustomerID == 1); + + q.Fetch(o => o.Customer) + .ThenFetchMany(c => c.CustomerTransactions) + .ToFuture(); + + return q.FetchMany(o => o.OrderLines) + .Distinct() + .ToFuture(); + } + + public static IQueryable Query3(NHibernate.ISession session) + { + return session.Query().GroupBy(ol => ol.TaxRate).Select(g => new Query3Projection { TaxRate = g.Key, Count = g.Count() }).OrderByDescending(x => x.Count); + } + + public static IQueryable Query4(NHibernate.ISession session) + { + return session.Query().OrderByDescending(ol => ol.Quantity).Take(50); + } + + public static IQueryable Query5(NHibernate.ISession session) + { + return session.Query().Select(ol => new Query5Projection { OrderLineID = ol.OrderLineID, Quantity = ol.Quantity }); + } + + private static object RunQuery(Func> q, Func? orderBySelector = null) + { + var query = q(); + var count = query.Count(); + return new { sqlString = "logged in console", count, + firstSample = count > 0 ? (orderBySelector != null ? query.OrderBy(orderBySelector).FirstOrDefault() : query.FirstOrDefault()) : default, + lastSample = count > 1 ? (orderBySelector != null ? query.OrderByDescending(orderBySelector).FirstOrDefault() : query.LastOrDefault()) : default }; + } + + public static void ValidateEntity(NHibernate.ISession session) where T : class + { + Console.WriteLine($"Validating NHibernate entity: {typeof(T).Name}"); + session.Query().FirstOrDefault(); + Console.WriteLine($"Successfully validated NHibernate entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + string connectionString = args.ElementAtOrDefault(0) + ?? System.Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + var configuration = new Configuration() + .DataBaseIntegration(db => + { + db.ConnectionString = connectionString; + db.Dialect(); + db.Driver(); + db.LogSqlInConsole = true; + }); + + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities()); + + using var sessionFactory = configuration.BuildSessionFactory(); + using var session = sessionFactory.OpenSession(); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + ValidateEntity(session); + ValidateEntity(session); + ValidateEntity(session); + ValidateEntity(session); + + // Now create and execute the queries and capture results + var results = new Dictionary(); + var harnesses = new Func[] { + () => RunQuery(() => Query1(session), ol => ol.OrderLineID), + () => RunQuery(() => Query2(session), o => o.OrderID), + () => RunQuery(() => Query3(session), x => x.TaxRate), + () => RunQuery(() => Query4(session)), + () => RunQuery(() => Query5(session), ol => ol.OrderLineID) + }; + + for (int i = 0; i < harnesses.Length; i++) { + var qid = i+1; + Console.WriteLine($"Running Query {qid}..."); + try { + results[$"query{qid}"] = harnesses[i](); + } catch (Exception ex) { + results[$"query{qid}"] = new { error = ex.Message }; + Console.Error.WriteLine($"Error occurred while running Query{qid}: {ex}"); + } + } + File.WriteAllText($"{System.Environment.GetEnvironmentVariable("NHIBERNATE_RESULTS_PATH")}/nhibernate_results_{DateTime.Now:yyyyMMdd_HHmmss}.json", CustomJsonSerializer.Serialize(results)); + } +} diff --git a/services/dotnet-service/nhibernate-sandbox/NHibernateSchemaValidationEntrypoint.cs b/services/dotnet-service/nhibernate-sandbox/NHibernateSchemaValidationEntrypoint.cs new file mode 100644 index 0000000..364100f --- /dev/null +++ b/services/dotnet-service/nhibernate-sandbox/NHibernateSchemaValidationEntrypoint.cs @@ -0,0 +1,297 @@ +using System; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace nhibernate_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +public class Customer +{ + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual required string CustomerName { get; set; } + public virtual DateTime AccountOpenedDate { get; set; } + public virtual decimal? CreditLimit { get; set; } + [JsonIgnore] // Don't serialize this, cause it's large + public virtual IList CustomerTransactions { get; set; } = []; +} + +public class CustomerTransaction +{ + [JsonPropertyName("customerTransactionId")] + public virtual int CustomerTransactionID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual DateTime TransactionDate { get; set; } + public virtual decimal TransactionAmount { get; set; } +} + +public class Order +{ + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual Customer Customer { get; set; } = null!; + [JsonIgnore] // Don't serialize this, cause it's large + public virtual IList OrderLines { get; set; } = []; +} + +public class OrderLine +{ + [JsonPropertyName("orderLineId")] + public virtual int OrderLineID { get; set; } + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("stockItemId")] + public virtual int StockItemID { get; set; } + public virtual required string Description { get; set; } + [JsonPropertyName("packageTypeId")] + public virtual int PackageTypeID { get; set; } + public virtual int Quantity { get; set; } + public virtual decimal? UnitPrice { get; set; } + public virtual decimal TaxRate { get; set; } + public virtual int PickedQuantity { get; set; } + public virtual DateTime? PickingCompletedWhen { get; set; } + public virtual int LastEditedBy { get; set; } + public virtual DateTime LastEditedWhen { get; set; } +} + +public class CustomerMap : ClassMapping { + public CustomerMap() { + Table("Customers"); Schema("Sales"); + Id(x => x.CustomerID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerName); + Property(x => x.AccountOpenedDate); + Property(x => x.CreditLimit); + Bag(x => x.CustomerTransactions, map => { map.Key(k => k.Column("CustomerID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} + +public class CustomerTransactionMap : ClassMapping { + public CustomerTransactionMap() { + Table("CustomerTransactions"); Schema("Sales"); + Id(x => x.CustomerTransactionID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.TransactionDate); + Property(x => x.TransactionAmount); + } +} + +public class OrderMap : ClassMapping { + public OrderMap() { + Table("Orders"); Schema("Sales"); + Id(x => x.OrderID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + ManyToOne(x => x.Customer, m => m.Column("CustomerID")); + Bag(x => x.OrderLines, map => { map.Key(k => k.Column("OrderID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} + +public class OrderLineMap : ClassMapping { + public OrderLineMap() { + Table("OrderLines"); Schema("Sales"); + Id(x => x.OrderLineID, m => m.Generator(Generators.Identity)); + Property(x => x.OrderID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.StockItemID); + Property(x => x.Description); + Property(x => x.PackageTypeID); + Property(x => x.Quantity); + Property(x => x.UnitPrice); + Property(x => x.TaxRate); + Property(x => x.PickedQuantity); + Property(x => x.PickingCompletedWhen); + Property(x => x.LastEditedBy); + Property(x => x.LastEditedWhen); + } +} + +// --- Schema Validation Entrypoint --- + +public static class NHibernateSchemaValidationEntrypoint +{ + public static void ValidateEntity(NHibernate.ISession session) where T : class + { + Console.WriteLine($"Validating NHibernate entity: {typeof(T).Name}"); + Console.WriteLine(CustomJsonSerializer.Serialize(session.Query().FirstOrDefault())); + Console.WriteLine($"Successfully validated NHibernate entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + var connectionString = args.ElementAtOrDefault(0) ?? + System.Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? + "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + var configuration = new Configuration(); + configuration.DataBaseIntegration(c => + { + c.Driver(); + c.Dialect(); + c.ConnectionString = connectionString; + c.LogSqlInConsole = true; + }); + + var mapper = new ModelMapper(); + mapper.AddMappings(new[] + { + typeof(CustomerMap), + typeof(CustomerTransactionMap), + typeof(OrderMap), + typeof(OrderLineMap) + }); + + configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities()); + + var sessionFactory = configuration.BuildSessionFactory(); + using var session = sessionFactory.OpenSession(); + + var queries = new Action[] + { + () => ValidateEntity(session), + () => ValidateEntity(session), + () => ValidateEntity(session), + () => ValidateEntity(session) + }; + + foreach (var query in queries) + { + try + { + query(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error occurred while validating entity: {ex}"); + } + } + } +} \ No newline at end of file diff --git a/services/dotnet-service/nhibernate-sandbox/Properties/launchSettings.json b/services/dotnet-service/nhibernate-sandbox/Properties/launchSettings.json new file mode 100644 index 0000000..1cfa717 --- /dev/null +++ b/services/dotnet-service/nhibernate-sandbox/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://www.schemastore.org/launchsettings.json", + "profiles": { + "default": { + "commandName": "Project", + "environmentVariables": { + "CONNECTION_STRING": "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True", + "NHIBERNATE_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/nhibernate-sandbox/results" + }, + "sqlDebugging": true + }, + } +} diff --git a/services/dotnet-service/nhibernate-sandbox/nhibernate-sandbox.csproj b/services/dotnet-service/nhibernate-sandbox/nhibernate-sandbox.csproj new file mode 100644 index 0000000..07f6d5b --- /dev/null +++ b/services/dotnet-service/nhibernate-sandbox/nhibernate-sandbox.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + nhibernate_sandbox + enable + enable + + + + + + diff --git a/services/dotnet-service/sandbox.csproj b/services/dotnet-service/sandbox.csproj new file mode 100644 index 0000000..ac12baa --- /dev/null +++ b/services/dotnet-service/sandbox.csproj @@ -0,0 +1,14 @@ + + + Library + net10.0 + enable + enable + + + + + + + + diff --git a/services/etl/.gitignore b/services/etl/.gitignore new file mode 100644 index 0000000..fc1159c --- /dev/null +++ b/services/etl/.gitignore @@ -0,0 +1,8 @@ +*dump +*jdbc* +*.tar.gz +*.zip +csv-output/ +neo4j-etl-cli-1.6.0/ +*.png +logs/ diff --git a/services/etl/README.md b/services/etl/README.md new file mode 100644 index 0000000..a7af260 --- /dev/null +++ b/services/etl/README.md @@ -0,0 +1,5 @@ +## Useful config information + +- JDBC Connection strings + - MS SQL Server: `jdbc:sqlserver://mssql_db:1433;databaseName=WideWorldImporters;encrypt=true;trustServerCertificate=true;` + - MongoDB: `mongodb://root:root@mongodb:27017/uom?authSource=admin` (if outside container, use `mongodb://root:root@localhost:27017/uom?authSource=admin`) diff --git a/services/etl/mongodb/UOM WideWorldImporters.relmig b/services/etl/mongodb/UOM WideWorldImporters.relmig new file mode 100644 index 0000000..ec375e8 --- /dev/null +++ b/services/etl/mongodb/UOM WideWorldImporters.relmig @@ -0,0 +1,7835 @@ +{ + "version": "1.8.0", + "project": { + "name": "UOM WideWorldImporters", + "type": "SQL_SERVER", + "lastModified": "2026-04-09T16:19:14.946Z", + "schemasId": "9e929ddbb2fd47a695b24131a75d0af4", + "jdbcId": "628f738685c243b2834831aa79f5dc08", + "mongodbId": "39e0dba6ff40462a9a6a3be0fd12a8cc", + "content": { + "settings": { + "shouldRecommendSchema": false, + "casing": "CAMEL_CASE", + "excludedJsonExportCollections": [], + "keyHandling": "GENERATED" + }, + "collections": { + "5ae73f19-fc2c-4cf3-9514-b021684ea118": { + "name": "suppliers" + }, + "67c830a5-d1e2-4067-89a3-940120710933": { + "name": "orderLines" + }, + "0050fc88-de6c-402c-a96d-f7c8805e0526": { + "name": "people" + }, + "5bd7402f-e4cd-4912-8577-7bc6488ae535": { + "name": "orders" + } + }, + "mappings": { + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItemStockGroups", + "foreignKeyName": "FK_Warehouse_StockItemStockGroups_StockItemID_Warehouse_StockItems" + }, + "fields": { + "StockItemStockGroupID": { + "target": { + "name": "stockItemStockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemStockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "StockGroupID": { + "target": { + "name": "stockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "67c830a5-d1e2-4067-89a3-940120710933", + "table": "9e8b294f-bc57-4cac-a069-428746557a3d" + }, + "9f196589-7438-4f25-bb13-8192bb30e9f8": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockGroups", + "foreignKeyName": "FK_Warehouse_StockGroups_Application_People" + }, + "fields": { + "StockGroupID": { + "target": { + "name": "stockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockGroupName": { + "target": { + "name": "stockGroupName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockGroupName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "425f7d7d-d603-454d-9b0c-e6da06bce933" + }, + "63db12cb-a4e7-47fa-9812-c8545aef2483": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItems", + "foreignKeyName": "FK_Warehouse_StockItems_Application_People" + }, + "fields": { + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemName": { + "target": { + "name": "stockItemName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockItemName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ColorID": { + "target": { + "name": "colorId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ColorID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPackageID": { + "target": { + "name": "unitPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "UnitPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OuterPackageID": { + "target": { + "name": "outerPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OuterPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Brand": { + "target": { + "name": "brand", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Brand", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Size": { + "target": { + "name": "size", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Size", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LeadTimeDays": { + "target": { + "name": "leadTimeDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LeadTimeDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "QuantityPerOuter": { + "target": { + "name": "quantityPerOuter", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "QuantityPerOuter", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "IsChillerStock": { + "target": { + "name": "isChillerStock", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsChillerStock", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Barcode": { + "target": { + "name": "barcode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Barcode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "RecommendedRetailPrice": { + "target": { + "name": "recommendedRetailPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "RecommendedRetailPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TypicalWeightPerUnit": { + "target": { + "name": "typicalWeightPerUnit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TypicalWeightPerUnit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "MarketingComments": { + "target": { + "name": "marketingComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "MarketingComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Tags": { + "target": { + "name": "tags", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Tags", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchDetails": { + "target": { + "name": "searchDetails", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchDetails", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc" + }, + "f59c4734-7712-43a7-98a9-7d4b81e08a79": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItemStockGroups", + "foreignKeyName": "FK_Warehouse_StockItemStockGroups_Application_People" + }, + "fields": { + "StockItemStockGroupID": { + "target": { + "name": "stockItemStockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemStockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "StockGroupID": { + "target": { + "name": "stockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "9e8b294f-bc57-4cac-a069-428746557a3d" + }, + "6ea7e1fe-2957-4481-bea7-c29313e65f43": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItems", + "foreignKeyName": "FK_Warehouse_StockItems_SupplierID_Purchasing_Suppliers" + }, + "fields": { + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemName": { + "target": { + "name": "stockItemName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockItemName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ColorID": { + "target": { + "name": "colorId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ColorID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPackageID": { + "target": { + "name": "unitPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "UnitPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OuterPackageID": { + "target": { + "name": "outerPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OuterPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Brand": { + "target": { + "name": "brand", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Brand", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Size": { + "target": { + "name": "size", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Size", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LeadTimeDays": { + "target": { + "name": "leadTimeDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LeadTimeDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "QuantityPerOuter": { + "target": { + "name": "quantityPerOuter", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "QuantityPerOuter", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "IsChillerStock": { + "target": { + "name": "isChillerStock", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsChillerStock", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Barcode": { + "target": { + "name": "barcode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Barcode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "RecommendedRetailPrice": { + "target": { + "name": "recommendedRetailPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "RecommendedRetailPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TypicalWeightPerUnit": { + "target": { + "name": "typicalWeightPerUnit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TypicalWeightPerUnit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "MarketingComments": { + "target": { + "name": "marketingComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "MarketingComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Tags": { + "target": { + "name": "tags", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Tags", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchDetails": { + "target": { + "name": "searchDetails", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchDetails", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5ae73f19-fc2c-4cf3-9514-b021684ea118", + "table": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc" + }, + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "customerTransactions", + "foreignKeyName": "FK_Sales_CustomerTransactions_CustomerID_Sales_Customers" + }, + "fields": { + "CustomerTransactionID": { + "target": { + "name": "customerTransactionId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerTransactionID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionTypeID": { + "target": { + "name": "transactionTypeId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "TransactionTypeID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "InvoiceID": { + "target": { + "name": "invoiceId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "InvoiceID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PaymentMethodID": { + "target": { + "name": "paymentMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionDate": { + "target": { + "name": "transactionDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "TransactionDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "AmountExcludingTax": { + "target": { + "name": "amountExcludingTax", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "AmountExcludingTax", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TaxAmount": { + "target": { + "name": "taxAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TransactionAmount": { + "target": { + "name": "transactionAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TransactionAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "OutstandingBalance": { + "target": { + "name": "outstandingBalance", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "OutstandingBalance", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "FinalizationDate": { + "target": { + "name": "finalizationDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "FinalizationDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "IsFinalized": { + "target": { + "name": "isFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5bd7402f-e4cd-4912-8577-7bc6488ae535", + "table": "72f6e4d6-a373-4657-9922-0b0752282ed6" + }, + "1b45b1e6-c743-412c-8ff7-04f63402c385": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "OrderID": { + "target": { + "name": "orderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OrderID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "SalespersonPersonID": { + "target": { + "name": "salespersonPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SalespersonPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PickedByPersonID": { + "target": { + "name": "pickedByPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PickedByPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ContactPersonID": { + "target": { + "name": "contactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "BackorderOrderID": { + "target": { + "name": "backorderOrderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BackorderOrderID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OrderDate": { + "target": { + "name": "orderDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "OrderDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "ExpectedDeliveryDate": { + "target": { + "name": "expectedDeliveryDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ExpectedDeliveryDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "CustomerPurchaseOrderNumber": { + "target": { + "name": "customerPurchaseOrderNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomerPurchaseOrderNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsUndersupplyBackordered": { + "target": { + "name": "isUndersupplyBackordered", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsUndersupplyBackordered", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Comments": { + "target": { + "name": "comments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Comments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryInstructions": { + "target": { + "name": "deliveryInstructions", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryInstructions", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PickingCompletedWhen": { + "target": { + "name": "pickingCompletedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "PickingCompletedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5bd7402f-e4cd-4912-8577-7bc6488ae535", + "table": "e9551f17-aa01-44d4-b1bd-96ce8d92906f" + }, + "44de7a1e-e986-4c92-8a88-6c7b6879eb89": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "SupplierName": { + "target": { + "name": "supplierName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierCategoryID": { + "target": { + "name": "supplierCategoryId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierCategoryID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PrimaryContactPersonID": { + "target": { + "name": "primaryContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PrimaryContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "AlternateContactPersonID": { + "target": { + "name": "alternateContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "AlternateContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryCityID": { + "target": { + "name": "deliveryCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PostalCityID": { + "target": { + "name": "postalCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PostalCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "SupplierReference": { + "target": { + "name": "supplierReference", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierReference", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountName": { + "target": { + "name": "bankAccountName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountBranch": { + "target": { + "name": "bankAccountBranch", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountBranch", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountCode": { + "target": { + "name": "bankAccountCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountNumber": { + "target": { + "name": "bankAccountNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankInternationalCode": { + "target": { + "name": "bankInternationalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankInternationalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PaymentDays": { + "target": { + "name": "paymentDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "WebsiteURL": { + "target": { + "name": "websiteUrl", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "WebsiteURL", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine1": { + "target": { + "name": "deliveryAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine2": { + "target": { + "name": "deliveryAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryPostalCode": { + "target": { + "name": "deliveryPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryLocation": { + "target": { + "name": "deliveryLocation", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "DeliveryLocation", + "databaseSpecificType": "geography", + "isPrimaryKey": false + } + }, + "PostalAddressLine1": { + "target": { + "name": "postalAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalAddressLine2": { + "target": { + "name": "postalAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalPostalCode": { + "target": { + "name": "postalPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5ae73f19-fc2c-4cf3-9514-b021684ea118", + "table": "204a2525-4420-4864-b4f3-6aa6b5b87009" + }, + "414ebc35-d5a9-4055-b976-aae8fbb07014": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "PersonID": { + "target": { + "name": "personId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PersonID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "FullName": { + "target": { + "name": "fullName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FullName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PreferredName": { + "target": { + "name": "preferredName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PreferredName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchName": { + "target": { + "name": "searchName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsPermittedToLogon": { + "target": { + "name": "isPermittedToLogon", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsPermittedToLogon", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "LogonName": { + "target": { + "name": "logonName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "LogonName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsExternalLogonProvider": { + "target": { + "name": "isExternalLogonProvider", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsExternalLogonProvider", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "HashedPassword": { + "target": { + "name": "hashedPassword", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "HashedPassword", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "IsSystemUser": { + "target": { + "name": "isSystemUser", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsSystemUser", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsEmployee": { + "target": { + "name": "isEmployee", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsEmployee", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsSalesperson": { + "target": { + "name": "isSalesperson", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsSalesperson", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "UserPreferences": { + "target": { + "name": "userPreferences", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "UserPreferences", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "EmailAddress": { + "target": { + "name": "emailAddress", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "EmailAddress", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "OtherLanguages": { + "target": { + "name": "otherLanguages", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "OtherLanguages", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "cb798bd4-dd41-4e73-a608-7043c3fae427" + }, + "7c6c2855-a520-4c17-84f8-b073f8bf9c83": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "customerTransactions", + "foreignKeyName": "FK_Sales_CustomerTransactions_Application_People" + }, + "fields": { + "CustomerTransactionID": { + "target": { + "name": "customerTransactionId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerTransactionID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionTypeID": { + "target": { + "name": "transactionTypeId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "TransactionTypeID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "InvoiceID": { + "target": { + "name": "invoiceId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "InvoiceID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PaymentMethodID": { + "target": { + "name": "paymentMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionDate": { + "target": { + "name": "transactionDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "TransactionDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "AmountExcludingTax": { + "target": { + "name": "amountExcludingTax", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "AmountExcludingTax", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TaxAmount": { + "target": { + "name": "taxAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TransactionAmount": { + "target": { + "name": "transactionAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TransactionAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "OutstandingBalance": { + "target": { + "name": "outstandingBalance", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "OutstandingBalance", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "FinalizationDate": { + "target": { + "name": "finalizationDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "FinalizationDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "IsFinalized": { + "target": { + "name": "isFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "72f6e4d6-a373-4657-9922-0b0752282ed6" + }, + "f8055db3-d857-4243-9a2e-3ad5d745f8ba": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "purchaseOrders", + "foreignKeyName": "FK_Purchasing_PurchaseOrders_SupplierID_Purchasing_Suppliers" + }, + "fields": { + "PurchaseOrderID": { + "target": { + "name": "purchaseOrderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PurchaseOrderID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OrderDate": { + "target": { + "name": "orderDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "OrderDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ContactPersonID": { + "target": { + "name": "contactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ExpectedDeliveryDate": { + "target": { + "name": "expectedDeliveryDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ExpectedDeliveryDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "SupplierReference": { + "target": { + "name": "supplierReference", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierReference", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsOrderFinalized": { + "target": { + "name": "isOrderFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOrderFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Comments": { + "target": { + "name": "comments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Comments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5ae73f19-fc2c-4cf3-9514-b021684ea118", + "table": "1e704ab1-0f91-464b-bd2e-82453fb1b08d" + }, + "36077cd4-107a-4238-8529-afaccb0d12c5": { + "settings": { + "type": "EMBEDDED_DOCUMENT", + "embeddedPath": "stockItem", + "foreignKeyName": "FK_Sales_OrderLines_StockItemID_Warehouse_StockItems" + }, + "fields": { + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemName": { + "target": { + "name": "stockItemName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockItemName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ColorID": { + "target": { + "name": "colorId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ColorID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPackageID": { + "target": { + "name": "unitPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "UnitPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OuterPackageID": { + "target": { + "name": "outerPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OuterPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Brand": { + "target": { + "name": "brand", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Brand", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Size": { + "target": { + "name": "size", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Size", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LeadTimeDays": { + "target": { + "name": "leadTimeDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LeadTimeDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "QuantityPerOuter": { + "target": { + "name": "quantityPerOuter", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "QuantityPerOuter", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "IsChillerStock": { + "target": { + "name": "isChillerStock", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsChillerStock", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Barcode": { + "target": { + "name": "barcode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Barcode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "RecommendedRetailPrice": { + "target": { + "name": "recommendedRetailPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "RecommendedRetailPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TypicalWeightPerUnit": { + "target": { + "name": "typicalWeightPerUnit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TypicalWeightPerUnit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "MarketingComments": { + "target": { + "name": "marketingComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "MarketingComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Tags": { + "target": { + "name": "tags", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Tags", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchDetails": { + "target": { + "name": "searchDetails", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchDetails", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "67c830a5-d1e2-4067-89a3-940120710933", + "table": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc" + }, + "0e36a268-5d48-434c-9064-41cef0cc9d7f": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "OrderLineID": { + "target": { + "name": "orderLineId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OrderLineID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "OrderID": { + "target": { + "name": "orderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OrderID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Description": { + "target": { + "name": "description", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Description", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PackageTypeID": { + "target": { + "name": "packageTypeId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PackageTypeID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Quantity": { + "target": { + "name": "quantity", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "Quantity", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "PickedQuantity": { + "target": { + "name": "pickedQuantity", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PickedQuantity", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PickingCompletedWhen": { + "target": { + "name": "pickingCompletedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "PickingCompletedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "67c830a5-d1e2-4067-89a3-940120710933", + "table": "36254277-7ab4-4e9d-9409-5570705349a3" + }, + "1bf20c42-5de3-452d-80dc-10e3dc127c05": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "customers", + "foreignKeyName": "FK_Sales_Customers_Application_People" + }, + "fields": { + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerName": { + "target": { + "name": "customerName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomerName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BillToCustomerID": { + "target": { + "name": "billToCustomerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BillToCustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CustomerCategoryID": { + "target": { + "name": "customerCategoryId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerCategoryID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "BuyingGroupID": { + "target": { + "name": "buyingGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BuyingGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PrimaryContactPersonID": { + "target": { + "name": "primaryContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PrimaryContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "AlternateContactPersonID": { + "target": { + "name": "alternateContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "AlternateContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryCityID": { + "target": { + "name": "deliveryCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PostalCityID": { + "target": { + "name": "postalCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PostalCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CreditLimit": { + "target": { + "name": "creditLimit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "CreditLimit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "AccountOpenedDate": { + "target": { + "name": "accountOpenedDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "AccountOpenedDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "StandardDiscountPercentage": { + "target": { + "name": "standardDiscountPercentage", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "StandardDiscountPercentage", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "IsStatementSent": { + "target": { + "name": "isStatementSent", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsStatementSent", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsOnCreditHold": { + "target": { + "name": "isOnCreditHold", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOnCreditHold", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "PaymentDays": { + "target": { + "name": "paymentDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryRun": { + "target": { + "name": "deliveryRun", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryRun", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "RunPosition": { + "target": { + "name": "runPosition", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "RunPosition", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "WebsiteURL": { + "target": { + "name": "websiteUrl", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "WebsiteURL", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine1": { + "target": { + "name": "deliveryAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine2": { + "target": { + "name": "deliveryAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryPostalCode": { + "target": { + "name": "deliveryPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryLocation": { + "target": { + "name": "deliveryLocation", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "DeliveryLocation", + "databaseSpecificType": "geography", + "isPrimaryKey": false + } + }, + "PostalAddressLine1": { + "target": { + "name": "postalAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalAddressLine2": { + "target": { + "name": "postalAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalPostalCode": { + "target": { + "name": "postalPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "b86d8cee-b856-4461-8766-cfc4cb41d67f" + }, + "8ae46573-a830-4364-8227-a9d0923eb860": { + "settings": { + "type": "EMBEDDED_DOCUMENT", + "embeddedPath": "customer", + "foreignKeyName": "FK_Sales_Orders_CustomerID_Sales_Customers" + }, + "fields": { + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerName": { + "target": { + "name": "customerName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomerName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BillToCustomerID": { + "target": { + "name": "billToCustomerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BillToCustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CustomerCategoryID": { + "target": { + "name": "customerCategoryId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerCategoryID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "BuyingGroupID": { + "target": { + "name": "buyingGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BuyingGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PrimaryContactPersonID": { + "target": { + "name": "primaryContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PrimaryContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "AlternateContactPersonID": { + "target": { + "name": "alternateContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "AlternateContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryCityID": { + "target": { + "name": "deliveryCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PostalCityID": { + "target": { + "name": "postalCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PostalCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CreditLimit": { + "target": { + "name": "creditLimit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "CreditLimit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "AccountOpenedDate": { + "target": { + "name": "accountOpenedDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "AccountOpenedDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "StandardDiscountPercentage": { + "target": { + "name": "standardDiscountPercentage", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "StandardDiscountPercentage", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "IsStatementSent": { + "target": { + "name": "isStatementSent", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsStatementSent", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsOnCreditHold": { + "target": { + "name": "isOnCreditHold", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOnCreditHold", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "PaymentDays": { + "target": { + "name": "paymentDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryRun": { + "target": { + "name": "deliveryRun", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryRun", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "RunPosition": { + "target": { + "name": "runPosition", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "RunPosition", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "WebsiteURL": { + "target": { + "name": "websiteUrl", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "WebsiteURL", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine1": { + "target": { + "name": "deliveryAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine2": { + "target": { + "name": "deliveryAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryPostalCode": { + "target": { + "name": "deliveryPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryLocation": { + "target": { + "name": "deliveryLocation", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "DeliveryLocation", + "databaseSpecificType": "geography", + "isPrimaryKey": false + } + }, + "PostalAddressLine1": { + "target": { + "name": "postalAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalAddressLine2": { + "target": { + "name": "postalAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalPostalCode": { + "target": { + "name": "postalPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5bd7402f-e4cd-4912-8577-7bc6488ae535", + "table": "b86d8cee-b856-4461-8766-cfc4cb41d67f" + }, + "f793abcd-17fa-48cd-b9f0-e0226de2421f": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "purchaseOrders", + "foreignKeyName": "FK_Purchasing_PurchaseOrders_Application_People" + }, + "fields": { + "PurchaseOrderID": { + "target": { + "name": "purchaseOrderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PurchaseOrderID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OrderDate": { + "target": { + "name": "orderDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "OrderDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ContactPersonID": { + "target": { + "name": "contactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ExpectedDeliveryDate": { + "target": { + "name": "expectedDeliveryDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ExpectedDeliveryDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "SupplierReference": { + "target": { + "name": "supplierReference", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierReference", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsOrderFinalized": { + "target": { + "name": "isOrderFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOrderFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Comments": { + "target": { + "name": "comments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Comments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "1e704ab1-0f91-464b-bd2e-82453fb1b08d" + } + }, + "relationships": { + "tables": { + "WideWorldImporters.Sales.Orders": { + "mappings": [ + "1b45b1e6-c743-412c-8ff7-04f63402c385" + ] + }, + "WideWorldImporters.Sales.Customers": { + "mappings": [ + "1bf20c42-5de3-452d-80dc-10e3dc127c05", + "8ae46573-a830-4364-8227-a9d0923eb860" + ] + }, + "WideWorldImporters.Purchasing.Suppliers": { + "mappings": [ + "44de7a1e-e986-4c92-8a88-6c7b6879eb89" + ] + }, + "WideWorldImporters.Warehouse.StockGroups": { + "mappings": [ + "9f196589-7438-4f25-bb13-8192bb30e9f8" + ] + }, + "WideWorldImporters.Purchasing.PurchaseOrders": { + "mappings": [ + "f8055db3-d857-4243-9a2e-3ad5d745f8ba", + "f793abcd-17fa-48cd-b9f0-e0226de2421f" + ] + }, + "WideWorldImporters.Sales.CustomerTransactions": { + "mappings": [ + "7c6c2855-a520-4c17-84f8-b073f8bf9c83", + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f" + ] + }, + "WideWorldImporters.Application.People": { + "mappings": [ + "414ebc35-d5a9-4055-b976-aae8fbb07014" + ] + }, + "WideWorldImporters.Warehouse.StockItemStockGroups": { + "mappings": [ + "f59c4734-7712-43a7-98a9-7d4b81e08a79", + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c" + ] + }, + "WideWorldImporters.Warehouse.StockItems": { + "mappings": [ + "6ea7e1fe-2957-4481-bea7-c29313e65f43", + "63db12cb-a4e7-47fa-9812-c8545aef2483", + "36077cd4-107a-4238-8529-afaccb0d12c5" + ] + }, + "WideWorldImporters.Sales.OrderLines": { + "mappings": [ + "0e36a268-5d48-434c-9064-41cef0cc9d7f" + ] + } + }, + "collections": { + "5ae73f19-fc2c-4cf3-9514-b021684ea118": { + "mappings": [ + "44de7a1e-e986-4c92-8a88-6c7b6879eb89", + "6ea7e1fe-2957-4481-bea7-c29313e65f43", + "f8055db3-d857-4243-9a2e-3ad5d745f8ba" + ] + }, + "67c830a5-d1e2-4067-89a3-940120710933": { + "mappings": [ + "0e36a268-5d48-434c-9064-41cef0cc9d7f", + "36077cd4-107a-4238-8529-afaccb0d12c5", + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c" + ] + }, + "0050fc88-de6c-402c-a96d-f7c8805e0526": { + "mappings": [ + "414ebc35-d5a9-4055-b976-aae8fbb07014", + "9f196589-7438-4f25-bb13-8192bb30e9f8", + "63db12cb-a4e7-47fa-9812-c8545aef2483", + "f793abcd-17fa-48cd-b9f0-e0226de2421f", + "1bf20c42-5de3-452d-80dc-10e3dc127c05", + "f59c4734-7712-43a7-98a9-7d4b81e08a79", + "7c6c2855-a520-4c17-84f8-b073f8bf9c83" + ] + }, + "5bd7402f-e4cd-4912-8577-7bc6488ae535": { + "mappings": [ + "1b45b1e6-c743-412c-8ff7-04f63402c385", + "8ae46573-a830-4364-8227-a9d0923eb860", + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f" + ] + } + }, + "mappings": { + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c": { + "children": [] + }, + "9f196589-7438-4f25-bb13-8192bb30e9f8": { + "children": [] + }, + "63db12cb-a4e7-47fa-9812-c8545aef2483": { + "children": [] + }, + "f59c4734-7712-43a7-98a9-7d4b81e08a79": { + "children": [] + }, + "6ea7e1fe-2957-4481-bea7-c29313e65f43": { + "children": [] + }, + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f": { + "children": [] + }, + "1b45b1e6-c743-412c-8ff7-04f63402c385": { + "children": [ + "8ae46573-a830-4364-8227-a9d0923eb860" + ] + }, + "44de7a1e-e986-4c92-8a88-6c7b6879eb89": { + "children": [ + "6ea7e1fe-2957-4481-bea7-c29313e65f43", + "f8055db3-d857-4243-9a2e-3ad5d745f8ba" + ] + }, + "414ebc35-d5a9-4055-b976-aae8fbb07014": { + "children": [ + "9f196589-7438-4f25-bb13-8192bb30e9f8", + "63db12cb-a4e7-47fa-9812-c8545aef2483", + "f793abcd-17fa-48cd-b9f0-e0226de2421f", + "1bf20c42-5de3-452d-80dc-10e3dc127c05", + "f59c4734-7712-43a7-98a9-7d4b81e08a79", + "7c6c2855-a520-4c17-84f8-b073f8bf9c83" + ] + }, + "7c6c2855-a520-4c17-84f8-b073f8bf9c83": { + "children": [] + }, + "f8055db3-d857-4243-9a2e-3ad5d745f8ba": { + "children": [] + }, + "36077cd4-107a-4238-8529-afaccb0d12c5": { + "children": [ + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c" + ] + }, + "0e36a268-5d48-434c-9064-41cef0cc9d7f": { + "children": [ + "36077cd4-107a-4238-8529-afaccb0d12c5" + ] + }, + "1bf20c42-5de3-452d-80dc-10e3dc127c05": { + "children": [] + }, + "8ae46573-a830-4364-8227-a9d0923eb860": { + "children": [ + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f" + ] + }, + "f793abcd-17fa-48cd-b9f0-e0226de2421f": { + "children": [] + } + } + }, + "diagrams": { + "activeTab": "df44aaa5-0a16-402e-a9d3-cd1bc87078c7", + "tabs": [ + { + "id": "df44aaa5-0a16-402e-a9d3-cd1bc87078c7", + "name": "Main Diagram", + "relational": { + "nodes": [ + { + "id": "425f7d7d-d603-454d-9b0c-e6da06bce933", + "type": "entityCard", + "position": { + "x": 12, + "y": 1200 + }, + "width": 244, + "height": 134, + "hidden": false + }, + { + "id": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "type": "entityCard", + "position": { + "x": 531, + "y": 372 + }, + "width": 244, + "height": 494, + "hidden": false + }, + { + "id": "9e8b294f-bc57-4cac-a069-428746557a3d", + "type": "entityCard", + "position": { + "x": 316.6666666666667, + "y": 138 + }, + "width": 244, + "height": 134, + "hidden": false + }, + { + "id": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "type": "entityCard", + "position": { + "x": 449.6666666666667, + "y": 978 + }, + "width": 244, + "height": 566, + "hidden": false + }, + { + "id": "1e704ab1-0f91-464b-bd2e-82453fb1b08d", + "type": "entityCard", + "position": { + "x": 184.66666666666669, + "y": 606 + }, + "width": 244, + "height": 260, + "hidden": false + }, + { + "id": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "type": "entityCard", + "position": { + "x": 885, + "y": 480 + }, + "width": 244, + "height": 332, + "hidden": false + }, + { + "id": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "type": "entityCard", + "position": { + "x": 1147.6666666666667, + "y": 966 + }, + "width": 244, + "height": 602, + "hidden": false + }, + { + "id": "36254277-7ab4-4e9d-9409-5570705349a3", + "type": "entityCard", + "position": { + "x": 885, + "y": 12 + }, + "width": 244, + "height": 260, + "hidden": false + }, + { + "id": "72f6e4d6-a373-4657-9922-0b0752282ed6", + "type": "entityCard", + "position": { + "x": 1229, + "y": 570 + }, + "width": 244, + "height": 296, + "hidden": false + }, + { + "id": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "type": "entityCard", + "position": { + "x": 109.6, + "y": 1668 + }, + "width": 244, + "height": 422, + "hidden": false + } + ], + "edges": [ + { + "id": "3fbfb3a9-b2d2-4a3c-8df4-0c68e9b83efd", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "425f7d7d-d603-454d-9b0c-e6da06bce933", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "2af463ac-831a-44b4-ba30-11ff11269e66", + "source": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "target": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "5791b2f2-19c3-4187-a683-0ee7c59dfbb5", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "eb8c695a-8bf4-49d2-b7d7-05d6c105eb4d", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "9e8b294f-bc57-4cac-a069-428746557a3d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "ff7d64f4-25e8-4b85-9a96-b897969291d6", + "source": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "target": "9e8b294f-bc57-4cac-a069-428746557a3d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "928f5f2e-0052-4ea6-b44c-2e9b477f9660", + "source": "425f7d7d-d603-454d-9b0c-e6da06bce933", + "target": "9e8b294f-bc57-4cac-a069-428746557a3d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "f87307b3-e9b4-401e-9cc7-f5c4246caf40", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "11900e22-71b4-448c-9c85-da437e73992f", + "source": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "target": "1e704ab1-0f91-464b-bd2e-82453fb1b08d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "5c496fa9-c0db-40c1-9b19-d86494883c5a", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "1e704ab1-0f91-464b-bd2e-82453fb1b08d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "52f00cb6-6425-475c-bac7-f556d7203212", + "source": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "target": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "cc48303a-9f36-4685-b369-77694d0dd922", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "54b3a4b8-e07e-42e8-b11c-1fd1ade50b83", + "source": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "target": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "f815a3b0-42a5-4cd7-8841-0cde392cba12", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "371ed3a4-c5b1-4acb-a025-298078c02d38", + "source": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "target": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "3a563a20-dd69-4e5c-9f84-ab4e4e7a600c", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "36254277-7ab4-4e9d-9409-5570705349a3", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "627cf0af-d18c-4fa2-94e0-93fc058ddf5a", + "source": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "target": "36254277-7ab4-4e9d-9409-5570705349a3", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "098e49ac-d389-4be5-8a65-cd12d527a30d", + "source": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "target": "36254277-7ab4-4e9d-9409-5570705349a3", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "d8dd8314-fa4b-402d-a674-f1c09fba04f4", + "source": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "target": "72f6e4d6-a373-4657-9922-0b0752282ed6", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "88533981-99fd-428d-9b5a-b6f9d5476da0", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "72f6e4d6-a373-4657-9922-0b0752282ed6", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "06ee654a-a1eb-4ab7-8c3a-0b718d1d13dd", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + } + ] + }, + "collection": { + "nodes": [ + { + "id": "suppliers", + "type": "entityCard", + "position": { + "x": 23, + "y": 966 + }, + "width": 244, + "height": 1286, + "hidden": false + }, + { + "id": "orderLines", + "type": "entityCard", + "position": { + "x": 23, + "y": 12 + }, + "width": 244, + "height": 854, + "hidden": false + }, + { + "id": "people", + "type": "entityCard", + "position": { + "x": 114.33333333333334, + "y": 2352 + }, + "width": 244, + "height": 2204, + "hidden": false + }, + { + "id": "orders", + "type": "entityCard", + "position": { + "x": 73.66666666666669, + "y": 4656 + }, + "width": 244, + "height": 1196, + "hidden": false + } + ], + "edges": [ + { + "id": "d22072de-1a00-4ab0-9c42-c97fcdd1c871", + "source": "people", + "target": "suppliers", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "c42b02dd-136b-47b9-8152-b151d895d315", + "source": "people", + "target": "orderLines", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "762870a8-1375-44ee-8d17-7aabd798ecb4", + "source": "suppliers", + "target": "orderLines", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "292b166f-2a44-44ea-a633-35cdd82ec15c", + "source": "orders", + "target": "orderLines", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "e49fd968-df89-4b8f-9da9-d2510484f89c", + "source": "people", + "target": "people", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "c48cc9e8-730e-4288-828c-77783b659ce3", + "source": "orders", + "target": "people", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "0b53107b-191f-492b-af8f-26f388732a95", + "source": "orders", + "target": "orders", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + } + ] + } + } + ] + }, + "tables": { + "425f7d7d-d603-454d-9b0c-e6da06bce933": { + "path": { + "database": "WideWorldImporters", + "schema": "Warehouse", + "table": "StockGroups" + } + }, + "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc": { + "path": { + "database": "WideWorldImporters", + "schema": "Warehouse", + "table": "StockItems" + } + }, + "9e8b294f-bc57-4cac-a069-428746557a3d": { + "path": { + "database": "WideWorldImporters", + "schema": "Warehouse", + "table": "StockItemStockGroups" + } + }, + "204a2525-4420-4864-b4f3-6aa6b5b87009": { + "path": { + "database": "WideWorldImporters", + "schema": "Purchasing", + "table": "Suppliers" + } + }, + "1e704ab1-0f91-464b-bd2e-82453fb1b08d": { + "path": { + "database": "WideWorldImporters", + "schema": "Purchasing", + "table": "PurchaseOrders" + } + }, + "e9551f17-aa01-44d4-b1bd-96ce8d92906f": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "Orders" + } + }, + "b86d8cee-b856-4461-8766-cfc4cb41d67f": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "Customers" + } + }, + "36254277-7ab4-4e9d-9409-5570705349a3": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "OrderLines" + } + }, + "72f6e4d6-a373-4657-9922-0b0752282ed6": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "CustomerTransactions" + } + }, + "cb798bd4-dd41-4e73-a608-7043c3fae427": { + "path": { + "database": "WideWorldImporters", + "schema": "Application", + "table": "People" + } + } + } + }, + "connectionDetails": {}, + "id": "d2bac673378a4060a14312eaf7c01d91" + }, + "schema": { + "id": "9e929ddbb2fd47a695b24131a75d0af4", + "full": { + "databases": { + "WideWorldImporters": { + "schemas": { + "Warehouse": { + "tables": { + "StockGroups": { + "type": "TABLE", + "size": -1, + "columns": { + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Warehouse_StockGroups_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 5, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Warehouse_StockGroups" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "StockGroupName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 4, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Warehouse_StockGroups": [ + "StockGroupID" + ], + "UQ_Warehouse_StockGroups_StockGroupName": [ + "StockGroupName" + ] + }, + "nonUniqueIndexes": {} + }, + "StockItems": { + "type": "TABLE", + "size": -1, + "columns": { + "RecommendedRetailPrice": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 15, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Size": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Warehouse_StockItems" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "Photo": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "varbinary", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 19, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsChillerStock": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ColorID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Warehouse_StockItems_ColorID_Warehouse_Colors", + "schema": "Warehouse", + "table": "Colors", + "column": "ColorID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "SupplierID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Warehouse_StockItems_SupplierID_Purchasing_Suppliers", + "schema": "Purchasing", + "table": "Suppliers", + "column": "SupplierID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "QuantityPerOuter": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "MarketingComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TaxRate": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 13, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 24, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OuterPackageID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Warehouse_StockItems_OuterPackageID_Warehouse_PackageTypes", + "schema": "Warehouse", + "table": "PackageTypes", + "column": "PackageTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "UnitPackageID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Warehouse_StockItems_UnitPackageID_Warehouse_PackageTypes", + "schema": "Warehouse", + "table": "PackageTypes", + "column": "PackageTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "Tags": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 21, + "nullable": true, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TypicalWeightPerUnit": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 16, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LeadTimeDays": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SearchDetails": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 22, + "nullable": false, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Brand": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "UnitPrice": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 14, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomFields": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 20, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 23, + "foreignKey": { + "name": "FK_Warehouse_StockItems_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Barcode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 25, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + } + }, + "uniqueIndexes": { + "UQ_Warehouse_StockItems_StockItemName": [ + "StockItemName" + ], + "PK_Warehouse_StockItems": [ + "StockItemID" + ] + }, + "nonUniqueIndexes": { + "FK_Warehouse_StockItems_OuterPackageID": [ + "OuterPackageID" + ], + "FK_Warehouse_StockItems_UnitPackageID": [ + "UnitPackageID" + ], + "FK_Warehouse_StockItems_SupplierID": [ + "SupplierID" + ], + "FK_Warehouse_StockItems_ColorID": [ + "ColorID" + ] + } + }, + "StockItemStockGroups": { + "type": "TABLE", + "size": -1, + "columns": { + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Warehouse_StockItemStockGroups_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Warehouse_StockItemStockGroups_StockItemID_Warehouse_StockItems", + "schema": "Warehouse", + "table": "StockItems", + "column": "StockItemID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "StockGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Warehouse_StockItemStockGroups_StockGroupID_Warehouse_StockGroups", + "schema": "Warehouse", + "table": "StockGroups", + "column": "StockGroupID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 5, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemStockGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Warehouse_StockItemStockGroups" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + } + }, + "uniqueIndexes": { + "UQ_StockItemStockGroups_StockGroupID_Lookup": [ + "StockGroupID", + "StockItemID" + ], + "PK_Warehouse_StockItemStockGroups": [ + "StockItemStockGroupID" + ], + "UQ_StockItemStockGroups_StockItemID_Lookup": [ + "StockItemID", + "StockGroupID" + ] + }, + "nonUniqueIndexes": {} + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + }, + "Purchasing": { + "tables": { + "Suppliers": { + "type": "TABLE", + "size": -1, + "columns": { + "PrimaryContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_PrimaryContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "DeliveryMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_DeliveryMethodID_Application_DeliveryMethods", + "schema": "Application", + "table": "DeliveryMethods", + "column": "DeliveryMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PostalAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 24, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankAccountBranch": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 25, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PaymentDays": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 15, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankInternationalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 14, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Purchasing_Suppliers" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "DeliveryPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 22, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankAccountCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 26, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FaxNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 28, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryLocation": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "geography", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 23, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierReference": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 8, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_PostalCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "DeliveryCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 7, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_DeliveryCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 16, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "WebsiteURL": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 256, + "scale": 0 + }, + "ordinalPosition": 19, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierCategoryID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_SupplierCategoryID_Purchasing_SupplierCategories", + "schema": "Purchasing", + "table": "SupplierCategories", + "column": "SupplierCategoryID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "BankAccountName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 27, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 29, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "AlternateContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_AlternateContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PhoneNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 21, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 20, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankAccountNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 13, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "UQ_Purchasing_Suppliers_SupplierName": [ + "SupplierName" + ], + "PK_Purchasing_Suppliers": [ + "SupplierID" + ] + }, + "nonUniqueIndexes": { + "FK_Purchasing_Suppliers_DeliveryMethodID": [ + "DeliveryMethodID" + ], + "FK_Purchasing_Suppliers_DeliveryCityID": [ + "DeliveryCityID" + ], + "FK_Purchasing_Suppliers_PostalCityID": [ + "PostalCityID" + ], + "FK_Purchasing_Suppliers_PrimaryContactPersonID": [ + "PrimaryContactPersonID" + ], + "FK_Purchasing_Suppliers_SupplierCategoryID": [ + "SupplierCategoryID" + ], + "FK_Purchasing_Suppliers_AlternateContactPersonID": [ + "AlternateContactPersonID" + ] + } + }, + "PurchaseOrders": { + "type": "TABLE", + "size": -1, + "columns": { + "SupplierID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_SupplierID_Purchasing_Suppliers", + "schema": "Purchasing", + "table": "Suppliers", + "column": "SupplierID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 11, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Comments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_DeliveryMethodID_Application_DeliveryMethods", + "schema": "Application", + "table": "DeliveryMethods", + "column": "DeliveryMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PurchaseOrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Purchasing_PurchaseOrders" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "ContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_ContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "ExpectedDeliveryDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierReference": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsOrderFinalized": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 12, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OrderDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Purchasing_PurchaseOrders": [ + "PurchaseOrderID" + ] + }, + "nonUniqueIndexes": { + "FK_Purchasing_PurchaseOrders_ContactPersonID": [ + "ContactPersonID" + ], + "FK_Purchasing_PurchaseOrders_DeliveryMethodID": [ + "DeliveryMethodID" + ], + "FK_Purchasing_PurchaseOrders_SupplierID": [ + "SupplierID" + ] + } + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + }, + "Sales": { + "tables": { + "Orders": { + "type": "TABLE", + "size": -1, + "columns": { + "BackorderOrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Sales_Orders_BackorderOrderID_Sales_Orders", + "schema": "Sales", + "table": "Orders", + "column": "OrderID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryInstructions": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsUndersupplyBackordered": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Comments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 13, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_Orders_ContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Sales_Orders_CustomerID_Sales_Customers", + "schema": "Sales", + "table": "Customers", + "column": "CustomerID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "OrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_Orders" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "OrderDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SalespersonPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_Orders_SalespersonPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PickingCompletedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 14, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 15, + "foreignKey": { + "name": "FK_Sales_Orders_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PickedByPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Sales_Orders_PickedByPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerPurchaseOrderNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ExpectedDeliveryDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 16, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Sales_Orders": [ + "OrderID" + ] + }, + "nonUniqueIndexes": { + "FK_Sales_Orders_PickedByPersonID": [ + "PickedByPersonID" + ], + "FK_Sales_Orders_SalespersonPersonID": [ + "SalespersonPersonID" + ], + "FK_Sales_Orders_CustomerID": [ + "CustomerID" + ], + "FK_Sales_Orders_ContactPersonID": [ + "ContactPersonID" + ] + } + }, + "Customers": { + "type": "TABLE", + "size": -1, + "columns": { + "PrimaryContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Sales_Customers_PrimaryContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "DeliveryMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 8, + "foreignKey": { + "name": "FK_Sales_Customers_DeliveryMethodID_Application_DeliveryMethods", + "schema": "Application", + "table": "DeliveryMethods", + "column": "DeliveryMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PostalAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 26, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomerCategoryID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Sales_Customers_CustomerCategoryID_Sales_CustomerCategories", + "schema": "Sales", + "table": "CustomerCategories", + "column": "CustomerCategoryID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PostalAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 27, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_Customers" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "PaymentDays": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 16, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "RunPosition": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 5, + "scale": 0 + }, + "ordinalPosition": 20, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 24, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 28, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FaxNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BuyingGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_Customers_BuyingGroupID_Sales_BuyingGroups", + "schema": "Sales", + "table": "BuyingGroups", + "column": "BuyingGroupID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 30, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsOnCreditHold": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 15, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "DeliveryLocation": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "geography", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 25, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 10, + "foreignKey": { + "name": "FK_Sales_Customers_PostalCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "BillToCustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_Customers_BillToCustomerID_Sales_Customers", + "schema": "Sales", + "table": "Customers", + "column": "CustomerID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "AccountOpenedDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 9, + "foreignKey": { + "name": "FK_Sales_Customers_DeliveryCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CreditLimit": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "WebsiteURL": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 256, + "scale": 0 + }, + "ordinalPosition": 21, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsStatementSent": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 14, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 29, + "foreignKey": { + "name": "FK_Sales_Customers_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 31, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "AlternateContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 7, + "foreignKey": { + "name": "FK_Sales_Customers_AlternateContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "StandardDiscountPercentage": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 13, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PhoneNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 23, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 22, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryRun": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 5, + "scale": 0 + }, + "ordinalPosition": 19, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "UQ_Sales_Customers_CustomerName": [ + "CustomerName" + ], + "PK_Sales_Customers": [ + "CustomerID" + ] + }, + "nonUniqueIndexes": { + "FK_Sales_Customers_PrimaryContactPersonID": [ + "PrimaryContactPersonID" + ], + "IX_Sales_Customers_Perf_20160301_06": [ + "IsOnCreditHold", + "CustomerID", + "BillToCustomerID" + ], + "FK_Sales_Customers_DeliveryMethodID": [ + "DeliveryMethodID" + ], + "FK_Sales_Customers_CustomerCategoryID": [ + "CustomerCategoryID" + ], + "FK_Sales_Customers_AlternateContactPersonID": [ + "AlternateContactPersonID" + ], + "FK_Sales_Customers_PostalCityID": [ + "PostalCityID" + ], + "FK_Sales_Customers_BuyingGroupID": [ + "BuyingGroupID" + ], + "FK_Sales_Customers_DeliveryCityID": [ + "DeliveryCityID" + ] + } + }, + "OrderLines": { + "type": "TABLE", + "size": -1, + "columns": { + "UnitPrice": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 7, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PickingCompletedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 10, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 11, + "foreignKey": { + "name": "FK_Sales_OrderLines_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Description": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 4, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PickedQuantity": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OrderLineID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_OrderLines" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "StockItemID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_OrderLines_StockItemID_Warehouse_StockItems", + "schema": "Warehouse", + "table": "StockItems", + "column": "StockItemID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "TaxRate": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Quantity": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Sales_OrderLines_OrderID_Sales_Orders", + "schema": "Sales", + "table": "Orders", + "column": "OrderID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PackageTypeID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_OrderLines_PackageTypeID_Warehouse_PackageTypes", + "schema": "Warehouse", + "table": "PackageTypes", + "column": "PackageTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 12, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Sales_OrderLines": [ + "OrderLineID" + ] + }, + "nonUniqueIndexes": { + "IX_Sales_OrderLines_Perf_20160301_01": [ + "PickingCompletedWhen", + "OrderID", + "OrderLineID" + ], + "FK_Sales_OrderLines_OrderID": [ + "OrderID" + ], + "IX_Sales_OrderLines_AllocatedStockItems": [ + "StockItemID" + ], + "IX_Sales_OrderLines_Perf_20160301_02": [ + "StockItemID", + "PickingCompletedWhen" + ], + "FK_Sales_OrderLines_PackageTypeID": [ + "PackageTypeID" + ] + } + }, + "CustomerTransactions": { + "type": "TABLE", + "size": -1, + "columns": { + "IsFinalized": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "AmountExcludingTax": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 7, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OutstandingBalance": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PaymentMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_PaymentMethodID_Application_PaymentMethods", + "schema": "Application", + "table": "PaymentMethods", + "column": "PaymentMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "TransactionTypeID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_TransactionTypeID_Application_TransactionTypes", + "schema": "Application", + "table": "TransactionTypes", + "column": "TransactionTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerTransactionID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_CustomerTransactions" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "CustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_CustomerID_Sales_Customers", + "schema": "Sales", + "table": "Customers", + "column": "CustomerID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "InvoiceID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_InvoiceID_Sales_Invoices", + "schema": "Sales", + "table": "Invoices", + "column": "InvoiceID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "TransactionAmount": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TaxAmount": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 13, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FinalizationDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 14, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TransactionDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Sales_CustomerTransactions": [ + "CustomerTransactionID" + ] + }, + "nonUniqueIndexes": { + "CX_Sales_CustomerTransactions": [ + "TransactionDate" + ], + "IX_Sales_CustomerTransactions_IsFinalized": [ + "IsFinalized" + ], + "FK_Sales_CustomerTransactions_CustomerID": [ + "CustomerID" + ], + "FK_Sales_CustomerTransactions_TransactionTypeID": [ + "TransactionTypeID" + ], + "FK_Sales_CustomerTransactions_PaymentMethodID": [ + "PaymentMethodID" + ], + "FK_Sales_CustomerTransactions_InvoiceID": [ + "InvoiceID" + ] + } + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + }, + "Application": { + "tables": { + "People": { + "type": "TABLE", + "size": -1, + "columns": { + "UserPreferences": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SearchName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 101, + "scale": 0 + }, + "ordinalPosition": 4, + "nullable": false, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Photo": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "varbinary", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 16, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsSystemUser": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "EmailAddress": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 256, + "scale": 0 + }, + "ordinalPosition": 15, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomFields": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 19, + "foreignKey": { + "name": "FK_Application_People_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Application_People" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "IsPermittedToLogon": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 5, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "IsEmployee": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 21, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FullName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "FaxNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 14, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OtherLanguages": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": true, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PhoneNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 13, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 20, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsExternalLogonProvider": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PreferredName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 3, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LogonName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "HashedPassword": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "varbinary", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsSalesperson": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Application_People": [ + "PersonID" + ] + }, + "nonUniqueIndexes": { + "IX_Application_People_Perf_20160301_05": [ + "IsPermittedToLogon", + "PersonID" + ], + "IX_Application_People_IsEmployee": [ + "IsEmployee" + ], + "IX_Application_People_IsSalesperson": [ + "IsSalesperson" + ], + "IX_Application_People_FullName": [ + "FullName" + ] + } + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + } + } + } + }, + "metadata": { + "databaseType": "SQL_SERVER", + "databaseVersion": "16.00.4245" + } + }, + "imported": { + "WideWorldImporters": { + "Warehouse": { + "StockGroups": {}, + "StockItems": {}, + "StockItemStockGroups": {} + }, + "Purchasing": { + "Suppliers": {}, + "PurchaseOrders": {} + }, + "Sales": { + "Orders": {}, + "Customers": {}, + "OrderLines": {}, + "CustomerTransactions": {} + }, + "Application": { + "People": {} + } + } + }, + "all": { + "WideWorldImporters": { + "Warehouse": { + "PackageTypes": {}, + "StockGroups": {}, + "ColdRoomTemperatures_Archive": {}, + "StockItems": {}, + "StockItemStockGroups": {}, + "Colors_Archive": {}, + "Colors": {}, + "PackageTypes_Archive": {}, + "StockItemTransactions": {}, + "StockItems_Archive": {}, + "ColdRoomTemperatures": {}, + "StockItemHoldings": {}, + "StockGroups_Archive": {}, + "VehicleTemperatures": {} + }, + "Purchasing": { + "SupplierTransactions": {}, + "Suppliers": {}, + "PurchaseOrderLines": {}, + "PurchaseOrders": {}, + "SupplierCategories_Archive": {}, + "SupplierCategories": {}, + "Suppliers_Archive": {} + }, + "Sales": { + "BuyingGroups": {}, + "Orders": {}, + "InvoiceLines": {}, + "CustomerCategories": {}, + "CustomerCategories_Archive": {}, + "Customers": {}, + "BuyingGroups_Archive": {}, + "OrderLines": {}, + "SpecialDeals": {}, + "CustomerTransactions": {}, + "Customers_Archive": {}, + "Invoices": {} + }, + "Application": { + "StateProvinces_Archive": {}, + "Countries": {}, + "People": {}, + "TransactionTypes_Archive": {}, + "Countries_Archive": {}, + "PaymentMethods": {}, + "PaymentMethods_Archive": {}, + "People_Archive": {}, + "Cities_Archive": {}, + "StateProvinces": {}, + "DeliveryMethods_Archive": {}, + "SystemParameters": {}, + "TransactionTypes": {}, + "DeliveryMethods": {}, + "Cities": {} + } + } + } + } +} \ No newline at end of file diff --git a/services/etl/mongodb/UOM-WideWorldImporters_SQL_SERVER_config.sql b/services/etl/mongodb/UOM-WideWorldImporters_SQL_SERVER_config.sql new file mode 100644 index 0000000..8197395 --- /dev/null +++ b/services/etl/mongodb/UOM-WideWorldImporters_SQL_SERVER_config.sql @@ -0,0 +1,25 @@ +/* +* Relational Migrator needs source database to allow change data capture. +* The following scripts must be executed on MS-SQL source database before starting migration to enable change data capture. +* See https://docs.microsoft.com/en-us/sql/relational-databases/track-changes/about-change-data-capture-sql-server?view=sql-server-ver16 for more details on CDC. +* +* Prerequisites for running this script: +* You are a member of the sysadmin fixed server role for the SQL Server. +* You are a db_owner of the database. +*/ + +/* +* Set up CDC for the database WideWorldImporters. +*/ +USE WideWorldImporters +GO +IF (N'WideWorldImporters') NOT in (SELECT name FROM sys.databases WHERE is_cdc_enabled = 1) +EXEC sys.sp_cdc_enable_db; +GO + + + +/* On SQL Server, parameters that control the behavior of the capture job agent are defined in the SQL Server table msdb.dbo.cdc_jobs. +* If you experience performance issues while running the capture job agent, adjust capture jobs settings to reduce CPU load by running the sys.sp_cdc_change_job stored procedure and supplying new values. +* See https://debezium.io/documentation/reference/stable/connectors/sqlserver.html#_sql_server_capture_job_agent_configuration_parameters for more details +*/ \ No newline at end of file diff --git a/services/etl/mongodb/config-sql-server.sh b/services/etl/mongodb/config-sql-server.sh new file mode 100644 index 0000000..af15f0e --- /dev/null +++ b/services/etl/mongodb/config-sql-server.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Ensure script stops on error and treats unset variables as errors +set -euo pipefail + +# Use -i to read from stdin, -C to trust server certificate +# Using 'docker compose exec' instead of 'docker exec' to target the service correctly +docker exec -i universal-object-mapping-mssql_db-1 /opt/mssql-tools18/bin/sqlcmd \ + -S localhost \ + -U SA \ + -P "${MSSQL_SA_PASSWORD:-Testingorms123}" \ + -C <"${SQL_SERVER_CONFIG_PATH:-UOM-WideWorldImporters_SQL_SERVER_config.sql}" diff --git a/services/etl/mongodb/init-mongodb.sh b/services/etl/mongodb/init-mongodb.sh new file mode 100644 index 0000000..54ed40c --- /dev/null +++ b/services/etl/mongodb/init-mongodb.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +mongo -- "$MONGO_INITDB_DATABASE" <&1 | tee -a "$log_file" || { + echo "[$(date +"%Y-%m-%d %T")] Neo4j ETL Export failed" |& tee -a "$log_file" +} + +# The ETL CLI script sometimes eats error codes on failure. Let's explicitly check if output was created. +docker exec -u 0 -it "$container_name" test -d "$output_dir" || { + echo "[$(date +"%Y-%m-%d %T")] Error: Output directory not created. Export likely failed despite exit code 0." |& tee -a "$log_file" + exit 1 +} + +# Step 2: Preprocess the CSV files (remove headers for compatibility with neo4j-admin import) +echo "[$(date +"%Y-%m-%d %T")] Preprocessing import params..." |& tee -a "$log_file" +docker exec -u 0 -it "$container_name" chmod -R 777 "$output_dir" +docker exec -u 0 -it "$container_name" sed -i '1,2d' "$output_dir/csv-001/neo4j-admin-import-params" + +# Step 3: Stop the database if possible (required for full bulk import) +# Note: In Community Edition, this may not be supported while keeping the container running. +echo "[$(date +"%Y-%m-%d %T")] Attempting to stop database for import..." |& tee -a "$log_file" +docker exec -u 0 -it "$container_name" neo4j stop 2>&1 | tee -a "$log_file" || echo "[$(date +"%Y-%m-%d %T")] Note: Neo4j database could not be stopped. Continuing anyway..." + +# Step 4: Import the CSVs into Neo4j +echo "[$(date +"%Y-%m-%d %T")] Neo4j bulk import started" |& tee -a "$log_file" +docker exec -u 0 -it "$container_name" /var/lib/neo4j/bin/neo4j-admin database import full \ + --verbose \ + --overwrite-destination \ + @"$output_dir/csv-001/neo4j-admin-import-params" 2>&1 | tee -a "$log_file" || { + echo "[$(date +"%Y-%m-%d %T")] Neo4j bulk import failed" |& tee -a "$log_file" + exit 1 +} +echo "[$(date +"%Y-%m-%d %T")] Neo4j ETL process finished successfully" |& tee -a "$log_file" + +cp "$mapping_file" "../../orchestrator/src/context/mssql_neo4j.json" 2>&1 | tee -a "$log_file" || echo "[$(date +"%Y-%m-%d %T")] Warning: Failed to copy mapping file to orchestrator context directory. Please copy it manually." +echo "[$(date +"%Y-%m-%d %T")] Mapping file copied to orchestrator context directory" |& tee -a "$log_file" + +# Step 5: Restart the database +echo "[$(date +"%Y-%m-%d %T")] Restarting Neo4j database..." |& tee -a "$log_file" +docker exec -u 0 -it "$container_name" neo4j start 2>&1 | tee -a "$log_file" || + docker exec -u 0 -it "$container_name" neo4j restart 2>&1 | tee -a "$log_file" || + echo "[$(date +"%Y-%m-%d %T")] Note: Neo4j database could not be re/started. Please restart it manually." + +# Step 6: Configure the database +echo "[$(date +"%Y-%m-%d %T")] Running database configuration script..." |& tee -a "$log_file" +docker exec -u 0 -it "$container_name" /bin/bash -c $(cat ./configure-db.sh) 2>&1 | tee -a "$log_file" +echo "[$(date +"%Y-%m-%d %T")] Database configuration script completed" |& tee -a "$log_file" diff --git a/services/java-services/.dockerignore b/services/java-services/.dockerignore new file mode 100644 index 0000000..d809aa8 --- /dev/null +++ b/services/java-services/.dockerignore @@ -0,0 +1,37 @@ +.git +.gitignore +.dockerignore +Dockerfile + +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/services/java-services/.envrc b/services/java-services/.envrc new file mode 100644 index 0000000..3d9d49b --- /dev/null +++ b/services/java-services/.envrc @@ -0,0 +1,4 @@ +# shellcheck disable=SC2148 +[[ -f ~/.envrc.local ]] && source_env ~/.envrc.local + +dotenv_if_exists .env.dev diff --git a/services/java-services/.gitattributes b/services/java-services/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/services/java-services/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/services/java-services/.gitignore b/services/java-services/.gitignore new file mode 100644 index 0000000..71e3fbc --- /dev/null +++ b/services/java-services/.gitignore @@ -0,0 +1,38 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +.env +.env.dev +misc/ +results/ diff --git a/services/java-services/.mvn/wrapper/maven-wrapper.properties b/services/java-services/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8dea6c2 --- /dev/null +++ b/services/java-services/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/services/java-services/Dockerfile b/services/java-services/Dockerfile new file mode 100644 index 0000000..8b98c09 --- /dev/null +++ b/services/java-services/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:rolling + +ENV DEBIAN_FRONTEND=noninteractive + +WORKDIR /sandbox + +# Install OpenJDK 25, Maven and OpenSSH Server +RUN apt-get update && \ + apt-get install -y --no-install-recommends openjdk-25-jdk maven openssh-server && \ + mkdir -p /var/run/sshd && \ + useradd -d /sandbox -s /bin/rbash sandbox && \ + echo 'sandbox:sandbox' | chpasswd && \ + echo 'root:root' | chpasswd && \ + sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config && \ + echo 'Match User sandbox' >> /etc/ssh/sshd_config && \ + echo ' ChrootDirectory none' >> /etc/ssh/sshd_config && \ + rm -rf /var/lib/apt/lists/* + +# Copy the sandbox-pom.xml +COPY ./sandbox-pom.xml . + +# Resolve dependencies +RUN chmod 711 /sandbox && mvn dependency:resolve -f sandbox-pom.xml --batch-mode + +# Change user +RUN chown -R sandbox:sandbox /sandbox + +EXPOSE 22 + +CMD ["/usr/sbin/sshd", "-D", "-o", "ListenAddress=0.0.0.0"] diff --git a/services/java-services/JavaServicesApplication.http b/services/java-services/JavaServicesApplication.http new file mode 100644 index 0000000..e37ff8e --- /dev/null +++ b/services/java-services/JavaServicesApplication.http @@ -0,0 +1,49 @@ +### Compile Single Class (Success) +POST http://localhost:8090/api/compiler/compile +Content-Type: application/json + +{ + "sourceCode": "import org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\n@Document(collection = \"orders\")\nclass Order {\n @Id\n private String id;\n private String customerName;\n\n public Order() {}\n public String getId() { return id; }\n public void setId(String id) { this.id = id; }\n public String getCustomerName() { return customerName; }\n public void setCustomerName(String customerName) { this.customerName = customerName; }\n}", + "framework": "spring-data-mongodb", + "validate": false +} + +### Compile Multi-Class Complex Schema (Success) +POST http://localhost:8090/api/compiler/compile +Content-Type: application/json + +{ + "sourceCode": "import java.math.BigDecimal;\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\nimport org.springframework.data.mongodb.core.mapping.Field;\n\n@Document(collection = \"orders\")\nclass Order {\n @Id\n private String id;\n @Field(\"orderId\")\n private Integer orderId;\n @Field(\"customerId\")\n private Integer customerId;\n @Field(\"orderDate\")\n private LocalDateTime orderDate;\n private Customer customer;\n public Order() {}\n public String getId() { return id; }\n}\n\nclass Customer {\n private Integer customerId;\n private String customerName;\n private BigDecimal creditLimit;\n private List customerTransactions = new ArrayList<>();\n public Customer() {}\n}\n\nclass CustomerTransaction {\n private Integer customerTransactionId;\n private BigDecimal transactionAmount;\n public CustomerTransaction() {}\n}\n\n@Document(collection = \"orderLines\")\nclass OrderLine {\n @Id\n private String id;\n @Field(\"orderLineId\")\n private Integer orderLineId;\n private Integer quantity;\n private BigDecimal unitPrice;\n private StockItem stockItem;\n public OrderLine() {}\n}\n\nclass StockItem {\n private Integer stockItemId;\n private String stockItemName;\n public StockItem() {}\n}", + "framework": "spring-data-mongodb", + "validate": false +} + +### Compile + Validate vs Live MongoDB (Success) +POST http://localhost:8090/api/compiler/compile +Content-Type: application/json + +{ + "sourceCode": "import org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\n@Document(collection = \"orders\")\nclass Order {\n @Id\n private String id;\n public Order() {}\n}", + "framework": "spring-data-mongodb", + "validate": true +} + +### Compilation Error Case (Expected Failure) +POST http://localhost:8090/api/compiler/compile +Content-Type: application/json + +{ + "sourceCode": "import org.springframework.data.mongodb.core.mapping.Document;\n\n@Document(collection = \"orders\")\nclass Order {\n private UndefinedType foo;\n}", + "framework": "spring-data-mongodb", + "validate": false +} + +### Maven Fallback Compilation (Success) +POST http://localhost:8090/api/compiler/maven-compile +Content-Type: application/json + +{ + "sourceCode": "import org.springframework.data.annotation.Id;\nimport org.springframework.data.mongodb.core.mapping.Document;\n\n@Document(collection = \"orders\")\nclass Order {\n @Id\n private String id;\n public Order() {}\n}", + "framework": "spring-data-mongodb", + "validate": false +} diff --git a/services/java-services/mongo_sandbox/pom.xml b/services/java-services/mongo_sandbox/pom.xml new file mode 100644 index 0000000..d945577 --- /dev/null +++ b/services/java-services/mongo_sandbox/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + uom.services + benchmarks + 0.0.1-SNAPSHOT + + + uom.services + mongo_sandbox + 1.0-SNAPSHOT + + + Minimal Maven project for MongoDB sandbox compilation of LLM-generated Java code. + + + + 25 + 25 + 25 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + 4.0.3 + + + diff --git a/services/java-services/mongo_sandbox/src/main/java/uom/services/MongoQueryEntrypoint.java b/services/java-services/mongo_sandbox/src/main/java/uom/services/MongoQueryEntrypoint.java new file mode 100644 index 0000000..876be6d --- /dev/null +++ b/services/java-services/mongo_sandbox/src/main/java/uom/services/MongoQueryEntrypoint.java @@ -0,0 +1,500 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; +import java.util.function.*; + +import org.slf4j.*; +import org.springframework.data.annotation.*; +import org.springframework.data.domain.*; +import org.springframework.data.mongodb.*; +import org.springframework.data.mongodb.core.*; +import org.springframework.data.mongodb.core.aggregation.*; +import org.springframework.data.mongodb.core.convert.*; +import org.springframework.data.mongodb.core.mapping.*; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.query.*; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.*; +import com.mongodb.client.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_MONGO_URI = "mongodb://uom_readonly:uom_readonly@mongodb:27017/uom"; + private static final String DEFAULT_MONGO_DATABASE = "uom"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.ALWAYS)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.mongodb.core.MongoTemplate"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String defaultMongoUri() { + return System.getenv("MONGODB_URI") != null ? System.getenv("MONGODB_URI") : DEFAULT_MONGO_URI; + } + + static String defaultMongoDatabase() { + return System.getenv("MONGODB_DATABASE") != null ? System.getenv("MONGODB_DATABASE") : DEFAULT_MONGO_DATABASE; + } +} + +// --- Schema and Related Settings --- + +/** + * Order document with embedded Customer and CustomerTransactions. + * + * TRANSLATED FROM: C# EFCore Customer, CustomerTransaction entities + * ARCHITECTURAL SHIFT: Denormalized - Customers no longer a root collection. + * Instead, Customer and its transactions are embedded within Order. + */ +@Document(collection = "orders") +@JsonIgnoreProperties({ "id" }) +class Order { + + @Id + private String id; + + @Field("orderId") + private Integer orderId; + + @Field("customerId") + private Integer customerId; + + // Embedded Customer document (denormalized from Sales.Customers table) + private Customer customer; + + @ReadOnlyProperty + @DocumentReference(lazy = true, lookup = "{ 'orderId': ?#{#self.orderId} }", sort = "{ 'orderLineId': 1 }") + private List orderLines; + + // Constructors + public Order() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +/** + * Embedded Customer document within Order. + * Represents the denormalized Sales.Customers table data. + * + * TRANSLATED FROM: C# Customer entity (no longer a @Document root) + * NOTE: No @Document annotation - this is a value object embedded in Order + */ +class Customer { + + private Integer customerId; + private String customerName; + private LocalDate accountOpenedDate; + private BigDecimal creditLimit; + + // Embedded CustomerTransactions array (denormalized from Sales.CustomerTransactions table) + private List customerTransactions = new ArrayList<>(); + + // Constructors + public Customer() { + } + + // Getters and Setters + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public BigDecimal getCreditLimit() { return creditLimit; } + public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +/** + * Embedded CustomerTransaction document within Customer.customerTransactions array. + * Represents the denormalized Sales.CustomerTransactions table data. + * + * TRANSLATED FROM: C# CustomerTransaction entity + * NOTE: No @Document annotation - this is a value object embedded in Customer + */ +class CustomerTransaction { + + private Integer customerTransactionId; + private Integer customerId; + private LocalDate transactionDate; + private BigDecimal transactionAmount; + + // Constructors + public CustomerTransaction() { + } + + // Getters and Setters + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public BigDecimal getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(BigDecimal transactionAmount) { this.transactionAmount = transactionAmount; } +} + +/** + * OrderLine document. + * Maps to the 'orderLines' collection in MongoDB. + * + * TRANSLATED FROM: C# OrderLine entity + */ +@Document(collection = "orderLines") +@JsonIgnoreProperties({ "id" }) +class OrderLine { + + @Id + private String id; + + @Field("orderLineId") + private Integer orderLineId; + + @Field("orderId") + private Integer orderId; + + @Field("stockItemId") + private Integer stockItemId; + + private String description; + + @Field("packageTypeId") + private Integer packageTypeId; + + private Integer quantity; + + private BigDecimal unitPrice; + + @Field("taxRate") + private BigDecimal taxRate; + + @Field("pickedQuantity") + private Integer pickedQuantity; + + @Field("pickingCompletedWhen") + private LocalDateTime pickingCompletedWhen; + + @Field("lastEditedBy") + private Integer lastEditedBy; + + @Field("lastEditedWhen") + private LocalDateTime lastEditedWhen; + + // Constructors + public OrderLine() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getStockItemId() { return stockItemId; } + public void setStockItemId(Integer stockItemId) { this.stockItemId = stockItemId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getPackageTypeId() { return packageTypeId; } + public void setPackageTypeId(Integer packageTypeId) { this.packageTypeId = packageTypeId; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public BigDecimal getUnitPrice() { return unitPrice; } + public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; } + public BigDecimal getTaxRate() { return taxRate; } + public void setTaxRate(BigDecimal taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public LocalDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(LocalDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public Integer getLastEditedBy() { return lastEditedBy; } + public void setLastEditedBy(Integer lastEditedBy) { this.lastEditedBy = lastEditedBy; } + public LocalDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(LocalDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class MongoTemplateFactory { + private MongoTemplateFactory() { + } + + static MongoTemplate create(String mongoUri, String mongoDatabase) { + MongoDatabaseFactory databaseFactory = new SimpleMongoClientDatabaseFactory(MongoClients.create(mongoUri), mongoDatabase); + MongoCustomConversions customConversions = MongoCustomConversions.create(configuration -> {}); + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); + mappingContext.afterPropertiesSet(); + + MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(databaseFactory), mappingContext); + converter.setCustomConversions(customConversions); + converter.setTypeMapper(new DefaultMongoTypeMapper(null)); + converter.afterPropertiesSet(); + + return new MongoTemplate(databaseFactory, converter); + } +} + +// --- Query Entrypoint --- + +record CountProjection(Long count) { +} + +interface Query5Projection { + Integer getOrderLineId(); + Integer getQuantity(); +} + +final class Query1 { + public static Query query() { + LocalDate from = LocalDate.of(2014, 12, 20); + LocalDate to = LocalDate.of(2014, 12, 31); + return new Query(Criteria.where("pickingCompletedWhen").gte(from).lte(to)); + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, OrderLine.class); + Object first = null; + if (count > 0) { + first = template.findOne(query().with(Sort.by(Sort.Direction.ASC, "orderLineId")).limit(1), OrderLine.class); + } + Object last = null; + if (count > 1) { + last = template.findOne(query().with(Sort.by(Sort.Direction.DESC, "orderLineId")).limit(1), OrderLine.class); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(OrderLine.class), "filter", q.getQueryObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query2 { + public static Query query() { + return new Query(Criteria.where("customerId").is(1)); + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, Order.class); + Object first = null; + if (count > 0) { + first = template.findOne(query().with(Sort.by(Sort.Direction.ASC, "orderId")).limit(1), Order.class); + } + Object last = null; + if (count > 1) { + last = template.findOne(query().with(Sort.by(Sort.Direction.DESC, "orderId")).limit(1), Order.class); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(Order.class), "filter", q.getQueryObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query3 { + public static TypedAggregation query() { + return Aggregation.newAggregation( + OrderLine.class, + Aggregation.group("taxRate").count().as("count"), + Aggregation.project("count").and("taxRate").previousOperation(), + Aggregation.sort(Sort.Direction.DESC, "count") + ); + } + + public static Map harness(MongoTemplate template) { + var baseAgg = query(); + + var countOps = new ArrayList<>(baseAgg.getPipeline().getOperations()); + countOps.add(Aggregation.count().as("count")); + var countAgg = Aggregation.newAggregation(OrderLine.class, countOps); + var countResult = template.aggregate(countAgg, OrderLine.class, CountProjection.class).getUniqueMappedResult(); + var count = countResult != null ? countResult.count() : 0L; + + Object first = null; + if (count > 0) { + var agg = Aggregation.newAggregation(query().getPipeline().add(Aggregation.sort(Sort.Direction.ASC, "taxRate")).add(Aggregation.limit(1)).getOperations()); + first = template.aggregate(agg, template.getCollectionName(OrderLine.class), Object.class).getUniqueMappedResult(); + } + Object last = null; + if (count > 1) { + var desc = Aggregation.newAggregation(query().getPipeline().add(Aggregation.sort(Sort.Direction.DESC, "taxRate")).add(Aggregation.limit(1)).getOperations()); + last = template.aggregate(desc, template.getCollectionName(OrderLine.class), Object.class).getUniqueMappedResult(); + } + return Map.of("mongoAggregation", Map.of("collection", template.getCollectionName(OrderLine.class), "pipeline", baseAgg.toString()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query4 { + public static Query query() { + return new Query().with(Sort.by(Sort.Direction.DESC, "quantity")).limit(50); + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, OrderLine.class); + + Object first = null; + if (count > 0) { + Query firstQ = q.with(Sort.by(Sort.Direction.ASC, "orderLineId")).limit(1); + first = template.findOne(firstQ, OrderLine.class); + } + Object last = null; + if (count > 1) { + Query lastQ = q.with(Sort.by(Sort.Direction.ASC, "orderLineId")).skip(count - 1).limit(1); + last = template.findOne(lastQ, OrderLine.class); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(OrderLine.class), "filter", q.getQueryObject(), "sort", q.getSortObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query5 { + public static Query query() { + Query q = new Query(); + q.fields().include("orderLineId", "quantity"); + return q; + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, OrderLine.class); + + Object first = null; + if (count > 0) { + Query asc = query().with(Sort.by(Sort.Direction.ASC, "orderLineId")).limit(1); + first = template.query(OrderLine.class).as(Query5Projection.class).matching(asc).firstValue(); + } + Object last = null; + if (count > 1) { + Query desc = query().with(Sort.by(Sort.Direction.DESC, "orderLineId")).limit(1); + last = template.query(OrderLine.class).as(Query5Projection.class).matching(desc).firstValue(); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(OrderLine.class), "filter", q.getQueryObject(), "fields", q.getFieldsObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +public class MongoQueryEntrypoint { + public static void validateMongoEntity(Class entityClass, MongoTemplate mongoTemplate) { + System.out.println("Validating MongoDB entity: " + entityClass.getSimpleName()); + var jsonMapper = QueryRuntimeSupport.createJsonMapper().rebuild().changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.NON_EMPTY)).build(); + Query query = new Query().limit(1); + // This will throw MappingException or similar if the entity mapping is invalid or if the collection doesn't exist / has incompatible data + System.out.println(jsonMapper.writeValueAsString(mongoTemplate.findOne(query, entityClass))); + System.out.println("Successfully validated MongoDB entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + var mongoUri = args.length > 0 ? args[0] : QueryRuntimeSupport.defaultMongoUri(); + var mongoDatabase = args.length > 1 ? args[1] : QueryRuntimeSupport.defaultMongoDatabase(); + QueryRuntimeSupport.configureLogger(); + var template = MongoTemplateFactory.create(mongoUri, mongoDatabase); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + // Only validate @Document aggregate roots - Order and OrderLine. Customer and CustomerTransaction are embedded value objects. + validateMongoEntity(Order.class, template); + validateMongoEntity(OrderLine.class, template); + + // Now create and execute the queries and capture results + var results = new LinkedHashMap(); + List>> harnesses = List.of( + () -> Query1.harness(template), + () -> Query2.harness(template), + () -> Query3.harness(template), + () -> Query4.harness(template), + () -> Query5.harness(template) + ); + + int idx = 0; + for (var harness : harnesses) { + idx += 1; + System.out.println("Executing query" + idx + "..."); + try { + results.put("query" + idx, harness.get()); + System.out.println("Successfully executed query" + idx); + } catch (Exception e) { + System.err.println("Error occurred while executing query" + idx); + e.printStackTrace(); + results.put("query" + idx, Map.of("error", e.toString())); + } + } + QueryRuntimeSupport.createJsonMapper().writeValue(new java.io.File(System.getenv("MONGO_RESULTS_PATH") + "/mongo_results_" + System.currentTimeMillis() + ".json"), results); + } +} diff --git a/services/java-services/mongo_sandbox/src/main/java/uom/services/MongoSchemaValidationEntrypoint.java b/services/java-services/mongo_sandbox/src/main/java/uom/services/MongoSchemaValidationEntrypoint.java new file mode 100644 index 0000000..dec0a5a --- /dev/null +++ b/services/java-services/mongo_sandbox/src/main/java/uom/services/MongoSchemaValidationEntrypoint.java @@ -0,0 +1,356 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; + +import org.slf4j.*; +import org.springframework.data.annotation.*; +import org.springframework.data.mongodb.*; +import org.springframework.data.mongodb.core.*; +import org.springframework.data.mongodb.core.convert.*; +import org.springframework.data.mongodb.core.mapping.*; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.query.*; + +import com.fasterxml.jackson.annotation.JsonInclude.*; +import com.mongodb.client.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_MONGO_URI = "mongodb://uom_readonly:uom_readonly@mongodb:27017/uom"; + private static final String DEFAULT_MONGO_DATABASE = "uom"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.NON_EMPTY)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.mongodb.core.MongoTemplate"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String defaultMongoUri() { + return System.getenv("MONGODB_URI") != null ? System.getenv("MONGODB_URI") : DEFAULT_MONGO_URI; + } + + static String defaultMongoDatabase() { + return System.getenv("MONGODB_DATABASE") != null ? System.getenv("MONGODB_DATABASE") : DEFAULT_MONGO_DATABASE; + } +} + +// --- Schema and Related Settings --- + +/** + * Order document with embedded Customer and CustomerTransactions. + * + * TRANSLATED FROM: C# EFCore Customer, CustomerTransaction entities + * ARCHITECTURAL SHIFT: Denormalized - Customers no longer a root collection. + * Instead, Customer and its transactions are embedded within Order. + */ +@Document(collection = "orders") +class Order { + + @Id + private String id; + + @Field("orderId") + private Integer orderId; + + @Field("customerId") + private Integer customerId; + + // Embedded Customer document (denormalized from Sales.Customers table) + private Customer customer; + + @ReadOnlyProperty + @DocumentReference(lazy = true, lookup = "{ 'orderId': ?#{#self.orderId} }", sort = "{ 'orderLineId': 1 }") + private List orderLines; + + // Constructors + public Order() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +/** + * Embedded Customer document within Order. + * Represents the denormalized Sales.Customers table data. + * + * TRANSLATED FROM: C# Customer entity (no longer a @Document root) + * NOTE: No @Document annotation - this is a value object embedded in Order + */ +class Customer { + + private Integer customerId; + private String customerName; + private LocalDate accountOpenedDate; + private BigDecimal creditLimit; + + // Embedded CustomerTransactions array (denormalized from Sales.CustomerTransactions table) + private List customerTransactions = new ArrayList<>(); + + // Constructors + public Customer() { + } + + // Getters and Setters + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public BigDecimal getCreditLimit() { return creditLimit; } + public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +/** + * Embedded CustomerTransaction document within Customer.customerTransactions array. + * Represents the denormalized Sales.CustomerTransactions table data. + * + * TRANSLATED FROM: C# CustomerTransaction entity + * NOTE: No @Document annotation - this is a value object embedded in Customer + */ +class CustomerTransaction { + + private Integer customerTransactionId; + private Integer customerId; + private LocalDate transactionDate; + private BigDecimal transactionAmount; + + // Constructors + public CustomerTransaction() { + } + + // Getters and Setters + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public BigDecimal getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(BigDecimal transactionAmount) { this.transactionAmount = transactionAmount; } +} + +/** + * OrderLine document. + * Maps to the 'orderLines' collection in MongoDB. + * + * TRANSLATED FROM: C# OrderLine entity + */ +@Document(collection = "orderLines") +class OrderLine { + + @Id + private String id; + + @Field("orderLineId") + private Integer orderLineId; + + @Field("orderId") + private Integer orderId; + + @Field("stockItemId") + private Integer stockItemId; + + private String description; + + @Field("packageTypeId") + private Integer packageTypeId; + + private Integer quantity; + + private BigDecimal unitPrice; + + @Field("taxRate") + private BigDecimal taxRate; + + @Field("pickedQuantity") + private Integer pickedQuantity; + + @Field("pickingCompletedWhen") + private LocalDateTime pickingCompletedWhen; + + @Field("lastEditedBy") + private Integer lastEditedBy; + + @Field("lastEditedWhen") + private LocalDateTime lastEditedWhen; + + // Constructors + public OrderLine() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getStockItemId() { return stockItemId; } + public void setStockItemId(Integer stockItemId) { this.stockItemId = stockItemId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getPackageTypeId() { return packageTypeId; } + public void setPackageTypeId(Integer packageTypeId) { this.packageTypeId = packageTypeId; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public BigDecimal getUnitPrice() { return unitPrice; } + public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; } + public BigDecimal getTaxRate() { return taxRate; } + public void setTaxRate(BigDecimal taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public LocalDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(LocalDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public Integer getLastEditedBy() { return lastEditedBy; } + public void setLastEditedBy(Integer lastEditedBy) { this.lastEditedBy = lastEditedBy; } + public LocalDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(LocalDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class MongoTemplateFactory { + private MongoTemplateFactory() { + } + + static MongoTemplate create(String mongoUri, String mongoDatabase) { + MongoDatabaseFactory databaseFactory = new SimpleMongoClientDatabaseFactory( + MongoClients.create(mongoUri), + mongoDatabase); + MongoCustomConversions customConversions = MongoCustomConversions.create(configuration -> { + }); + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); + mappingContext.afterPropertiesSet(); + + MappingMongoConverter converter = new MappingMongoConverter( + new DefaultDbRefResolver(databaseFactory), + mappingContext); + converter.setCustomConversions(customConversions); + converter.setTypeMapper(new DefaultMongoTypeMapper(null)); + converter.afterPropertiesSet(); + + return new MongoTemplate(databaseFactory, converter); + } +} + +// --- Schema Validation Entrypoint --- + +public class MongoSchemaValidationEntrypoint { + /** + * Validates a MongoDB entity by running + * {@code mongoTemplate.findOne(query, entityClass)} + * with a limit of 1. If the mapping is invalid, Spring Data MongoDB will throw. + */ + public static void validateMongoEntity(Class entityClass, MongoTemplate mongoTemplate, JsonMapper jsonMapper) { + System.out.println("Validating MongoDB entity: " + entityClass.getSimpleName()); + Query query = new Query().limit(1); + // This will throw MappingException or similar if the entity mapping is invalid or if the collection doesn't exist / has incompatible data + System.out.println(jsonMapper.writeValueAsString(mongoTemplate.findOne(query, entityClass))); + System.out.println("Successfully validated MongoDB entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + var mongoUri = args.length > 0 ? args[0] : QueryRuntimeSupport.defaultMongoUri(); + var mongoDatabase = args.length > 1 ? args[1] : QueryRuntimeSupport.defaultMongoDatabase(); + QueryRuntimeSupport.configureLogger(); + var jsonMapper = QueryRuntimeSupport.createJsonMapper(); + MongoTemplate template = MongoTemplateFactory.create(mongoUri, mongoDatabase); + + // Only validate @Document aggregate roots - Order and OrderLine. Customer and CustomerTransaction are embedded value objects. + List queries = List.of( + () -> validateMongoEntity(Order.class, template, jsonMapper), + () -> validateMongoEntity(OrderLine.class, template, jsonMapper)); + + for (var query : queries) { + try { + query.run(); + } catch (Exception e) { + System.err.println("Error occurred while validating entity"); + e.printStackTrace(); + } + } + } +} diff --git a/services/java-services/mvnw b/services/java-services/mvnw new file mode 100755 index 0000000..bd8896b --- /dev/null +++ b/services/java-services/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/services/java-services/mvnw.cmd b/services/java-services/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/services/java-services/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/services/java-services/neo4j_sandbox/pom.xml b/services/java-services/neo4j_sandbox/pom.xml new file mode 100644 index 0000000..35de756 --- /dev/null +++ b/services/java-services/neo4j_sandbox/pom.xml @@ -0,0 +1,34 @@ + + + 4.0.0 + + uom.services + benchmarks + 0.0.1-SNAPSHOT + + + uom.services + neo4j_sandbox + 1.0-SNAPSHOT + + + Minimal Maven project for Neo4j sandbox compilation of LLM-generated Java code. + + + + 25 + 25 + 25 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-data-neo4j + 4.0.3 + + + diff --git a/services/java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jQueryEntrypoint.java b/services/java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jQueryEntrypoint.java new file mode 100644 index 0000000..8186c79 --- /dev/null +++ b/services/java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jQueryEntrypoint.java @@ -0,0 +1,529 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; +import java.util.Set; +import java.util.function.*; + +import org.neo4j.cypherdsl.core.*; +import org.neo4j.cypherdsl.core.SortItem.*; +import org.neo4j.cypherdsl.core.StatementBuilder.*; +import org.neo4j.driver.*; +import org.slf4j.*; +import org.springframework.data.neo4j.core.*; +import org.springframework.data.neo4j.core.mapping.*; +import org.springframework.data.neo4j.core.schema.*; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.transaction.*; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_NEO4J_URI = "neo4j://neo4j:7687"; + private static final String DEFAULT_NEO4J_USER = "neo4j"; + private static final String DEFAULT_NEO4J_PASS = "password"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.ALWAYS)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.neo4j.cypher"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String getNeo4jUri() { + String uri = System.getenv("NEO4J_URI"); + return uri != null ? uri : DEFAULT_NEO4J_URI; + } + + static String getNeo4jUsername() { + String user = System.getenv("NEO4J_USERNAME"); + return user != null ? user : DEFAULT_NEO4J_USER; + } + + static String getNeo4jPassword() { + String pass = System.getenv("NEO4J_PASSWORD"); + return pass != null ? pass : DEFAULT_NEO4J_PASS; + } +} + +// --- Schema and Related Settings --- + +@Node("Order") +@JsonIgnoreProperties({ "id" }) +class Order { + + @Id @GeneratedValue + private String id; + + @Property("orderId") + private Integer orderId; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.OUTGOING) + private Customer customer; + + @Relationship(type = "ORDERS", direction = Relationship.Direction.INCOMING) + private List orderLines; + + public Order() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +@Node("Customer") +@JsonIgnoreProperties({ "id" }) +class Customer { + + @Id @GeneratedValue + private String id; + + @Property("customerId") + private Integer customerId; + + @Property("customerName") + private String customerName; + + @Property("accountOpenedDate") + private LocalDate accountOpenedDate; + + @Property("creditLimit") + private Double creditLimit; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.INCOMING) + private List customerTransactions; + + public Customer() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public Double getCreditLimit() { return creditLimit; } + public void setCreditLimit(Double creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +@Node("CustomerTransaction") +@JsonIgnoreProperties({ "id" }) +class CustomerTransaction { + + @Id @GeneratedValue + private String id; + + @Property("customerTransactionId") + private Integer customerTransactionId; + + @Property("transactionDate") + private LocalDate transactionDate; + + @Property("transactionAmount") + private Double transactionAmount; + + public CustomerTransaction() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public Double getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(Double transactionAmount) { this.transactionAmount = transactionAmount; } +} + +@Node("OrderLine") +@JsonIgnoreProperties({ "id" }) +class OrderLine { + + @Id @GeneratedValue + private String id; + + @Property("orderLineId") + private Integer orderLineId; + + @Property("description") + private String description; + + @Property("quantity") + private Integer quantity; + + @Property("unitPrice") + private Double unitPrice; + + @Property("taxRate") + private Double taxRate; + + @Property("pickedQuantity") + private Integer pickedQuantity; + + @Property("pickingCompletedWhen") + private ZonedDateTime pickingCompletedWhen; + + @Property("lastEditedWhen") + private ZonedDateTime lastEditedWhen; + + public OrderLine() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public Double getUnitPrice() { return unitPrice; } + public void setUnitPrice(Double unitPrice) { this.unitPrice = unitPrice; } + public Double getTaxRate() { return taxRate; } + public void setTaxRate(Double taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public ZonedDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(ZonedDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public ZonedDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(ZonedDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class Neo4jTemplateFactory { + private Neo4jTemplateFactory() { + } + + static Neo4jTemplate create(Driver driver) { + Neo4jClient client = Neo4jClient.create(driver); + var mappingContext = new Neo4jMappingContext(); + mappingContext.setInitialEntitySet(Set.of(Order.class, Customer.class, CustomerTransaction.class, OrderLine.class)); + mappingContext.afterPropertiesSet(); + + Neo4jTransactionManager transactionManager = new Neo4jTransactionManager(driver); + return new Neo4jTemplate(client, mappingContext, transactionManager); + } +} + +// --- Queries --- + +record Query3Projection(Double taxRate, Long count) { +} + +record Query5Projection(Integer orderLineId, Integer quantity) { +} + +final class Query1 { + public static BuildableStatement query(boolean returnCount) { + ZonedDateTime from = ZonedDateTime.of(2014, 12, 20, 0, 0, 0, 0, ZoneOffset.UTC); + ZonedDateTime to = ZonedDateTime.of(2014, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC); + var orderLine = Cypher.node("OrderLine").named("ol"); + var partialStatement = Cypher.match(orderLine) + .where(orderLine.property("pickingCompletedWhen").gte(Cypher.parameter("from", from))) + .and(orderLine.property("pickingCompletedWhen").lte(Cypher.parameter("to", to))); + if (returnCount) return partialStatement.returning(Cypher.count(orderLine)); + return partialStatement.returning(orderLine); + } + + public static Map harness(Neo4jTemplate template) { + long count = template.count(query(true).build()); + var q = query(false); + Object first = null; + if (count > 0) { + var asc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.ASC)).limit(1).build(); + first = template.findOne(asc, asc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + Object last = null; + if (count > 1) { + var desc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.DESC)).limit(1).build(); + last = template.findOne(desc, desc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query2 { + // Note: Avoid cartesian products, collect nodes and relationships and their properties into SDN aggregate roots/projections, see https://docs.spring.io/spring-data/neo4j/reference/appendix/custom-queries.html + public static BuildableStatement query(boolean returnCount) { + var order = Cypher.node("Order").named("o"); + var customer = Cypher.node("Customer").named("c"); + var rel1 = order.relationshipTo(customer, "CUSTOMERS").named("r1"); + var orderLine = Cypher.node("OrderLine").named("ol"); + var rel2 = order.relationshipFrom(orderLine, "ORDERS").named("r2"); + var transaction = Cypher.node("CustomerTransaction").named("ct"); + var rel3 = customer.relationshipFrom(transaction, "CUSTOMERS").named("r3"); + + var orderLines = Cypher.name("orderLines"); + var rel2List = Cypher.name("rel2List"); + var customerTransactions = Cypher.name("customerTransactions"); + var rel3List = Cypher.name("rel3List"); + + var partial = Cypher.match(rel2, rel1, rel3) + .where(customer.property("customerId").isEqualTo(Cypher.literalOf(1))) + .with(order, Cypher.collect(rel2).as(rel2List), Cypher.collect(orderLine).as(orderLines), rel1, customer, rel3, transaction) + .with(order, rel2List, orderLines, rel1, customer, Cypher.collect(rel3).as(rel3List), Cypher.collect(transaction).as(customerTransactions)); + + if (returnCount) return partial.returning(Cypher.count(order)); + return partial.returning(order.getRequiredSymbolicName(), rel2List, orderLines, rel1.getRequiredSymbolicName(), customer.getRequiredSymbolicName(), rel3List, customerTransactions); + } + + public static Map harness(Neo4jTemplate template) { + long count = template.count(query(true).build()); + var q = query(false); + Object first = null; + if (count > 0) { + var asc = ((OngoingReadingAndReturn)query(false)).orderBy(Cypher.sort(Cypher.property("o", "orderId"), Direction.ASC)).limit(1).build(); + first = template.findOne(asc, asc.getCatalog().getParameters(), Order.class).orElse(null); + } + Object last = null; + if (count > 1) { + var desc = ((OngoingReadingAndReturn)query(false)).orderBy(Cypher.sort(Cypher.property("o", "orderId"), Direction.DESC)).limit(1).build(); + last = template.findOne(desc, desc.getCatalog().getParameters(), Order.class).orElse(null); + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query3 { + public static BuildableStatement query(boolean returnCount) { + var orderLine = Cypher.node("OrderLine").named("ol"); + var withClause = Cypher.match(orderLine) + .with(orderLine.property("taxRate").as("taxRate"), Cypher.count(orderLine).as("count")); + if (returnCount) return withClause.returning(Cypher.count(Cypher.asterisk())); + return withClause.returning(Cypher.name("taxRate"), Cypher.name("count")).orderBy(Cypher.sort(Cypher.name("count"), Direction.DESC)); + } + + public static Map harness(Neo4jTemplate template, Neo4jClient client) { + long count = template.count(query(true).build()); + Object first = null; + if (count > 0) { + var asc = Cypher.call(query(false).build()).returning(Cypher.asterisk()).orderBy(Cypher.sort(Cypher.name("taxRate"), Direction.ASC)).limit(1).build(); + var firstMap = client.query(asc.getCypher()).bindAll(asc.getCatalog().getParameters()).fetch().one().orElse(null); + first = new Query3Projection((Double) firstMap.get("taxRate"), (Long) firstMap.get("count")); + } + Object last = null; + if (count > 1) { + var desc = Cypher.call(query(false).build()).returning(Cypher.asterisk()).orderBy(Cypher.sort(Cypher.name("taxRate"), Direction.DESC)).limit(1).build(); + var lastMap = client.query(desc.getCypher()).bindAll(desc.getCatalog().getParameters()).fetch().one().orElse(null); + last = new Query3Projection((Double) lastMap.get("taxRate"), (Long) lastMap.get("count")); + } + var stmt = query(false).build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query4 { + public static BuildableStatement query(boolean returnCount) { + var orderLine = Cypher.node("OrderLine").named("ol"); + var partial = Cypher.match(orderLine); + if (returnCount) return partial.returning(Cypher.count(orderLine)); + return partial.returning(orderLine).orderBy(Cypher.sort(orderLine.property("quantity"), Direction.DESC)).limit(50); + } + + public static Map harness(Neo4jTemplate template) { + long count = template.count(query(true).build()); + long actualCount = Math.min(count, 50); + + var q = query(false); + Object first = null; + if (actualCount > 0) { + var asc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.ASC)).limit(1).build(); + first = template.findOne(asc, asc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + Object last = null; + if (actualCount > 1) { + var desc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.DESC)).limit(1).build(); + last = template.findOne(desc, desc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", actualCount, "firstSample", first, "lastSample", last); + } +} + +final class Query5 { + public static BuildableStatement query(boolean returnCount) { + var orderLine = Cypher.node("OrderLine").named("ol"); + if (returnCount) return Cypher.match(orderLine).returning(Cypher.count(orderLine)); + return Cypher.match(orderLine).returning( + orderLine.property("orderLineId").as("orderLineId"), + orderLine.property("quantity").as("quantity") + ); + } + + public static Map harness(Neo4jTemplate template, Neo4jClient client) { + long count = template.count(query(true).build()); + + var q = query(false); + Object first = null; + if (count > 0) { + var asc = ((OngoingReadingAndReturn)q) + .orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.ASC)) + .limit(1).build(); + var firstMap = client.query(asc.getCypher()) + .bindAll(asc.getCatalog().getParameters()) + .fetch().one().orElse(null); + if (firstMap != null) { + Number id = (Number) firstMap.get("orderLineId"); + Number quantity = (Number) firstMap.get("quantity"); + first = new Query5Projection(id != null ? id.intValue() : null, quantity != null ? quantity.intValue() : null); + } + } + Object last = null; + if (count > 1) { + var desc = ((OngoingReadingAndReturn)q) + .orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.DESC)) + .limit(1).build(); + var lastMap = client.query(desc.getCypher()) + .bindAll(desc.getCatalog().getParameters()) + .fetch().one().orElse(null); + if (lastMap != null) { + Number id = (Number) lastMap.get("orderLineId"); + Number quantity = (Number) lastMap.get("quantity"); + last = new Query5Projection(id != null ? id.intValue() : null, quantity != null ? quantity.intValue() : null); + } + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +// --- Query Entrypoint --- + +public class Neo4jQueryEntrypoint { + + public static void validateNeo4jEntity(Class entityClass, Neo4jTemplate neo4jTemplate, JsonMapper jsonMapper) { + System.out.println("Validating Neo4j entity: " + entityClass.getSimpleName()); + var node = Cypher.node(entityClass.getSimpleName()); + System.out.println(jsonMapper.writeValueAsString(neo4jTemplate.findOne(Cypher.match(node).returning(node).limit(1).build(), Map.of(), entityClass))); + System.out.println("Successfully validated Neo4j entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + String uri = args.length > 0 ? args[0] : QueryRuntimeSupport.getNeo4jUri(); + String user = args.length > 1 ? args[1] : QueryRuntimeSupport.getNeo4jUsername(); + String pass = args.length > 2 ? args[2] : QueryRuntimeSupport.getNeo4jPassword(); + QueryRuntimeSupport.configureLogger(); + + try (Driver driver = GraphDatabase.driver(uri, AuthTokens.basic(user, pass))) { + Neo4jTemplate template = Neo4jTemplateFactory.create(driver); + Neo4jClient client = Neo4jClient.create(driver); + var jsonMapper = QueryRuntimeSupport.createJsonMapper(); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + // Validate all entities annotated with @Node + validateNeo4jEntity(Order.class, template, jsonMapper); + validateNeo4jEntity(Customer.class, template, jsonMapper); + validateNeo4jEntity(CustomerTransaction.class, template, jsonMapper); + validateNeo4jEntity(OrderLine.class, template, jsonMapper); + + // Now create and execute the queries and capture results + var results = new LinkedHashMap(); + List>> harnesses = List.of( + () -> Query1.harness(template), + () -> Query2.harness(template), + () -> Query3.harness(template, client), + () -> Query4.harness(template), + () -> Query5.harness(template, client) + ); + + int idx = 0; + for (var harness : harnesses) { + idx += 1; + System.out.println("Executing query" + idx + "..."); + try { + results.put("query" + idx, harness.get()); + System.out.println("Successfully executed query" + idx); + } catch (Exception e) { + System.err.println("Error occurred while executing query" + idx); + e.printStackTrace(); + results.put("query" + idx, Map.of("error", e.toString())); + } + } + QueryRuntimeSupport.createJsonMapper().writeValue(new java.io.File(System.getenv("NEO4J_RESULTS_PATH") + "/neo4j_results_" + System.currentTimeMillis() + ".json"), results); + } + } +} diff --git a/services/java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jSchemaValidationEntrypoint.java b/services/java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jSchemaValidationEntrypoint.java new file mode 100644 index 0000000..a5ff757 --- /dev/null +++ b/services/java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jSchemaValidationEntrypoint.java @@ -0,0 +1,323 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; +import java.util.Set; + +import org.neo4j.cypherdsl.core.*; +import org.neo4j.driver.*; +import org.slf4j.*; +import org.springframework.data.neo4j.core.*; +import org.springframework.data.neo4j.core.mapping.*; +import org.springframework.data.neo4j.core.schema.*; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.transaction.*; + +import com.fasterxml.jackson.annotation.JsonInclude.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_NEO4J_URI = "neo4j://neo4j:7687"; + private static final String DEFAULT_NEO4J_USER = "neo4j"; + private static final String DEFAULT_NEO4J_PASS = "password"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.ALWAYS)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.neo4j.cypher"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String getNeo4jUri() { + String uri = System.getenv("NEO4J_URI"); + return uri != null ? uri : DEFAULT_NEO4J_URI; + } + + static String getNeo4jUsername() { + String user = System.getenv("NEO4J_USERNAME"); + return user != null ? user : DEFAULT_NEO4J_USER; + } + + static String getNeo4jPassword() { + String pass = System.getenv("NEO4J_PASSWORD"); + return pass != null ? pass : DEFAULT_NEO4J_PASS; + } +} + +// --- Schema and Related Settings --- + +@Node("Order") +class Order { + + @Id @GeneratedValue + private String id; + + @Property("orderId") + private Integer orderId; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.OUTGOING) + private Customer customer; + + @Relationship(type = "ORDERS", direction = Relationship.Direction.INCOMING) + private List orderLines; + + public Order() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +@Node("Customer") +class Customer { + + @Id @GeneratedValue + private String id; + + @Property("customerId") + private Integer customerId; + + @Property("customerName") + private String customerName; + + @Property("accountOpenedDate") + private LocalDate accountOpenedDate; + + @Property("creditLimit") + private Double creditLimit; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.INCOMING) + private List customerTransactions; + + public Customer() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public Double getCreditLimit() { return creditLimit; } + public void setCreditLimit(Double creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +@Node("CustomerTransaction") +class CustomerTransaction { + + @Id @GeneratedValue + private String id; + + @Property("customerTransactionId") + private Integer customerTransactionId; + + @Property("transactionDate") + private LocalDate transactionDate; + + @Property("transactionAmount") + private Double transactionAmount; + + public CustomerTransaction() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public Double getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(Double transactionAmount) { this.transactionAmount = transactionAmount; } +} + +@Node("OrderLine") +class OrderLine { + + @Id @GeneratedValue + private String id; + + @Property("orderLineId") + private Integer orderLineId; + + @Property("description") + private String description; + + @Property("quantity") + private Integer quantity; + + @Property("unitPrice") + private Double unitPrice; + + @Property("taxRate") + private Double taxRate; + + @Property("pickedQuantity") + private Integer pickedQuantity; + + @Property("pickingCompletedWhen") + private ZonedDateTime pickingCompletedWhen; + + @Property("lastEditedWhen") + private ZonedDateTime lastEditedWhen; + + public OrderLine() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public Double getUnitPrice() { return unitPrice; } + public void setUnitPrice(Double unitPrice) { this.unitPrice = unitPrice; } + public Double getTaxRate() { return taxRate; } + public void setTaxRate(Double taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public ZonedDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(ZonedDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public ZonedDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(ZonedDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class Neo4jTemplateFactory { + private Neo4jTemplateFactory() { + } + + static Neo4jTemplate create(Driver driver) { + Neo4jClient client = Neo4jClient.create(driver); + var mappingContext = new Neo4jMappingContext(); + mappingContext.setInitialEntitySet(Set.of(Order.class, Customer.class, CustomerTransaction.class, OrderLine.class)); + mappingContext.afterPropertiesSet(); + + Neo4jTransactionManager transactionManager = new Neo4jTransactionManager(driver); + return new Neo4jTemplate(client, mappingContext, transactionManager); + } +} + +// --- Query Entrypoint --- + +public class Neo4jSchemaValidationEntrypoint { + /** + * Validates a Neo4j entity by running + * {@code neo4jTemplate.findAll(entityClass)} + * which translates to a MATCH query. If the mapping is invalid, Spring Data + * Neo4j + * will throw a MappingException. + */ + public static void validateNeo4jEntity(Class entityClass, Neo4jTemplate neo4jTemplate, JsonMapper jsonMapper) { + System.out.println("Validating Neo4j entity: " + entityClass.getSimpleName()); + var node = Cypher.node(entityClass.getSimpleName()); + System.out.println(jsonMapper.writeValueAsString(neo4jTemplate.findOne(Cypher.match(node).returning(node).limit(1).build(), Map.of(), entityClass))); + System.out.println("Successfully validated Neo4j entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + String uri = args.length > 0 ? args[0] : QueryRuntimeSupport.getNeo4jUri(); + String user = args.length > 1 ? args[1] : QueryRuntimeSupport.getNeo4jUsername(); + String pass = args.length > 2 ? args[2] : QueryRuntimeSupport.getNeo4jPassword(); + QueryRuntimeSupport.configureLogger(); + + try (Driver driver = GraphDatabase.driver(uri, AuthTokens.basic(user, pass))) { + Neo4jTemplate template = Neo4jTemplateFactory.create(driver); + var jsonMapper = QueryRuntimeSupport.createJsonMapper(); + List queries = List.of( + () -> validateNeo4jEntity(Order.class, template, jsonMapper), + () -> validateNeo4jEntity(Customer.class, template, jsonMapper), + () -> validateNeo4jEntity(CustomerTransaction.class, template, jsonMapper), + () -> validateNeo4jEntity(OrderLine.class, template, jsonMapper)); + for (var query : queries) { + try { + query.run(); + } catch (Exception e) { + System.err.println("Error occurred while validating entity"); + e.printStackTrace(); + } + } + } + } +} diff --git a/services/java-services/pom.xml b/services/java-services/pom.xml new file mode 100644 index 0000000..184a94d --- /dev/null +++ b/services/java-services/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + + uom.services + benchmarks + 0.0.1-SNAPSHOT + pom + + mongo_sandbox + neo4j_sandbox + + java-services + java-services + + + + + + + + + + + + + + + 25 + 5.1.0 + + + + + me.paulschwarz + spring-dotenv-bom + ${spring-dotenv-bom.version} + pom + import + + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-data-neo4j + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-mongodb-test + test + + + org.springframework.boot + spring-boot-resttestclient + test + + + org.springframework.boot + spring-boot-starter-data-neo4j-test + test + + + org.springframework.boot + spring-boot-starter-test + test + + + me.paulschwarz + springboot4-dotenv + true + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 3.0.3 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/services/java-services/sandbox-pom.xml b/services/java-services/sandbox-pom.xml new file mode 100644 index 0000000..ea616c3 --- /dev/null +++ b/services/java-services/sandbox-pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + uom.sandbox + sandbox + 1.0.0 + + + Minimal Maven project for sandbox compilation of LLM-generated Java code. + Contains the same Spring Data dependencies as the main java-services project + but without the Spring Boot plugin (compile-only, no packaging). + + + + 25 + 25 + 25 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + 4.0.3 + + + org.springframework.boot + spring-boot-starter-data-neo4j + 4.0.3 + + + diff --git a/services/java-services/src/main/java/uom/services/benchmarks/JavaServicesApplication.java b/services/java-services/src/main/java/uom/services/benchmarks/JavaServicesApplication.java new file mode 100644 index 0000000..2ced444 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/JavaServicesApplication.java @@ -0,0 +1,13 @@ +package uom.services.benchmarks; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JavaServicesApplication { + + public static void main(String[] args) { + SpringApplication.run(JavaServicesApplication.class, args); + } + +} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/config/AppConfig.java b/services/java-services/src/main/java/uom/services/benchmarks/config/AppConfig.java new file mode 100644 index 0000000..8c4371d --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/config/AppConfig.java @@ -0,0 +1,18 @@ +package uom.services.benchmarks.config; + +import org.neo4j.cypherdsl.core.renderer.Configuration; +import org.neo4j.cypherdsl.core.renderer.Dialect; +import org.springframework.context.annotation.Bean; + +@org.springframework.context.annotation.Configuration +public class AppConfig { + /* + * Factory bean that creates the org.neo4j.cypherdsl.core.renderer.Configuration + * instance + */ + @Bean + Configuration cypherDslConfiguration() { + return Configuration.newConfig() + .withDialect(Dialect.NEO4J_5).build(); + } +} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/controller/CompilationController.java b/services/java-services/src/main/java/uom/services/benchmarks/controller/CompilationController.java new file mode 100644 index 0000000..3a15b60 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/controller/CompilationController.java @@ -0,0 +1,139 @@ +package uom.services.benchmarks.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import uom.services.benchmarks.dto.CompileRequest; +import uom.services.benchmarks.dto.CompilationResult; +import uom.services.benchmarks.service.CompilationService; +import uom.services.benchmarks.service.CompilationService.CompileOutput; +import uom.services.benchmarks.service.MavenCompilationService; +import uom.services.benchmarks.service.ValidationService; + +import java.nio.file.Path; + +/** + * REST controller for compiling and validating dynamically generated Java source code. + *

+ * Provides two compilation strategies: + *

    + *
  • {@code POST /api/compiler/compile} — Fast compilation via {@code javax.tools.JavaCompiler}
  • + *
  • {@code POST /api/compiler/maven-compile} — Full Maven compilation as fallback
  • + *
+ * Both endpoints optionally validate the compiled entity mapping against live databases + * (Neo4j, MongoDB) by running sample queries. + */ +@RestController +@RequestMapping("/api/compiler") +public class CompilationController { + @Autowired + private Environment environment; + + private static final Logger log = LoggerFactory.getLogger(CompilationController.class); + + private final CompilationService compilationService; + private final MavenCompilationService mavenCompilationService; + private final ValidationService validationService; + + public CompilationController( + CompilationService compilationService, + MavenCompilationService mavenCompilationService, + ValidationService validationService + ) { + this.compilationService = compilationService; + this.mavenCompilationService = mavenCompilationService; + this.validationService = validationService; + } + + /** + * Compiles Java source code using {@code javax.tools.JavaCompiler} with the full + * Spring Data classpath. Optionally validates entity mapping against live databases. + * + * @param request the compile request containing source code, framework hint, and validate flag + * @return 200 with success result, or 400 with structured errors + */ + @PostMapping("/compile") + public ResponseEntity compile(@RequestBody CompileRequest request) { + log.info("Received compile request: framework={}, validate={}, sourceLength={}", + request.framework(), request.validate(), request.sourceCode().length()); + + CompileOutput output = compilationService.compileWithOutput(request.sourceCode()); + return handleCompileOutput(output, request); + } + + /** + * Compiles Java source code using Maven ({@code mvn compile}) as a fallback strategy. + * Slower but catches dependency resolution issues that javac might miss. + * + * @param request the compile request + * @return 200 with success result, or 400 with structured errors + */ + @PostMapping("/maven-compile") + public ResponseEntity mavenCompile(@RequestBody CompileRequest request) { + log.info("Received Maven compile request: framework={}, validate={}, sourceLength={}", + request.framework(), request.validate(), request.sourceCode().length()); + + CompileOutput output = mavenCompilationService.compileWithMaven(request.sourceCode()); + return handleCompileOutput(output, request); + } + + /** + * Common handler for compilation output: if compilation succeeded and validation + * is requested, runs schema validation against the database. Then cleans up. + */ + private ResponseEntity handleCompileOutput(CompileOutput output, CompileRequest request) { + CompilationResult result = output.result(); + Path classesDir = output.classesDir(); + + try { + // If compilation failed, return immediately + if (!result.success()) { + log.info("Compilation failed, returning errors."); + return ResponseEntity.badRequest().body(result); + } + + // If validation is not requested, return compilation success + if (!request.validate()) { + log.info("Compilation succeeded, validation not requested."); + return ResponseEntity.ok(result); + } + + // Validation requested: load compiled classes and run sample queries + log.info("Compilation succeeded, running validation against {} ...", request.framework()); + CompilationResult validationResult = validationService.validate(classesDir, request.framework()); + + if (validationResult.success()) { + // Merge compilation warnings with validation result + var allWarnings = new java.util.ArrayList<>(result.warnings()); + allWarnings.addAll(validationResult.warnings()); + return ResponseEntity.ok(CompilationResult.success( + result.message() + " " + validationResult.message(), + allWarnings + )); + } else { + // Validation failed — return as 400 with both compilation and validation info + var allWarnings = new java.util.ArrayList<>(result.warnings()); + allWarnings.addAll(validationResult.warnings()); + return ResponseEntity.badRequest().body(CompilationResult.failure( + "Compilation succeeded but validation failed: " + validationResult.message(), + validationResult.errors(), + allWarnings + )); + } + } finally { + // Always clean up the compilation output directory in production + if (classesDir != null && !environment.matchesProfiles("development")) { + // classesDir is inside the temp compile dir (e.g., /app/sandbox/compile-xxx/classes) + // We want to clean up the parent temp dir + Path tempDir = classesDir.getParent(); + compilationService.cleanupDirectory(tempDir); + } + } + } +} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/domain/TranslatedSchema.java b/services/java-services/src/main/java/uom/services/benchmarks/domain/TranslatedSchema.java new file mode 100644 index 0000000..494baf9 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/domain/TranslatedSchema.java @@ -0,0 +1,275 @@ +package uom.services.benchmarks.domain; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +/** + * Order document with embedded Customer and CustomerTransactions. + * Maps to the 'orders' collection in MongoDB. + * + * TRANSLATED FROM: C# EFCore Customer, CustomerTransaction entities + * ARCHITECTURAL SHIFT: Denormalized - Customers no longer a root collection. + * Instead, Customer and its transactions are embedded within Order. + */ +@Document(collection = "orders") +class Order { + + @Id + private String id; + + @Field("orderId") + private Integer orderId; + + @Field("customerId") + private Integer customerId; + + @Field("orderDate") + private LocalDateTime orderDate; + + @Field("expectedDeliveryDate") + private LocalDate expectedDeliveryDate; + + // Embedded Customer document (denormalized from Sales.Customers table) + private Customer customer; + + // Constructors + public Order() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + + public LocalDateTime getOrderDate() { return orderDate; } + public void setOrderDate(LocalDateTime orderDate) { this.orderDate = orderDate; } + + public LocalDate getExpectedDeliveryDate() { return expectedDeliveryDate; } + public void setExpectedDeliveryDate(LocalDate expectedDeliveryDate) { this.expectedDeliveryDate = expectedDeliveryDate; } + + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } +} + +/** + * Embedded Customer document within Order. + * Represents the denormalized Sales.Customers table data. + * + * TRANSLATED FROM: C# Customer entity (no longer a @Document root) + * NOTE: No @Document annotation - this is a value object embedded in Order + */ +class Customer { + + private Integer customerId; + private String customerName; + private LocalDate accountOpenedDate; + private BigDecimal creditLimit; + + // Embedded CustomerTransactions array (denormalized from Sales.CustomerTransactions table) + private List customerTransactions = new ArrayList<>(); + + // Constructors + public Customer() { + } + + // Getters and Setters + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + + public BigDecimal getCreditLimit() { return creditLimit; } + public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +/** + * Embedded CustomerTransaction document within Customer.customerTransactions array. + * Represents the denormalized Sales.CustomerTransactions table data. + * + * TRANSLATED FROM: C# CustomerTransaction entity + * NOTE: No @Document annotation - this is a value object embedded in Customer + */ +class CustomerTransaction { + + private Integer customerTransactionId; + private Integer customerId; + private LocalDate transactionDate; + private BigDecimal transactionAmount; + + // Additional transaction fields from Sales.CustomerTransactions + private BigDecimal amountExcludingTax; + private BigDecimal taxAmount; + private BigDecimal outstandingBalance; + private Boolean isFinalized; + + // Constructors + public CustomerTransaction() { + } + + // Getters and Setters + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + + public BigDecimal getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(BigDecimal transactionAmount) { this.transactionAmount = transactionAmount; } + + public BigDecimal getAmountExcludingTax() { return amountExcludingTax; } + public void setAmountExcludingTax(BigDecimal amountExcludingTax) { this.amountExcludingTax = amountExcludingTax; } + + public BigDecimal getTaxAmount() { return taxAmount; } + public void setTaxAmount(BigDecimal taxAmount) { this.taxAmount = taxAmount; } + + public BigDecimal getOutstandingBalance() { return outstandingBalance; } + public void setOutstandingBalance(BigDecimal outstandingBalance) { this.outstandingBalance = outstandingBalance; } + + public Boolean getIsFinalized() { return isFinalized; } + public void setIsFinalized(Boolean isFinalized) { this.isFinalized = isFinalized; } +} + +/** + * OrderLine document with embedded StockItem. + * Maps to the 'orderLines' collection in MongoDB. + * + * TRANSLATED FROM: C# OrderLine entity + */ +@Document(collection = "orderLines") +class OrderLine { + + @Id + private String id; + + @Field("orderLineId") + private Integer orderLineId; + + @Field("orderId") + private Integer orderId; + + @Field("stockItemId") + private Integer stockItemId; + + private String description; + + @Field("packageTypeId") + private Integer packageTypeId; + + private Integer quantity; + + private BigDecimal unitPrice; + + @Field("taxRate") + private BigDecimal taxRate; + + @Field("pickedQuantity") + private Integer pickedQuantity; + + @Field("pickingCompletedWhen") + private LocalDateTime pickingCompletedWhen; + + @Field("lastEditedBy") + private Integer lastEditedBy; + + @Field("lastEditedWhen") + private LocalDateTime lastEditedWhen; + + // Embedded StockItem document (denormalized reference data) + private StockItem stockItem; + + // Constructors + public OrderLine() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + + public Integer getStockItemId() { return stockItemId; } + public void setStockItemId(Integer stockItemId) { this.stockItemId = stockItemId; } + + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + + public Integer getPackageTypeId() { return packageTypeId; } + public void setPackageTypeId(Integer packageTypeId) { this.packageTypeId = packageTypeId; } + + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + + public BigDecimal getUnitPrice() { return unitPrice; } + public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; } + + public BigDecimal getTaxRate() { return taxRate; } + public void setTaxRate(BigDecimal taxRate) { this.taxRate = taxRate; } + + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + + public LocalDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(LocalDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + + public Integer getLastEditedBy() { return lastEditedBy; } + public void setLastEditedBy(Integer lastEditedBy) { this.lastEditedBy = lastEditedBy; } + + public LocalDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(LocalDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } + + public StockItem getStockItem() { return stockItem; } + public void setStockItem(StockItem stockItem) { this.stockItem = stockItem; } +} + +/** + * Embedded StockItem document within OrderLine. + * Represents denormalized stock item reference data. + * + * NOTE: No @Document annotation - this is a value object embedded in OrderLine + */ +class StockItem { + + private Integer stockItemId; + private String stockItemName; + private BigDecimal unitPrice; + + // Constructors + public StockItem() { + } + + // Getters and Setters + public Integer getStockItemId() { return stockItemId; } + public void setStockItemId(Integer stockItemId) { this.stockItemId = stockItemId; } + + public String getStockItemName() { return stockItemName; } + public void setStockItemName(String stockItemName) { this.stockItemName = stockItemName; } + + public BigDecimal getUnitPrice() { return unitPrice; } + public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; } +} \ No newline at end of file diff --git a/services/java-services/src/main/java/uom/services/benchmarks/dto/CompilationResult.java b/services/java-services/src/main/java/uom/services/benchmarks/dto/CompilationResult.java new file mode 100644 index 0000000..f216262 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/dto/CompilationResult.java @@ -0,0 +1,52 @@ +package uom.services.benchmarks.dto; + +import java.util.List; + +/** + * Response DTO for the Java compilation and validation endpoints. + * + * @param success whether compilation (and optional validation) succeeded + * @param message human-readable summary + * @param errors structured list of compilation or validation errors + * @param warnings any non-fatal warnings (e.g., schema mismatches that didn't prevent compilation) + */ +public record CompilationResult( + boolean success, + String message, + List errors, + List warnings +) { + + /** + * A single compilation or validation error with location information. + * + * @param code the compiler error code (e.g., "compiler.err.cant.resolve") or "VALIDATION_ERROR" + * @param message the error message + * @param lineNumber the 1-based line number where the error occurred (-1 if unknown) + * @param columnNumber the 1-based column number (-1 if unknown) + * @param sourceFile the source file name (if available) + */ + public record CompilationError( + String code, + String message, + long lineNumber, + long columnNumber, + String sourceFile + ) {} + + public static CompilationResult success(String message) { + return new CompilationResult(true, message, List.of(), List.of()); + } + + public static CompilationResult success(String message, List warnings) { + return new CompilationResult(true, message, List.of(), warnings); + } + + public static CompilationResult failure(String message, List errors) { + return new CompilationResult(false, message, errors, List.of()); + } + + public static CompilationResult failure(String message, List errors, List warnings) { + return new CompilationResult(false, message, errors, warnings); + } +} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/dto/CompileRequest.java b/services/java-services/src/main/java/uom/services/benchmarks/dto/CompileRequest.java new file mode 100644 index 0000000..bfea478 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/dto/CompileRequest.java @@ -0,0 +1,14 @@ +package uom.services.benchmarks.dto; + +/** + * Request DTO for the Java compilation and validation endpoints. + * + * @param sourceCode the Java source code to compile (may contain multiple classes in one file) + * @param framework the target Spring Data framework: "spring-data-neo4j" or "spring-data-mongodb" + * @param validate if true, also validate entity mapping against the live database after compilation + */ +public record CompileRequest( + String sourceCode, + String framework, + boolean validate +) {} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/service/CompilationService.java b/services/java-services/src/main/java/uom/services/benchmarks/service/CompilationService.java new file mode 100644 index 0000000..e9921e5 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/service/CompilationService.java @@ -0,0 +1,264 @@ +package uom.services.benchmarks.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import uom.services.benchmarks.dto.CompilationResult; +import uom.services.benchmarks.dto.CompilationResult.CompilationError; + +import javax.tools.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * Compiles Java source code dynamically using {@link javax.tools.JavaCompiler}. + *

+ * The source code may contain multiple classes in a single file (root entities + * annotated with {@code @Document}/{@code @Node} plus embedded value objects). + * The classpath includes all Spring Data dependencies from the running application. + */ +@Service +public class CompilationService { + @Autowired + private Environment environment; + + private static final Logger log = LoggerFactory.getLogger(CompilationService.class); + + /** + * Pattern to extract a public class name (determines the .java filename). + * Handles annotations preceding the class declaration on the same line, + * e.g. {@code @Document(collection="orders") public class Order}. + * Falls back to any top-level class if no public class is found. + */ + private static final Pattern PUBLIC_CLASS_PATTERN = + Pattern.compile("\\bpublic\\s+(?:class|record|enum|interface)\\s+(\\w+)"); + private static final Pattern ANY_CLASS_PATTERN = + Pattern.compile("(?:^|\\s)(?:class|record|enum|interface)\\s+(\\w+)", Pattern.MULTILINE); + + private final Path sandboxDir; + + public CompilationService(@Value("${compilation.sandbox.dir:/app/sandbox}") String sandboxDir) { + this.sandboxDir = Path.of(sandboxDir); + } + + /** + * Compiles and returns both the result and the output directory path. + * + * @param sourceCode the Java source code + * @return a record containing the result and the output directory + */ + public CompileOutput compileWithOutput(String sourceCode) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + return new CompileOutput( + CompilationResult.failure( + "No Java compiler available. Ensure the JDK (not JRE) is installed.", + List.of(new CompilationError("NO_COMPILER", "javax.tools.JavaCompiler not found", -1, -1, null)) + ), + null + ); + } + + Path outputDir = null; + try { + // Prepare sandbox directories + Files.createDirectories(sandboxDir); + outputDir = Files.createTempDirectory(sandboxDir, "compile-"); + Path srcDir = outputDir.resolve("src"); + Files.createDirectories(srcDir); + Path classesDir = outputDir.resolve("classes"); + Files.createDirectories(classesDir); + + // Determine filename from source code + String fileName = extractFileName(sourceCode); + Path sourceFile = srcDir.resolve(fileName); + Files.writeString(sourceFile, sourceCode); + + log.info("Compiling {} ({} bytes) in {}", fileName, sourceCode.length(), outputDir); + + // Build classpath + String classpath = buildClasspath(); + + // Compile + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + try (StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) { + Iterable compilationUnits = + fileManager.getJavaFileObjects(sourceFile.toFile()); + + List options = new ArrayList<>(); + options.addAll(List.of("-classpath", classpath)); + options.addAll(List.of("-d", classesDir.toString())); + options.add("-Xlint:all"); + + JavaCompiler.CompilationTask task = + compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits); + + boolean success = task.call(); + + List errors = new ArrayList<>(); + List warnings = new ArrayList<>(); + + for (Diagnostic diag : diagnostics.getDiagnostics()) { + String source = diag.getSource() != null ? diag.getSource().getName() : null; + if (diag.getKind() == Diagnostic.Kind.ERROR) { + errors.add(new CompilationError( + diag.getCode(), + diag.getMessage(null), + diag.getLineNumber(), + diag.getColumnNumber(), + source + )); + } else if (diag.getKind() == Diagnostic.Kind.WARNING + || diag.getKind() == Diagnostic.Kind.MANDATORY_WARNING) { + warnings.add(String.format("Line %d: %s", diag.getLineNumber(), diag.getMessage(null))); + } + } + + if (success) { + String msg = String.format("Compilation successful. %d class(es) compiled.", countClassFiles(classesDir)); + log.info(msg); + return new CompileOutput(CompilationResult.success(msg, warnings), classesDir); + } else { + String msg = String.format("Compilation failed with %d error(s).", errors.size()); + log.warn(msg); + // Clean up on failure + if (!environment.matchesProfiles("development")) { + cleanupDirectory(outputDir); + } + return new CompileOutput(CompilationResult.failure(msg, errors, warnings), null); + } + } + } catch (IOException e) { + log.error("IO error during compilation", e); + if (outputDir != null && !environment.matchesProfiles("development")) { + cleanupDirectory(outputDir); + } + return new CompileOutput( + CompilationResult.failure( + "IO error: " + e.getMessage(), + List.of(new CompilationError("IO_ERROR", e.getMessage(), -1, -1, null)) + ), + null + ); + } + } + + /** + * Extracts the primary class name to determine the .java filename. + * Prefers a public class; falls back to the first top-level class. + */ + String extractFileName(String sourceCode) { + Matcher publicMatcher = PUBLIC_CLASS_PATTERN.matcher(sourceCode); + if (publicMatcher.find()) { + return publicMatcher.group(1) + ".java"; + } + + Matcher anyMatcher = ANY_CLASS_PATTERN.matcher(sourceCode); + if (anyMatcher.find()) { + return anyMatcher.group(1) + ".java"; + } + + // Fallback + return "GeneratedCode.java"; + } + + /** + * Builds the classpath string for the dynamic compiler. + *

+ * When running under {@code mvn spring-boot:run}, the {@code java.class.path} + * system property only contains the Maven launcher jar. The actual application + * classes and dependencies are loaded by a custom classloader. We need to + * reconstruct the full classpath by scanning known locations. + */ + String buildClasspath() { + List paths = new ArrayList<>(); + + // 1. Application's own compiled classes + Path appClasses = Path.of("/app/target/classes"); + if (Files.isDirectory(appClasses)) { + paths.add(appClasses.toString()); + } + + // 2. Maven local repository jars (used by mvn spring-boot:run) + // Spring Boot Maven plugin resolves deps to ~/.m2/repository + Path m2Repo = Path.of(System.getProperty("user.home", "/root"), ".m2", "repository"); + if (Files.isDirectory(m2Repo)) { + try (Stream jars = Files.walk(m2Repo)) { + jars.filter(p -> p.toString().endsWith(".jar")) + .forEach(p -> paths.add(p.toString())); + } catch (IOException e) { + log.warn("Failed to scan Maven local repository at {}", m2Repo, e); + } + } + + // 3. Also try any copied dependency jars in target/dependency/ + Path depsDir = Path.of("/app/target/dependency"); + if (Files.isDirectory(depsDir)) { + try (Stream jars = Files.walk(depsDir)) { + jars.filter(p -> p.toString().endsWith(".jar")) + .forEach(p -> paths.add(p.toString())); + } catch (IOException e) { + log.warn("Failed to scan dependency directory", e); + } + } + + // 4. Fallback: if we found nothing above, try java.class.path + if (paths.isEmpty()) { + String runtimeClasspath = System.getProperty("java.class.path"); + if (runtimeClasspath != null && !runtimeClasspath.isBlank()) { + log.info("Using runtime java.class.path as fallback ({} chars)", runtimeClasspath.length()); + return runtimeClasspath; + } + } + + String classpath = String.join(File.pathSeparator, paths); + log.info("Built classpath with {} entries ({} chars)", paths.size(), classpath.length()); + return classpath; + } + + private long countClassFiles(Path classesDir) { + try (Stream files = Files.walk(classesDir)) { + return files.filter(p -> p.toString().endsWith(".class")).count(); + } catch (IOException e) { + return 0; + } + } + + /** + * Cleans up a temporary directory and all its contents. + */ + public void cleanupDirectory(Path dir) { + if (dir == null || !Files.exists(dir)) return; + try (Stream paths = Files.walk(dir)) { + paths.sorted(Comparator.reverseOrder()) + .forEach(p -> { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + log.warn("Failed to delete {}", p, e); + } + }); + } catch (IOException e) { + log.warn("Failed to clean up directory {}", dir, e); + } + } + + /** + * Result of a compilation that includes the path to compiled classes. + * + * @param result the compilation result + * @param classesDir path to the directory containing compiled .class files (null on failure) + */ + public record CompileOutput(CompilationResult result, Path classesDir) {} +} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/service/MavenCompilationService.java b/services/java-services/src/main/java/uom/services/benchmarks/service/MavenCompilationService.java new file mode 100644 index 0000000..d5c43b8 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/service/MavenCompilationService.java @@ -0,0 +1,261 @@ +package uom.services.benchmarks.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; +import uom.services.benchmarks.dto.CompilationResult; +import uom.services.benchmarks.dto.CompilationResult.CompilationError; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Maven-based fallback compilation service. + *

+ * Copies source code into a sandbox Maven project and runs {@code mvn compile}. + * This catches dependency resolution issues that {@code javax.tools.JavaCompiler} + * might miss, but is slower (~10-20s vs ~1-2s). + */ +@Service +public class MavenCompilationService { + @Autowired + private Environment environment; + + private static final Logger log = LoggerFactory.getLogger(MavenCompilationService.class); + + /** + * Maven compiler error pattern: {@code [ERROR] /path/File.java:[line,col] error: message} + */ + private static final Pattern MAVEN_ERROR_PATTERN = + Pattern.compile("\\[ERROR]\\s+.*?([\\w.]+\\.java):\\[(\\d+),(\\d+)]\\s*(.+)"); + + /** + * Alternate Maven error pattern: {@code [ERROR] /path/File.java:line: error: message} + */ + private static final Pattern MAVEN_ERROR_PATTERN_ALT = + Pattern.compile("\\[ERROR]\\s+.*?([\\w.]+\\.java):(\\d+):\\s*(?:error:\\s*)?(.+)"); + + private final Path sandboxDir; + private final Path sandboxPomPath; + private final int timeoutSeconds; + private final CompilationService compilationService; + + public MavenCompilationService( + @Value("${compilation.sandbox.dir:/app/sandbox}") String sandboxDir, + @Value("${compilation.timeout-seconds:30}") int timeoutSeconds, + CompilationService compilationService + ) { + this.sandboxDir = Path.of(sandboxDir); + this.sandboxPomPath = Path.of("/app/sandbox-pom.xml"); + this.timeoutSeconds = timeoutSeconds; + this.compilationService = compilationService; + } + + /** + * Compiles source code using Maven as a fallback. + * + * @param sourceCode the Java source code + * @return the compilation result + */ + public CompilationService.CompileOutput compileWithMaven(String sourceCode) { + Path projectDir = null; + try { + // Create sandbox Maven project structure + Files.createDirectories(sandboxDir); + projectDir = Files.createTempDirectory(sandboxDir, "maven-"); + Path srcDir = projectDir.resolve("src/main/java/sandbox"); + Files.createDirectories(srcDir); + Path classesDir = projectDir.resolve("target/classes"); + + // Write pom.xml + Path pomDest = projectDir.resolve("pom.xml"); + if (Files.exists(sandboxPomPath)) { + Files.copy(sandboxPomPath, pomDest); + } else { + Files.writeString(pomDest, generateSandboxPom()); + } + + // Write source file + String fileName = compilationService.extractFileName(sourceCode); + + // Prepend package declaration if not present + String codeWithPackage = sourceCode; + if (!sourceCode.contains("package ")) { + codeWithPackage = "package sandbox;\n\n" + sourceCode; + } + + Path sourceFile = srcDir.resolve(fileName); + Files.writeString(sourceFile, codeWithPackage); + + log.info("Maven compiling {} in {}", fileName, projectDir); + + // Run mvn compile + ProcessBuilder pb = new ProcessBuilder( + "mvn", "compile", + "-f", "pom.xml", + "-q", // quiet mode to reduce output noise + "--batch-mode", + "--no-transfer-progress" + ); + pb.directory(projectDir.toFile()); + pb.redirectErrorStream(true); + + Process process = pb.start(); + List outputLines = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + outputLines.add(line); + } + } + + boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS); + if (!finished) { + process.destroyForcibly(); + if (!environment.matchesProfiles("development")) { + compilationService.cleanupDirectory(projectDir); + } + return new CompilationService.CompileOutput( + CompilationResult.failure( + "Maven compilation timed out after " + timeoutSeconds + " seconds.", + List.of(new CompilationError("TIMEOUT", "Maven process timed out", -1, -1, null)) + ), + null + ); + } + + int exitCode = process.exitValue(); + if (exitCode == 0) { + String msg = "Maven compilation successful."; + log.info(msg); + return new CompilationService.CompileOutput( + CompilationResult.success(msg), + classesDir + ); + } else { + // Parse Maven error output + List errors = parseMavenErrors(outputLines); + if (errors.isEmpty()) { + // If we couldn't parse structured errors, return raw output + String rawOutput = String.join("\n", outputLines); + errors.add(new CompilationError( + "MAVEN_ERROR", + rawOutput.length() > 2000 ? rawOutput.substring(0, 2000) + "..." : rawOutput, + -1, -1, null + )); + } + + String msg = String.format("Maven compilation failed with %d error(s).", errors.size()); + log.warn(msg); + if (!environment.matchesProfiles("development")) { + compilationService.cleanupDirectory(projectDir); + } + return new CompilationService.CompileOutput( + CompilationResult.failure(msg, errors), + null + ); + } + } catch (IOException e) { + log.error("IO error during Maven compilation", e); + if (projectDir != null && !environment.matchesProfiles("development")) + compilationService.cleanupDirectory(projectDir); + return new CompilationService.CompileOutput( + CompilationResult.failure( + "IO error: " + e.getMessage(), + List.of(new CompilationError("IO_ERROR", e.getMessage(), -1, -1, null)) + ), + null + ); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + if (projectDir != null && !environment.matchesProfiles("development")) + compilationService.cleanupDirectory(projectDir); + return new CompilationService.CompileOutput( + CompilationResult.failure( + "Maven compilation interrupted.", + List.of(new CompilationError("INTERRUPTED", e.getMessage(), -1, -1, null)) + ), + null + ); + } + } + + /** + * Parses Maven compiler output to extract structured errors. + */ + private List parseMavenErrors(List outputLines) { + List errors = new ArrayList<>(); + for (String line : outputLines) { + if (!line.contains("[ERROR]")) continue; + + Matcher m = MAVEN_ERROR_PATTERN.matcher(line); + if (m.find()) { + errors.add(new CompilationError( + "MAVEN_COMPILE_ERROR", + m.group(4).trim(), + Long.parseLong(m.group(2)), + Long.parseLong(m.group(3)), + m.group(1) + )); + continue; + } + + Matcher mAlt = MAVEN_ERROR_PATTERN_ALT.matcher(line); + if (mAlt.find()) { + errors.add(new CompilationError( + "MAVEN_COMPILE_ERROR", + mAlt.group(3).trim(), + Long.parseLong(mAlt.group(2)), + -1, + mAlt.group(1) + )); + } + } + return errors; + } + + /** + * Generates a minimal sandbox pom.xml with Spring Data dependencies. + */ + private String generateSandboxPom() { + return """ + + + 4.0.0 + uom.sandbox + sandbox + 1.0.0 + + 25 + 25 + 25 + + + + org.springframework.boot + spring-boot-starter-data-mongodb + 4.0.3 + + + org.springframework.boot + spring-boot-starter-data-neo4j + 4.0.3 + + + + """; + } +} diff --git a/services/java-services/src/main/java/uom/services/benchmarks/service/ValidationService.java b/services/java-services/src/main/java/uom/services/benchmarks/service/ValidationService.java new file mode 100644 index 0000000..31b2c79 --- /dev/null +++ b/services/java-services/src/main/java/uom/services/benchmarks/service/ValidationService.java @@ -0,0 +1,274 @@ +package uom.services.benchmarks.service; + +import org.neo4j.cypherdsl.core.Cypher; +import org.neo4j.cypherdsl.core.Statement; +import org.neo4j.cypherdsl.core.StatementBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.neo4j.core.Neo4jTemplate; +import org.springframework.stereotype.Service; +import uom.services.benchmarks.dto.CompilationResult; +import uom.services.benchmarks.dto.CompilationResult.CompilationError; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Validates compiled Java entity classes against live databases. + *

+ * After compilation, this service: + *

    + *
  1. Loads all compiled classes from the output directory using a {@link URLClassLoader}
  2. + *
  3. Identifies root entity classes (annotated with {@code @Document} or {@code @Node})
  4. + *
  5. Runs a sample {@code LIMIT 1} query for each root entity against the live database
  6. + *
  7. Returns validation errors if the mapping is invalid (Spring Data throws {@code MappingException})
  8. + *
+ */ +@Service +public class ValidationService { + + private static final Logger log = LoggerFactory.getLogger(ValidationService.class); + + private final Neo4jTemplate neo4jTemplate; + private final MongoTemplate mongoTemplate; + + public ValidationService(Neo4jTemplate neo4jTemplate, MongoTemplate mongoTemplate) { + this.neo4jTemplate = neo4jTemplate; + this.mongoTemplate = mongoTemplate; + } + + /** + * Validates compiled entity classes against the live database. + * + * @param classesDir the directory containing compiled .class files + * @param framework the target framework ("spring-data-neo4j" or "spring-data-mongodb") + * @return the validation result + */ + public CompilationResult validate(Path classesDir, String framework) { + if (classesDir == null || !Files.isDirectory(classesDir)) { + return CompilationResult.failure( + "No compiled classes directory available for validation.", + List.of(new CompilationError("NO_CLASSES", "Classes directory is null or does not exist.", -1, -1, null)) + ); + } + + List> loadedClasses; + URLClassLoader classLoader; + try { + classLoader = new URLClassLoader( + new URL[]{classesDir.toUri().toURL()}, + Thread.currentThread().getContextClassLoader() // Parent: Spring Boot classloader + ); + loadedClasses = loadClassesFromDir(classesDir, classLoader); + } catch (IOException e) { + log.error("Failed to create classloader for {}", classesDir, e); + return CompilationResult.failure( + "Failed to load compiled classes: " + e.getMessage(), + List.of(new CompilationError("CLASSLOAD_ERROR", e.getMessage(), -1, -1, null)) + ); + } + + if (loadedClasses.isEmpty()) { + return CompilationResult.failure( + "No classes found in compilation output.", + List.of(new CompilationError("NO_CLASSES", "No .class files found in " + classesDir, -1, -1, null)) + ); + } + + log.info("Loaded {} class(es) for validation: {}", loadedClasses.size(), + loadedClasses.stream().map(Class::getSimpleName).toList()); + + // Identify root entity classes based on framework-specific annotations + List> rootEntities = identifyRootEntities(loadedClasses, framework); + + if (rootEntities.isEmpty()) { + List warnings = List.of( + "No root entity annotations found (@Document or @Node). " + + "Compilation succeeded but schema validation was skipped." + ); + return CompilationResult.success( + "Compilation successful. No root entities to validate (all classes may be embedded value objects).", + warnings + ); + } + + log.info("Identified {} root entit(ies) to validate: {}", rootEntities.size(), + rootEntities.stream().map(Class::getSimpleName).toList()); + + // Validate each root entity by running a sample query + List errors = new ArrayList<>(); + List warnings = new ArrayList<>(); + int validatedCount = 0; + + for (Class entityClass : rootEntities) { + try { + validateEntity(entityClass, framework); + validatedCount++; + log.info("Validation passed for entity: {}", entityClass.getSimpleName()); + } catch (Exception e) { + log.warn("Validation failed for entity: {}", entityClass.getSimpleName(), e); + errors.add(new CompilationError( + "VALIDATION_ERROR", + String.format("Entity '%s': %s", entityClass.getSimpleName(), extractRootCause(e)), + -1, -1, + entityClass.getSimpleName() + ".java" + )); + } + } + + if (errors.isEmpty()) { + String msg = String.format( + "Validation passed. %d root entit(ies) validated against %s.", + validatedCount, framework + ); + return CompilationResult.success(msg, warnings); + } else { + String msg = String.format( + "Validation failed for %d of %d root entit(ies).", + errors.size(), rootEntities.size() + ); + return CompilationResult.failure(msg, errors, warnings); + } + } + + /** + * Loads all .class files from a directory into classes. + */ + private List> loadClassesFromDir(Path classesDir, URLClassLoader classLoader) throws IOException { + List> classes = new ArrayList<>(); + try (Stream paths = Files.walk(classesDir)) { + List classFiles = paths + .filter(p -> p.toString().endsWith(".class")) + .toList(); + + for (Path classFile : classFiles) { + // Convert file path to class name: /dir/com/example/Foo.class -> com.example.Foo + Path relative = classesDir.relativize(classFile); + String className = relative.toString() + .replace('/', '.') + .replace('\\', '.') + .replaceAll("\\.class$", ""); + + try { + Class loaded = classLoader.loadClass(className); + classes.add(loaded); + log.debug("Loaded class: {}", className); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + log.warn("Could not load class {}: {}", className, e.getMessage()); + } + } + } + return classes; + } + + /** + * Identifies root entity classes from loaded classes based on framework annotations. + */ + private List> identifyRootEntities(List> classes, String framework) { + List> rootEntities = new ArrayList<>(); + for (Class clazz : classes) { + if (isRootEntity(clazz, framework)) { + rootEntities.add(clazz); + } + } + return rootEntities; + } + + /** + * Checks if a class is a root entity for the given framework. + */ + private boolean isRootEntity(Class clazz, String framework) { + return switch (framework.toLowerCase()) { + case "spring-data-mongodb" -> hasAnnotation(clazz, "org.springframework.data.mongodb.core.mapping.Document"); + case "spring-data-neo4j" -> hasAnnotation(clazz, "org.springframework.data.neo4j.core.schema.Node"); + default -> { + // Check both + yield hasAnnotation(clazz, "org.springframework.data.mongodb.core.mapping.Document") + || hasAnnotation(clazz, "org.springframework.data.neo4j.core.schema.Node"); + } + }; + } + + /** + * Checks if a class has an annotation by its fully qualified name. + * Uses name comparison to handle classloader boundaries. + */ + private boolean hasAnnotation(Class clazz, String annotationFqn) { + for (var annotation : clazz.getAnnotations()) { + if (annotation.annotationType().getName().equals(annotationFqn)) { + return true; + } + } + return false; + } + + /** + * Validates a single entity class by running a sample query against the database. + */ + private void validateEntity(Class entityClass, String framework) { + switch (framework.toLowerCase()) { + case "spring-data-mongodb" -> validateMongoEntity(entityClass); + case "spring-data-neo4j" -> validateNeo4jEntity(entityClass); + default -> { + // Try to detect from annotations + if (hasAnnotation(entityClass, "org.springframework.data.mongodb.core.mapping.Document")) { + validateMongoEntity(entityClass); + } else if (hasAnnotation(entityClass, "org.springframework.data.neo4j.core.schema.Node")) { + validateNeo4jEntity(entityClass); + } else { + log.warn("Cannot determine framework for entity: {}", entityClass.getSimpleName()); + } + } + } + } + + /** + * Validates a MongoDB entity by running {@code mongoTemplate.findOne(query, entityClass)} + * with a limit of 1. If the mapping is invalid, Spring Data MongoDB will throw. + */ + private void validateMongoEntity(Class entityClass) { + log.info("Validating MongoDB entity: {}", entityClass.getSimpleName()); + Query query = new Query().limit(1); + // This will throw MappingException or similar if the entity mapping is invalid + // or if the collection doesn't exist / has incompatible data + mongoTemplate.findOne(query, entityClass); + } + + /** + * Validates a Neo4j entity by running {@code neo4jTemplate.findAll(entityClass)} + * which translates to a MATCH query. If the mapping is invalid, Spring Data Neo4j + * will throw a MappingException. + */ + private void validateNeo4jEntity(Class entityClass) { + log.info("Validating Neo4j entity: {}", entityClass.getSimpleName()); + neo4jTemplate.findOne("MATCH (n) RETURN n LIMIT 1", Map.of(), entityClass); + } + + /** + * Extracts the root cause message from a nested exception chain. + */ + private String extractRootCause(Throwable t) { + Throwable root = t; + while (root.getCause() != null && root.getCause() != root) { + root = root.getCause(); + } + String msg = root.getMessage(); + if (msg == null || msg.isBlank()) { + msg = root.getClass().getSimpleName(); + } + // Include the direct exception type for context + if (root != t) { + return String.format("%s (caused by %s: %s)", t.getMessage(), root.getClass().getSimpleName(), msg); + } + return msg; + } +} diff --git a/services/java-services/src/main/resources/application-development.properties b/services/java-services/src/main/resources/application-development.properties new file mode 100644 index 0000000..2d65647 --- /dev/null +++ b/services/java-services/src/main/resources/application-development.properties @@ -0,0 +1,27 @@ +# Development profile configuration +# Load development env file (overrides default .env from application.properties) +springdotenv.filename=.env.dev + +spring.config.import=optional:file:./application.properties + +server.port=8090 + +# Local development database endpoints (avoid docker-internal hostnames from .env) +spring.mongodb.uri=mongodb://uom_readonly:uom_readonly@localhost:27027/uom +spring.mongodb.database=uom +spring.neo4j.uri=neo4j://localhost:7697 +spring.neo4j.authentication.username=neo4j +spring.neo4j.authentication.password=password + +# Debug mode for Spring Boot (enables auto-config report) +debug=true + +compilation.sandbox.dir=target/sandbox + +# Development logging +logging.level.org.springframework=INFO +logging.level.uom.services.benchmarks=DEBUG + +# Database logs +logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG +logging.level.org.springframework.data.neo4j.cypher=DEBUG diff --git a/services/java-services/src/main/resources/application.properties b/services/java-services/src/main/resources/application.properties new file mode 100644 index 0000000..c5f4f54 --- /dev/null +++ b/services/java-services/src/main/resources/application.properties @@ -0,0 +1,14 @@ +spring.application.name=java-services + +# Spring Data MongoDB +spring.mongodb.uri=${MONGODB_URI} +spring.mongodb.database=${MONGODB_DATABASE} + +# Spring Data Neo4j +spring.neo4j.uri=${NEO4J_URI} +spring.neo4j.authentication.username=neo4j +spring.neo4j.authentication.password=${NEO4J_PASSWORD} + +# Compilation sandbox +compilation.sandbox.dir=/app/sandbox +compilation.timeout-seconds=30 diff --git a/services/java-services/src/test/java/uom/services/benchmarks/JavaServicesApplicationTests.java b/services/java-services/src/test/java/uom/services/benchmarks/JavaServicesApplicationTests.java new file mode 100644 index 0000000..0bd9f60 --- /dev/null +++ b/services/java-services/src/test/java/uom/services/benchmarks/JavaServicesApplicationTests.java @@ -0,0 +1,13 @@ +package uom.services.benchmarks; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class JavaServicesApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/services/java-services/src/test/java/uom/services/benchmarks/controller/CompilationControllerIntegrationTest.java b/services/java-services/src/test/java/uom/services/benchmarks/controller/CompilationControllerIntegrationTest.java new file mode 100644 index 0000000..cfca65c --- /dev/null +++ b/services/java-services/src/test/java/uom/services/benchmarks/controller/CompilationControllerIntegrationTest.java @@ -0,0 +1,229 @@ +package uom.services.benchmarks.controller; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import uom.services.benchmarks.dto.CompileRequest; +import uom.services.benchmarks.dto.CompilationResult; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { + "compilation.sandbox.dir=target/sandbox", + "spring.mongodb.uri=mongodb://uom_readonly:uom_readonly@localhost:27027/uom", + "spring.mongodb.database=uom", + "spring.neo4j.uri=neo4j://localhost:7697", + "spring.neo4j.authentication.username=neo4j", + "spring.neo4j.authentication.password=password" +}) +class CompilationControllerIntegrationTest { + + @LocalServerPort + private int port; + + private final RestTemplate restTemplate; + + public CompilationControllerIntegrationTest() { + this.restTemplate = new RestTemplate(); + this.restTemplate.setErrorHandler(new org.springframework.web.client.DefaultResponseErrorHandler() { + @Override + public boolean hasError(org.springframework.http.client.ClientHttpResponse response) { + return false; // prevent RestTemplate from throwing exceptions on 4xx/5xx responses + } + }); + } + + private String url(String path) { + return "http://localhost:" + port + path; + } + + @Test + void testCompileSingleClass() { + String sourceCode = """ + import org.springframework.data.annotation.Id; + import org.springframework.data.mongodb.core.mapping.Document; + + @Document(collection = "orders") + class Order { + @Id + private String id; + private String customerName; + + public Order() {} + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + }"""; + + CompileRequest request = new CompileRequest(sourceCode, "spring-data-mongodb", false); + + ResponseEntity response = restTemplate.postForEntity( + url("/api/compiler/compile"), request, CompilationResult.class); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().success()).isTrue(); + assertThat(response.getBody().message()).contains("Compilation successful"); + } + + @Test + void testCompileMultiClassComplexSchema() { + String sourceCode = """ + import java.math.BigDecimal; + import java.time.LocalDate; + import java.time.LocalDateTime; + import java.util.ArrayList; + import java.util.List; + import org.springframework.data.annotation.Id; + import org.springframework.data.mongodb.core.mapping.Document; + import org.springframework.data.mongodb.core.mapping.Field; + + @Document(collection = "orders") + class Order { + @Id + private String id; + @Field("orderId") + private Integer orderId; + @Field("customerId") + private Integer customerId; + @Field("orderDate") + private LocalDateTime orderDate; + private Customer customer; + public Order() {} + public String getId() { return id; } + } + + class Customer { + private Integer customerId; + private String customerName; + private BigDecimal creditLimit; + private List customerTransactions = new ArrayList<>(); + public Customer() {} + } + + class CustomerTransaction { + private Integer customerTransactionId; + private BigDecimal transactionAmount; + public CustomerTransaction() {} + } + + @Document(collection = "orderLines") + class OrderLine { + @Id + private String id; + @Field("orderLineId") + private Integer orderLineId; + private Integer quantity; + private BigDecimal unitPrice; + private StockItem stockItem; + public OrderLine() {} + } + + class StockItem { + private Integer stockItemId; + private String stockItemName; + public StockItem() {} + }"""; + + CompileRequest request = new CompileRequest(sourceCode, "spring-data-mongodb", false); + + ResponseEntity response = restTemplate.postForEntity( + url("/api/compiler/compile"), request, CompilationResult.class); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().success()).isTrue(); + assertThat(response.getBody().message()).contains("Compilation successful"); + } + + @Test + void testCompileAndValidateLiveMongoDb() { + String sourceCode = """ + import org.springframework.data.annotation.Id; + import org.springframework.data.mongodb.core.mapping.Document; + + @Document(collection = "orders") + class Order { + @Id + private String id; + public Order() {} + }"""; + + CompileRequest request = new CompileRequest(sourceCode, "spring-data-mongodb", true); + + // Note: This requires a live MongoDB instance to succeed completely cleanly. + // We ensure the endpoint returns an OK status. + ResponseEntity response = restTemplate.postForEntity( + url("/api/compiler/compile"), request, CompilationResult.class); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void testCompilationErrorCase() { + String sourceCode = """ + import org.springframework.data.mongodb.core.mapping.Document; + + @Document(collection = "orders") + class Order { + private UndefinedType foo; + }"""; + + CompileRequest request = new CompileRequest(sourceCode, "spring-data-mongodb", false); + + ResponseEntity response = restTemplate.postForEntity( + url("/api/compiler/compile"), request, CompilationResult.class); + + assertThat(response.getStatusCode().is4xxClientError()).isTrue(); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().success()).isFalse(); + assertThat(response.getBody().message()).contains("Compilation failed"); + assertThat(response.getBody().errors()).isNotEmpty(); + assertThat(response.getBody().errors().get(0).message()).contains("cannot find symbol"); + } + + @Test + void testMavenFallbackCompilation() { + String sourceCode = """ + import org.springframework.data.annotation.Id; + import org.springframework.data.mongodb.core.mapping.Document; + + @Document(collection = "orders") + class Order { + @Id + private String id; + public Order() {} + }"""; + + CompileRequest request = new CompileRequest(sourceCode, "spring-data-mongodb", false); + + ResponseEntity response = restTemplate.postForEntity( + url("/api/compiler/maven-compile"), request, CompilationResult.class); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + } + + @Test + void testNeo4jCompilationAndValidation() { + String sourceCode = """ + import org.springframework.data.neo4j.core.schema.Node; + import org.springframework.data.neo4j.core.schema.Id; + + @Node("Person") + class Person { + @Id + private String name; + public Person() {} + }"""; + + CompileRequest request = new CompileRequest(sourceCode, "spring-data-neo4j", true); + + ResponseEntity response = restTemplate.postForEntity( + url("/api/compiler/compile"), request, CompilationResult.class); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + } +} diff --git a/services/orchestrator/.codespellignore b/services/orchestrator/.codespellignore new file mode 100644 index 0000000..e69de29 diff --git a/services/orchestrator/.dockerignore b/services/orchestrator/.dockerignore new file mode 100644 index 0000000..c6e0346 --- /dev/null +++ b/services/orchestrator/.dockerignore @@ -0,0 +1,47 @@ +# Ignore node_modules and other dependency directories +node_modules +bower_components +vendor + +# Ignore logs and temporary files +*.log +*.tmp +*.swp + +# Ignore .env files and other environment files +.env +.env.* +*.local + +# Ignore git-related files +.git +.gitignore + +# Ignore Docker-related files and configs +.dockerignore +docker-compose.yml + +# Ignore build and cache directories +dist +build +.cache +__pycache__ + +# Ignore IDE and editor configurations +.vscode +.idea +*.sublime-project +*.sublime-workspace +.DS_Store # macOS-specific + +# Ignore test and coverage files +coverage +*.coverage +*.test.js +*.spec.js +tests + +# Custom ignores +misc +logs +GEMINI.md diff --git a/services/orchestrator/.env.example b/services/orchestrator/.env.example new file mode 100644 index 0000000..a9ad5f4 --- /dev/null +++ b/services/orchestrator/.env.example @@ -0,0 +1,64 @@ +DEVELOPMENT=false + +TAVILY_API_KEY= + +# Observability +LANGSMITH_TRACING=true +LANGSMITH_ENDPOINT=https://eu.api.smith.langchain.com +LANGSMITH_API_KEY= +LANGSMITH_PROJECT="LLM orchestrator service" + +LANGFUSE_SECRET_KEY= +LANGFUSE_PUBLIC_KEY= +LANGFUSE_BASE_URL="https://cloud.langfuse.com" + +LOGFIRE_TOKEN= +LOGFIRE_PYDANTIC_PLUGIN_RECORD=failure + +OTEL_PYTHON_HTTPX_EXCLUDED_URLS="^.*(langfuse|smith\.langchain\.com).*$" +OTEL_PYTHON_REQUESTS_EXCLUDED_URLS="^.*(langfuse|smith\.langchain\.com).*$" +OTEL_RESOURCE_ATTRIBUTES=vcs.repository.url.full=https://github.com/corovcam/Universal-Object-Mapping +OTEL_RESOURCE_ATTRIBUTES=${OTEL_RESOURCE_ATTRIBUTES},vcs.repository.ref.revision=main +OTEL_RESOURCE_ATTRIBUTES=${OTEL_RESOURCE_ATTRIBUTES},vcs.root.path=services/orchestrator + +# The following depend on your selected configuration + +# OLLAMA server: gpt-oss:latest, qwen3-coder:30b, mistral-small3.2:latest, qwen3-embedding:latest +OLLAMA_HOST=http://ollama-autossh-tunnel:11434 + +# OpenAI compatible server +# Available models at: https://llm.ai.e-infra.cz/v1/models +OPENAI_API_URL=https://llm.ai.e-infra.cz/v1 +OPENAI_API_KEY= + +# MODEL=ollama/qwen3-coder:30b +MODEL=einfra/deepseek-v4-pro-thinking + +DOTNET_SANDBOX_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0 +JAVA_SANDBOX_IMAGE=bellsoft/liberica-openjdk-debian:25-cds +CONTEXT_ABSOLUTE_PATH=/deps/orchestrator/src/context + +# LangChain integrations +REDIS_URI=redis://langgraph-redis:6379 +POSTGRES_URI=postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable + +# Dependency variables (databases, container from upstream - should be in sync) +NEO4J_DATABASE=neo4j +NEO4J_URI=neo4j://host.docker.internal:7697 +MONGODB_URI=mongodb://uom_readonly:uom_readonly@host.docker.internal:27027/uom +MONGODB_DATABASE=uom +MSSQL_HOST=host.docker.internal +MSSQL_PORT=1444 +MSSQL_CONNECTION_STRING="Server=${MSSQL_HOST},${MSSQL_PORT};Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" +DB_TOOLBOX_URI=http://db-toolbox:5000 +MONGODB_MCP_URI=http://mongodb-mcp-server:3000/mcp +DOTNET_SERVICE_URI=http://dotnet-service:5083 +DOTNET_SERVICE_SSH_URI=ssh://dotnet-service:5022 +JAVA_SERVICE_URI=http://java-service:8090 +JAVA_SERVICE_SSH_URI=ssh://java-service:8022 + +DAYTONA_API_KEY= +DAYTONA_API_URL=http://api:3000/api +DAYTONA_TARGET=us +DAYTONA_EXPERIMENTAL_OTEL_ENABLED=true +DAYTONA_OTEL_ENABLED=true diff --git a/services/orchestrator/.envrc b/services/orchestrator/.envrc new file mode 100644 index 0000000..3dc61c3 --- /dev/null +++ b/services/orchestrator/.envrc @@ -0,0 +1,10 @@ +# shellcheck disable=SC2148 +[[ -f ~/.envrc.local ]] && source_env ~/.envrc.local + +if [ -d ".venv/bin" ]; then + source .venv/bin/activate +elif [ -d ".venv/Scripts" ]; then + source .venv/Scripts/activate +fi + +dotenv_if_exists .env.dev diff --git a/services/orchestrator/.github/workflows/integration-tests.yml b/services/orchestrator/.github/workflows/integration-tests.yml new file mode 100644 index 0000000..ce7e3d2 --- /dev/null +++ b/services/orchestrator/.github/workflows/integration-tests.yml @@ -0,0 +1,44 @@ +# This workflow will run integration tests for the current project once per day + +name: Integration Tests + +on: + schedule: + - cron: "37 14 * * *" # Run at 7:37 AM Pacific Time (14:37 UTC) every day + workflow_dispatch: # Allows triggering the workflow manually in GitHub UI + +# If another scheduled run starts while this workflow is still running, +# cancel the earlier run in favor of the next run. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration-tests: + name: Integration Tests + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.11", "3.12"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + uv venv + uv pip install -r pyproject.toml + uv pip install -U pytest-asyncio vcrpy + - name: Run integration tests + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} + LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} + LANGSMITH_TRACING: true + LANGSMITH_TEST_CACHE: tests/cassettes + run: | + uv run pytest tests/integration_tests -m "not integration" diff --git a/services/orchestrator/.github/workflows/unit-tests.yml b/services/orchestrator/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..055407c --- /dev/null +++ b/services/orchestrator/.github/workflows/unit-tests.yml @@ -0,0 +1,57 @@ +# This workflow will run unit tests for the current project + +name: CI + +on: + push: + branches: ["main"] + pull_request: + workflow_dispatch: # Allows triggering the workflow manually in GitHub UI + +# If another push to the same PR or branch happens while this workflow is still running, +# cancel the earlier run in favor of the next run. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + unit-tests: + name: Unit Tests + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.11", "3.12"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + curl -LsSf https://astral.sh/uv/install.sh | sh + uv venv + uv pip install -r pyproject.toml + - name: Lint with ruff + run: | + uv pip install ruff + uv run ruff check . + - name: Lint with mypy + run: | + uv pip install mypy + uv run mypy --strict src/ + - name: Check README spelling + uses: codespell-project/actions-codespell@v2 + with: + ignore_words_file: .codespellignore + path: README.md + - name: Check code spelling + uses: codespell-project/actions-codespell@v2 + with: + ignore_words_file: .codespellignore + path: src/ + - name: Run tests with pytest + run: | + uv pip install pytest + uv run pytest tests/unit_tests diff --git a/services/orchestrator/.gitignore b/services/orchestrator/.gitignore new file mode 100644 index 0000000..9927b65 --- /dev/null +++ b/services/orchestrator/.gitignore @@ -0,0 +1,169 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +.langgraph_api + +.local-validation-logs/ + +misc/ +.env.dev diff --git a/services/orchestrator/Dockerfile b/services/orchestrator/Dockerfile new file mode 100644 index 0000000..78504b0 --- /dev/null +++ b/services/orchestrator/Dockerfile @@ -0,0 +1,26 @@ +FROM langchain/langgraph-api:3.13-wolfi + +RUN apk update && apk add nodejs-24 npm && node --version && npm --version + +# -- Adding local package . -- +ADD . /deps/orchestrator +# -- End of local package . -- + +# -- Installing all local dependencies -- +RUN for dep in /deps/*; do echo "Installing $dep"; if [ -d "$dep" ]; then echo "Installing $dep"; (cd "$dep" && PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir -c /api/constraints.txt -e .); fi; done +# -- End of local dependencies install -- +ENV LANGSERVE_GRAPHS='{"agent": "/deps/orchestrator/src/react_agent/graph.py:graph"}' + + + +# -- Ensure user deps didn't inadvertently overwrite langgraph-api +RUN mkdir -p /api/langgraph_api /api/langgraph_runtime /api/langgraph_license && touch /api/langgraph_api/__init__.py /api/langgraph_runtime/__init__.py /api/langgraph_license/__init__.py +RUN PYTHONDONTWRITEBYTECODE=1 uv pip install --system --no-cache-dir --no-deps -e /api +# -- End of ensuring user deps didn't inadvertently overwrite langgraph-api -- +# -- Removing build deps from the final image ~<:===~~~ -- +RUN pip uninstall -y pip setuptools wheel +RUN rm -rf /usr/local/lib/python*/site-packages/pip* /usr/local/lib/python*/site-packages/setuptools* /usr/local/lib/python*/site-packages/wheel* && find /usr/local/bin -name "pip*" -delete || true +RUN rm -rf /usr/lib/python*/site-packages/pip* /usr/lib/python*/site-packages/setuptools* /usr/lib/python*/site-packages/wheel* && find /usr/bin -name "pip*" -delete || true +RUN uv pip uninstall --system pip setuptools wheel && rm /usr/bin/uv /usr/bin/uvx + +WORKDIR /deps/orchestrator \ No newline at end of file diff --git a/services/orchestrator/LICENSE b/services/orchestrator/LICENSE new file mode 100644 index 0000000..57d0481 --- /dev/null +++ b/services/orchestrator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 LangChain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/services/orchestrator/Makefile b/services/orchestrator/Makefile new file mode 100644 index 0000000..8409ad9 --- /dev/null +++ b/services/orchestrator/Makefile @@ -0,0 +1,107 @@ +.PHONY: all format lint type_check dev debug test tests test_watch integration_tests docker_tests help extended_tests + +# Default target executed when no arguments are given to make. +all: help + +###################### +# DEVELOPMENT +###################### + +ifeq ($(OS),Windows_NT) +install: + uv sync --all-extras + +dev: + powershell -ExecutionPolicy Bypass -File scripts/load_env.ps1 .env.dev + langgraph dev --allow-blocking + +debug: + powershell -ExecutionPolicy Bypass -File scripts/load_env.ps1 .env.dev + langgraph dev --debug-port 5678 --wait-for-client --allow-blocking +else +install: + uv sync --all-extras + ./scripts/setup_env.sh + ./scripts/install_logdy.sh + +dev: + langgraph dev --allow-blocking + +debug: + langgraph dev --debug-port 5678 --wait-for-client --allow-blocking +endif + +###################### +# TESTS +###################### + +# Define a variable for the test file path. +TEST_FILE ?= tests/unit_tests/ + +test: + python -m pytest $(TEST_FILE) + +integration_tests: + python -m pytest tests/integration_tests + +test_watch: + python -m ptw --snapshot-update --now . -- -vv tests/unit_tests + +test_profile: + python -m pytest -vv tests/unit_tests/ --profile-svg + +extended_tests: + python -m pytest --only-extended $(TEST_FILE) + + +###################### +# LINTING AND FORMATTING +###################### + +# Define a variable for Python and notebook files. +PYTHON_FILES=src/ +MYPY_CACHE=.mypy_cache +lint format: PYTHON_FILES=. +lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --diff-filter=d main | grep -E '\.py$$|\.ipynb$$') +lint_package: PYTHON_FILES=src +lint_tests: PYTHON_FILES=tests +lint_tests: MYPY_CACHE=.mypy_cache_test + +lint lint_diff lint_package lint_tests: + python -m ruff check . + [ "$(PYTHON_FILES)" = "" ] || python -m ruff format $(PYTHON_FILES) --diff + [ "$(PYTHON_FILES)" = "" ] || python -m ruff check --select I $(PYTHON_FILES) + [ "$(PYTHON_FILES)" = "" ] || python -m ty check +# [ "$(PYTHON_FILES)" = "" ] || python -m mypy --strict $(PYTHON_FILES) +# [ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) && python -m mypy --strict $(PYTHON_FILES) --cache-dir $(MYPY_CACHE) + +format format_diff: + ruff format $(PYTHON_FILES) + ruff check --select I --fix $(PYTHON_FILES) + +type_check: + ty check + +spell_check: + codespell --toml pyproject.toml + +spell_fix: + codespell --toml pyproject.toml -w + +###################### +# HELP +###################### + +help: + @echo '----' + @echo 'install - setup development environment and install dependencies' + @echo 'dev - start development server' + @echo 'debug - start development server in debug mode' + @echo 'format - run code formatters' + @echo 'type_check - run type checker' + @echo 'lint - run linters' + @echo 'test - run unit tests' + @echo 'tests - run unit tests' + @echo 'test TEST_FILE= - run all tests in file' + @echo 'test_watch - run unit tests in watch mode' + diff --git a/services/orchestrator/README.md b/services/orchestrator/README.md new file mode 100644 index 0000000..028f8bd --- /dev/null +++ b/services/orchestrator/README.md @@ -0,0 +1,41 @@ +# UOM Orchestrator + +The UOM Orchestrator is the core LLM orchestration engine for the Universal Object Mapping (UOM) system. It is a Python-based service utilizing [LangGraph](https://github.com/langchain-ai/langgraph) to drive the iterative ORM-to-ORM/ODM/OGM translation workflow. + +## What it does + +The Orchestrator uses a semi-deterministic graph-based workflow to translate database schemas and queries from .NET frameworks (EF Core, NHibernate, Dapper) to Java frameworks (Spring Data MongoDB, Spring Data Neo4j). + +The workflow consists of: +1. **Extraction**: Parses incoming user requests to identify the source code, source framework, and destination framework. +2. **Council of Models**: Queries multiple LLMs (local Ollama and remote OpenAI-compatible APIs like E-Infra vLLM) in parallel to brainstorm translation strategies. +3. **Translation Agent (ReAct)**: An autonomous reasoning and acting agent that generates the translation and iteratively refines it by calling validation tools (.NET compilation, Java Maven compilation, database schema extraction). +4. **Human-in-the-Loop**: If the agent exhausts its retry limit without achieving a valid translation, execution is paused for manual user correction. + +## Stack + +- **Runtime:** Python 3.11+ managed with `uv` +- **Framework:** LangGraph / LangChain +- **API Server:** FastAPI / LangGraph API +- **LLM Providers:** Ollama (local) and OpenAI-compatible endpoints (e.g., E-Infra vLLM) +- **State Persistence:** PostgreSQL with pgvector (via Docker Compose) + +## Setup + +1. Copy `.env.example` to `.env` and configure your API keys (e.g., `OPENAI_API_KEY` for E-Infra, `OLLAMA_HOST`). +2. Run the LangGraph development server: + ```bash + langgraph dev + ``` +3. Or run the full stack via Docker Compose from the repository root: + ```bash + docker compose up --build + ``` + +## Development + +- Install dependencies: `uv sync --group dev` +- Run tests: `make test` and `make integration_tests` +- Formatting & Linting: `make format` and `make lint` + +See `GEMINI.md` for AI agent instructions and conventions. \ No newline at end of file diff --git a/services/orchestrator/docker-compose.yml b/services/orchestrator/docker-compose.yml new file mode 100644 index 0000000..868dd4e --- /dev/null +++ b/services/orchestrator/docker-compose.yml @@ -0,0 +1,56 @@ +volumes: + langgraph-data: + driver: local + +services: + langgraph-redis: + image: redis:6 + healthcheck: + test: redis-cli ping + interval: 5s + timeout: 1s + retries: 5 + + langgraph-postgres: + image: pgvector/pgvector:pg16 + ports: + - "5433:5432" + environment: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + command: + - postgres + - -c + - shared_preload_libraries=vector + volumes: + - langgraph-data:/var/lib/postgresql/data + healthcheck: + test: pg_isready -U postgres + start_period: 10s + timeout: 1s + retries: 5 + interval: 60s + start_interval: 1s + + langgraph-api: + ports: + - "8123:8000" + depends_on: + langgraph-redis: + condition: service_healthy + langgraph-postgres: + condition: service_healthy + environment: + REDIS_URI: redis://langgraph-redis:6379 + POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable + healthcheck: + test: python /api/healthcheck.py + interval: 60s + start_interval: 1s + start_period: 10s + env_file: + - .env + build: + context: . + dockerfile: Dockerfile diff --git a/services/orchestrator/langgraph-prod.json b/services/orchestrator/langgraph-prod.json new file mode 100644 index 0000000..631379a --- /dev/null +++ b/services/orchestrator/langgraph-prod.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://langgra.ph/schema.json", + "python_version": "3.13", + "image_distro": "wolfi", + "dockerfile_lines": [ + "RUN apk update && apk add nodejs-24 npm && node --version && npm --version" + ], + "dependencies": [ + "." + ], + "graphs": { + "agent": "./src/react_agent/graph.py:graph" + }, + "env": ".env" +} \ No newline at end of file diff --git a/services/orchestrator/langgraph.json b/services/orchestrator/langgraph.json new file mode 100644 index 0000000..dfcc573 --- /dev/null +++ b/services/orchestrator/langgraph.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://langgra.ph/schema.json", + "python_version": "3.13", + "image_distro": "wolfi", + "dependencies": [ + "." + ], + "graphs": { + "agent": "./src/react_agent/graph.py:graph" + }, + "env": ".env.dev" +} \ No newline at end of file diff --git a/services/orchestrator/pyproject.toml b/services/orchestrator/pyproject.toml new file mode 100644 index 0000000..245a5f4 --- /dev/null +++ b/services/orchestrator/pyproject.toml @@ -0,0 +1,125 @@ +[project] +name = "uom-orchestrator" +version = "0.0.1" +description = "LLM Orchestrator for Universal Object Mapping using LangGraph" +authors = [{ name = "Martin Čorovčák", email = "martincorovcak@gmail.com" }] +readme = "README.md" +license = { text = "MIT" } +requires-python = ">=3.12,<4.0" +dependencies = [ + "langgraph>=1.2.0", + "langchain>=1.3.0", + "python-dotenv>=1.2.2", + "langchain-tavily>=0.2.18", + "fastapi>=0.136.1", + "uvicorn>=0.46.0", + "pydantic>=2.13.3", + "ollama>=0.6.2", + "langchain-ollama>=1.1.0", + "langchain-mcp-adapters>=0.2.2", + "langchain-openai>=1.2.1", + "toolbox-langchain>=1.0.0", + "httpx>=0.28.1", + "deepagents>=0.5.6", + "langfuse>=4.6.1", + "aiofiles>=24.1.0", + "types-aiofiles>=24.1.0", + "orjson>=3.11.8", + "deepdiff[optimize]>=9.0.0", + "logfire[httpx,requests]>=4.32.1", + "daytona>=0.174.0", + "python-json-logger>=4.1.0", + "redis>=7.4.0", + "langchain-redis>=0.2.5", + "langchain-community>=0.4.1", +] +# external-dependencies = [ +# "pkg:generic/node@24", +# "pkg:generic/npm@11", +# ] + +[project.optional-dependencies] +dev = ["ty>=0.0.23", "ruff>=0.6.1"] + +[dependency-groups] +dev = [ + "anyio>=4.13.0", + "debugpy>=1.8.20", + "langgraph-cli[inmem]>=0.4.24", + "pytest>=9.0.3", + "pytest-asyncio>=1.3.0", + "pytest-recording>=0.13.4", + "ty>=0.0.34", +] + +[build-system] +requires = ["setuptools>=73.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = ["langgraph.templates.react_agent", "react_agent"] +[tool.setuptools.package-dir] +"langgraph.templates.react_agent" = "src/react_agent" +"react_agent" = "src/react_agent" + + +[tool.setuptools.package-data] +"*" = ["py.typed"] + +[tool.ruff] +lint.select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "D", # pydocstyle + "D401", # First line should be in imperative mood + "T201", + "UP", +] +lint.ignore = [ + "UP006", + "UP007", + # We actually do want to import from typing_extensions + "UP035", + # Relax the convention by _not_ requiring documentation for every function parameter. + "D417", + "E501", + "D401", + "D106", + "D107", + "D101", + "D102", + "F841", +] +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["D", "UP"] +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.pytest.ini_options] +testpaths = ["tests"] +collect_ignore_glob = ["old/*"] +markers = [ + "integration: requires running Docker services", + "vcr: uses vcrpy to record/replay HTTP interactions", +] +log_cli = true + +[tool.ty.src] +include = ["src", "tests"] + +[tool.ty.rules] +unused-type-ignore-comment = "ignore" + +[[tool.ty.overrides]] +include = ["tests"] + +[tool.ty.overrides.rules] +missing-typed-dict-key = "warn" +invalid-key = "warn" + +[tool.pyright] +include = ["src", "tests"] +venvPath = "." +venv = ".venv" +reportUnnecessaryTypeIgnoreComment = "none" diff --git a/services/orchestrator/scripts/copy_snippets_to_context.sh b/services/orchestrator/scripts/copy_snippets_to_context.sh new file mode 100755 index 0000000..80db3fe --- /dev/null +++ b/services/orchestrator/scripts/copy_snippets_to_context.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +snippets_dir="src/context/snippets" + +# in orchestrator directory + +cp -f ../java-services/mongo_sandbox/src/main/java/uom/services/MongoQueryEntrypoint.java $snippets_dir +cp -f ../java-services/mongo_sandbox/src/main/java/uom/services/MongoSchemaValidationEntrypoint.java $snippets_dir + +cp -f ../java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jQueryEntrypoint.java $snippets_dir +cp -f ../java-services/neo4j_sandbox/src/main/java/uom/services/Neo4jSchemaValidationEntrypoint.java $snippets_dir + +cp -f ../dotnet-service/efcore-sandbox/EFCoreQueryEntrypoint.cs $snippets_dir +cp -f ../dotnet-service/efcore-sandbox/EFCoreSchemaValidationEntrypoint.cs $snippets_dir + +cp -f ../dotnet-service/nhibernate-sandbox/NHibernateQueryEntrypoint.cs $snippets_dir +cp -f ../dotnet-service/nhibernate-sandbox/NHibernateSchemaValidationEntrypoint.cs $snippets_dir + +cp -f ../dotnet-service/dapper-sandbox/DapperQueryEntrypoint.cs $snippets_dir +cp -f ../dotnet-service/dapper-sandbox/DapperSchemaValidationEntrypoint.cs $snippets_dir diff --git a/services/orchestrator/scripts/daytona-reset-proxy-dns.ps1 b/services/orchestrator/scripts/daytona-reset-proxy-dns.ps1 new file mode 100644 index 0000000..e8311ae --- /dev/null +++ b/services/orchestrator/scripts/daytona-reset-proxy-dns.ps1 @@ -0,0 +1,19 @@ +# 1. Ensure the script is running as Administrator +if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Error "This script requires Administrator privileges." + exit 1 +} + +# 2. Get active internet-connected adapters +$ActiveAdapters = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null } + +Write-Host "`n=== RESTORING ORIGINAL DNS (DHCP) ===" -ForegroundColor Cyan + +# 3. Restore DNS to Automatic (DHCP) +foreach ($Adapter in $ActiveAdapters) { + Write-Host "Resetting $($Adapter.InterfaceAlias) to Automatic DNS..." + # -ResetServerAddresses removes the static IPs and defaults back to DHCP + Set-DnsClientServerAddress -InterfaceIndex $Adapter.InterfaceIndex -ResetServerAddresses +} + +Write-Host "Done. Your system is back to normal." -ForegroundColor Green diff --git a/services/orchestrator/scripts/daytona-setup-proxy-dns.ps1 b/services/orchestrator/scripts/daytona-setup-proxy-dns.ps1 new file mode 100644 index 0000000..93069f7 --- /dev/null +++ b/services/orchestrator/scripts/daytona-setup-proxy-dns.ps1 @@ -0,0 +1,71 @@ +# Copyright 2025 Daytona Platforms Inc. +# SPDX-License-Identifier: AGPL-3.0 + +<# +.SYNOPSIS +Setup wildcard DNS for *.proxy.localhost -> 127.0.0.1 on Windows. +Requires Acrylic DNS Proxy (installed automatically via winget if missing). +#> + +# 1. Ensure the script is running as Administrator +if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + Write-Error "This script requires Administrator privileges. Please run PowerShell as Admin." + exit 1 +} + +# Define paths early so we can check if it's already installed +$AcrylicDir = "${env:ProgramFiles(x86)}\Acrylic DNS Proxy" +$AcrylicHostsPath = "$AcrylicDir\AcrylicHosts.txt" + +# 2. Check and Install Acrylic DNS Proxy +if (-not (Test-Path $AcrylicDir)) { + Write-Host "Installing Acrylic DNS Proxy via Winget (Windows equivalent to dnsmasq)..." + winget install Mayakron.AcrylicDNS --accept-package-agreements --accept-source-agreements --silent + + # Give the installer a moment to flush files to disk + Start-Sleep -Seconds 5 +} else { + Write-Host "Acrylic DNS Proxy is already installed. Skipping installation." -ForegroundColor Cyan +} + +# Verify installation was successful / exists +if (Test-Path $AcrylicHostsPath) { + # 3. Configure the Wildcard + Write-Host "Configuring wildcard DNS (*.proxy.localhost -> 127.0.0.1)..." + $DnsEntry = "127.0.0.1 *.proxy.localhost" + + # Read current hosts to avoid appending duplicate entries on multiple runs + $CurrentHosts = Get-Content -Path $AcrylicHostsPath -Raw + if ($CurrentHosts -notmatch [regex]::Escape($DnsEntry)) { + # Append to Acrylic's custom hosts file + Add-Content -Path $AcrylicHostsPath -Value $DnsEntry + Write-Host " -> Added wildcard entry to AcrylicHosts.txt" + } else { + Write-Host " -> Wildcard entry already exists. Skipping." + } + + # 4. Restart the Service + Write-Host "Restarting Acrylic DNS Proxy service..." + Restart-Service -Name "AcrylicDNSProxySvc" -ErrorAction Stop + + # 5. Set Windows to use the local DNS server for BOTH IPv4 and IPv6 + Write-Host "Configuring active network adapters to use local DNS..." + + # Fetch only adapters actively connected to the internet (have a gateway) + $ActiveConfigs = Get-NetIPConfiguration | Where-Object { $_.IPv4DefaultGateway -ne $null } + + foreach ($Config in $ActiveConfigs) { + Write-Host " -> Setting DNS for adapter: $($Config.InterfaceAlias)" + + # Windows will automatically assign IPv4 strings to IPv4 and IPv6 strings to IPv6 + $DnsServers = @("127.0.0.1", "::1", "8.8.8.8", "2001:4860:4860::8888") + + Set-DnsClientServerAddress -InterfaceIndex $Config.InterfaceIndex -ServerAddresses $DnsServers + } + + Write-Host "`nDone. Test with:" + Write-Host "Resolve-DnsName 2280-test.proxy.localhost" -ForegroundColor Green +} else { + Write-Error "Failed to locate AcrylicHosts.txt at $AcrylicHostsPath. Installation may have failed." + exit 1 +} \ No newline at end of file diff --git a/services/orchestrator/scripts/daytona-setup-proxy-dns.sh b/services/orchestrator/scripts/daytona-setup-proxy-dns.sh new file mode 100755 index 0000000..72eff85 --- /dev/null +++ b/services/orchestrator/scripts/daytona-setup-proxy-dns.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Copyright 2025 Daytona Platforms Inc. +# SPDX-License-Identifier: AGPL-3.0 + +# Setup DNS for *.proxy.localhost -> 127.0.0.1 + +if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + command -v dnsmasq >/dev/null || brew install dnsmasq + sudo mkdir -p /usr/local/etc /etc/resolver + echo "address=/proxy.localhost/127.0.0.1" | sudo tee -a /usr/local/etc/dnsmasq.conf + echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/proxy.localhost + brew services start dnsmasq +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + command -v dnsmasq >/dev/null || { sudo apt update && sudo apt install -y dnsmasq; } + echo "address=/proxy.localhost/127.0.0.1" | sudo tee -a /etc/dnsmasq.conf + sudo systemctl restart dnsmasq && sudo systemctl enable dnsmasq + echo -e "nameserver 127.0.0.1\nnameserver 8.8.8.8" | sudo tee /etc/resolv.conf +else + echo "Unsupported OS: $OSTYPE" && exit 1 +fi + +echo "Done. Test: dig 2280-test.proxy.localhost" \ No newline at end of file diff --git a/services/orchestrator/scripts/install_logdy.sh b/services/orchestrator/scripts/install_logdy.sh new file mode 100755 index 0000000..84e02b0 --- /dev/null +++ b/services/orchestrator/scripts/install_logdy.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +version='v0.17.0' + +os=$(uname -s | tr '[:upper:]' '[:lower:]') +machine=$(uname -m) + +case $os in +linux | darwin) + ext='' + ;; +windows) + os=windows + ext='.exe' + ;; +*) + echo "Unsupported OS: $os" >&2 + exit 1 + ;; +esac + +case $machine in +x86_64 | amd64) + arch=amd64 + ;; +arm64 | aarch64) + arch=arm64 + ;; +*) + echo "Unsupported architecture: $machine" >&2 + exit 1 + ;; +esac + +asset="logdy_${os}_${arch}${ext}" +echo "Installing Logdy.dev ${version} (${asset})" +curl -Lfs "https://github.com/logdyhq/logdy-core/releases/download/${version}/${asset}" -o logdy + +chmod +x logdy +mv logdy .venv/bin/logdy +# mv logdy /usr/local/bin/logdy diff --git a/services/orchestrator/scripts/install_node.sh b/services/orchestrator/scripts/install_node.sh new file mode 100755 index 0000000..550d714 --- /dev/null +++ b/services/orchestrator/scripts/install_node.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euo pipefail + +# Download and install nvm: +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash + +# in lieu of restarting the shell +\. "$HOME/.nvm/nvm.sh" + +# Download and install Node.js: +nvm install 24 + +# Verify the Node.js version: +node -v + +# Verify npm version: +npm -v diff --git a/services/orchestrator/scripts/load_env.ps1 b/services/orchestrator/scripts/load_env.ps1 new file mode 100644 index 0000000..88e47a1 --- /dev/null +++ b/services/orchestrator/scripts/load_env.ps1 @@ -0,0 +1,10 @@ +$envFile = if ($args.Count -gt 0) { $args[0] } else { '.env.dev' } + +Get-Content $envFile | foreach { + $name, $value = $_.split('=') + if ([string]::IsNullOrWhiteSpace($name) -or $name.Contains('#')) { + # skip empty or comment line in ENV file + return + } + Set-Content env:\$name $value +} diff --git a/services/orchestrator/src/config/database_tools.yaml b/services/orchestrator/src/config/database_tools.yaml new file mode 100644 index 0000000..e69de29 diff --git a/services/orchestrator/src/context/mappings/mssql_mongodb.json b/services/orchestrator/src/context/mappings/mssql_mongodb.json new file mode 100644 index 0000000..ec375e8 --- /dev/null +++ b/services/orchestrator/src/context/mappings/mssql_mongodb.json @@ -0,0 +1,7835 @@ +{ + "version": "1.8.0", + "project": { + "name": "UOM WideWorldImporters", + "type": "SQL_SERVER", + "lastModified": "2026-04-09T16:19:14.946Z", + "schemasId": "9e929ddbb2fd47a695b24131a75d0af4", + "jdbcId": "628f738685c243b2834831aa79f5dc08", + "mongodbId": "39e0dba6ff40462a9a6a3be0fd12a8cc", + "content": { + "settings": { + "shouldRecommendSchema": false, + "casing": "CAMEL_CASE", + "excludedJsonExportCollections": [], + "keyHandling": "GENERATED" + }, + "collections": { + "5ae73f19-fc2c-4cf3-9514-b021684ea118": { + "name": "suppliers" + }, + "67c830a5-d1e2-4067-89a3-940120710933": { + "name": "orderLines" + }, + "0050fc88-de6c-402c-a96d-f7c8805e0526": { + "name": "people" + }, + "5bd7402f-e4cd-4912-8577-7bc6488ae535": { + "name": "orders" + } + }, + "mappings": { + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItemStockGroups", + "foreignKeyName": "FK_Warehouse_StockItemStockGroups_StockItemID_Warehouse_StockItems" + }, + "fields": { + "StockItemStockGroupID": { + "target": { + "name": "stockItemStockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemStockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "StockGroupID": { + "target": { + "name": "stockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "67c830a5-d1e2-4067-89a3-940120710933", + "table": "9e8b294f-bc57-4cac-a069-428746557a3d" + }, + "9f196589-7438-4f25-bb13-8192bb30e9f8": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockGroups", + "foreignKeyName": "FK_Warehouse_StockGroups_Application_People" + }, + "fields": { + "StockGroupID": { + "target": { + "name": "stockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockGroupName": { + "target": { + "name": "stockGroupName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockGroupName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "425f7d7d-d603-454d-9b0c-e6da06bce933" + }, + "63db12cb-a4e7-47fa-9812-c8545aef2483": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItems", + "foreignKeyName": "FK_Warehouse_StockItems_Application_People" + }, + "fields": { + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemName": { + "target": { + "name": "stockItemName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockItemName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ColorID": { + "target": { + "name": "colorId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ColorID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPackageID": { + "target": { + "name": "unitPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "UnitPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OuterPackageID": { + "target": { + "name": "outerPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OuterPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Brand": { + "target": { + "name": "brand", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Brand", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Size": { + "target": { + "name": "size", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Size", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LeadTimeDays": { + "target": { + "name": "leadTimeDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LeadTimeDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "QuantityPerOuter": { + "target": { + "name": "quantityPerOuter", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "QuantityPerOuter", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "IsChillerStock": { + "target": { + "name": "isChillerStock", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsChillerStock", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Barcode": { + "target": { + "name": "barcode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Barcode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "RecommendedRetailPrice": { + "target": { + "name": "recommendedRetailPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "RecommendedRetailPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TypicalWeightPerUnit": { + "target": { + "name": "typicalWeightPerUnit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TypicalWeightPerUnit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "MarketingComments": { + "target": { + "name": "marketingComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "MarketingComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Tags": { + "target": { + "name": "tags", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Tags", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchDetails": { + "target": { + "name": "searchDetails", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchDetails", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc" + }, + "f59c4734-7712-43a7-98a9-7d4b81e08a79": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItemStockGroups", + "foreignKeyName": "FK_Warehouse_StockItemStockGroups_Application_People" + }, + "fields": { + "StockItemStockGroupID": { + "target": { + "name": "stockItemStockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemStockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "StockGroupID": { + "target": { + "name": "stockGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "9e8b294f-bc57-4cac-a069-428746557a3d" + }, + "6ea7e1fe-2957-4481-bea7-c29313e65f43": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "stockItems", + "foreignKeyName": "FK_Warehouse_StockItems_SupplierID_Purchasing_Suppliers" + }, + "fields": { + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemName": { + "target": { + "name": "stockItemName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockItemName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ColorID": { + "target": { + "name": "colorId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ColorID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPackageID": { + "target": { + "name": "unitPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "UnitPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OuterPackageID": { + "target": { + "name": "outerPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OuterPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Brand": { + "target": { + "name": "brand", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Brand", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Size": { + "target": { + "name": "size", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Size", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LeadTimeDays": { + "target": { + "name": "leadTimeDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LeadTimeDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "QuantityPerOuter": { + "target": { + "name": "quantityPerOuter", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "QuantityPerOuter", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "IsChillerStock": { + "target": { + "name": "isChillerStock", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsChillerStock", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Barcode": { + "target": { + "name": "barcode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Barcode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "RecommendedRetailPrice": { + "target": { + "name": "recommendedRetailPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "RecommendedRetailPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TypicalWeightPerUnit": { + "target": { + "name": "typicalWeightPerUnit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TypicalWeightPerUnit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "MarketingComments": { + "target": { + "name": "marketingComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "MarketingComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Tags": { + "target": { + "name": "tags", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Tags", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchDetails": { + "target": { + "name": "searchDetails", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchDetails", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5ae73f19-fc2c-4cf3-9514-b021684ea118", + "table": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc" + }, + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "customerTransactions", + "foreignKeyName": "FK_Sales_CustomerTransactions_CustomerID_Sales_Customers" + }, + "fields": { + "CustomerTransactionID": { + "target": { + "name": "customerTransactionId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerTransactionID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionTypeID": { + "target": { + "name": "transactionTypeId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "TransactionTypeID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "InvoiceID": { + "target": { + "name": "invoiceId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "InvoiceID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PaymentMethodID": { + "target": { + "name": "paymentMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionDate": { + "target": { + "name": "transactionDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "TransactionDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "AmountExcludingTax": { + "target": { + "name": "amountExcludingTax", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "AmountExcludingTax", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TaxAmount": { + "target": { + "name": "taxAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TransactionAmount": { + "target": { + "name": "transactionAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TransactionAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "OutstandingBalance": { + "target": { + "name": "outstandingBalance", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "OutstandingBalance", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "FinalizationDate": { + "target": { + "name": "finalizationDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "FinalizationDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "IsFinalized": { + "target": { + "name": "isFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5bd7402f-e4cd-4912-8577-7bc6488ae535", + "table": "72f6e4d6-a373-4657-9922-0b0752282ed6" + }, + "1b45b1e6-c743-412c-8ff7-04f63402c385": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "OrderID": { + "target": { + "name": "orderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OrderID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "SalespersonPersonID": { + "target": { + "name": "salespersonPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SalespersonPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PickedByPersonID": { + "target": { + "name": "pickedByPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PickedByPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ContactPersonID": { + "target": { + "name": "contactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "BackorderOrderID": { + "target": { + "name": "backorderOrderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BackorderOrderID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OrderDate": { + "target": { + "name": "orderDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "OrderDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "ExpectedDeliveryDate": { + "target": { + "name": "expectedDeliveryDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ExpectedDeliveryDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "CustomerPurchaseOrderNumber": { + "target": { + "name": "customerPurchaseOrderNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomerPurchaseOrderNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsUndersupplyBackordered": { + "target": { + "name": "isUndersupplyBackordered", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsUndersupplyBackordered", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Comments": { + "target": { + "name": "comments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Comments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryInstructions": { + "target": { + "name": "deliveryInstructions", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryInstructions", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PickingCompletedWhen": { + "target": { + "name": "pickingCompletedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "PickingCompletedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5bd7402f-e4cd-4912-8577-7bc6488ae535", + "table": "e9551f17-aa01-44d4-b1bd-96ce8d92906f" + }, + "44de7a1e-e986-4c92-8a88-6c7b6879eb89": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "SupplierName": { + "target": { + "name": "supplierName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierCategoryID": { + "target": { + "name": "supplierCategoryId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierCategoryID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PrimaryContactPersonID": { + "target": { + "name": "primaryContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PrimaryContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "AlternateContactPersonID": { + "target": { + "name": "alternateContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "AlternateContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryCityID": { + "target": { + "name": "deliveryCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PostalCityID": { + "target": { + "name": "postalCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PostalCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "SupplierReference": { + "target": { + "name": "supplierReference", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierReference", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountName": { + "target": { + "name": "bankAccountName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountBranch": { + "target": { + "name": "bankAccountBranch", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountBranch", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountCode": { + "target": { + "name": "bankAccountCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankAccountNumber": { + "target": { + "name": "bankAccountNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankAccountNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BankInternationalCode": { + "target": { + "name": "bankInternationalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "BankInternationalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PaymentDays": { + "target": { + "name": "paymentDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "WebsiteURL": { + "target": { + "name": "websiteUrl", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "WebsiteURL", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine1": { + "target": { + "name": "deliveryAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine2": { + "target": { + "name": "deliveryAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryPostalCode": { + "target": { + "name": "deliveryPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryLocation": { + "target": { + "name": "deliveryLocation", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "DeliveryLocation", + "databaseSpecificType": "geography", + "isPrimaryKey": false + } + }, + "PostalAddressLine1": { + "target": { + "name": "postalAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalAddressLine2": { + "target": { + "name": "postalAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalPostalCode": { + "target": { + "name": "postalPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5ae73f19-fc2c-4cf3-9514-b021684ea118", + "table": "204a2525-4420-4864-b4f3-6aa6b5b87009" + }, + "414ebc35-d5a9-4055-b976-aae8fbb07014": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "PersonID": { + "target": { + "name": "personId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PersonID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "FullName": { + "target": { + "name": "fullName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FullName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PreferredName": { + "target": { + "name": "preferredName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PreferredName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchName": { + "target": { + "name": "searchName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsPermittedToLogon": { + "target": { + "name": "isPermittedToLogon", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsPermittedToLogon", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "LogonName": { + "target": { + "name": "logonName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "LogonName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsExternalLogonProvider": { + "target": { + "name": "isExternalLogonProvider", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsExternalLogonProvider", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "HashedPassword": { + "target": { + "name": "hashedPassword", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "HashedPassword", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "IsSystemUser": { + "target": { + "name": "isSystemUser", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsSystemUser", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsEmployee": { + "target": { + "name": "isEmployee", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsEmployee", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsSalesperson": { + "target": { + "name": "isSalesperson", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsSalesperson", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "UserPreferences": { + "target": { + "name": "userPreferences", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "UserPreferences", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "EmailAddress": { + "target": { + "name": "emailAddress", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "EmailAddress", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "OtherLanguages": { + "target": { + "name": "otherLanguages", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "OtherLanguages", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "cb798bd4-dd41-4e73-a608-7043c3fae427" + }, + "7c6c2855-a520-4c17-84f8-b073f8bf9c83": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "customerTransactions", + "foreignKeyName": "FK_Sales_CustomerTransactions_Application_People" + }, + "fields": { + "CustomerTransactionID": { + "target": { + "name": "customerTransactionId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerTransactionID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionTypeID": { + "target": { + "name": "transactionTypeId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "TransactionTypeID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "InvoiceID": { + "target": { + "name": "invoiceId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "InvoiceID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PaymentMethodID": { + "target": { + "name": "paymentMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "TransactionDate": { + "target": { + "name": "transactionDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "TransactionDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "AmountExcludingTax": { + "target": { + "name": "amountExcludingTax", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "AmountExcludingTax", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TaxAmount": { + "target": { + "name": "taxAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TransactionAmount": { + "target": { + "name": "transactionAmount", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TransactionAmount", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "OutstandingBalance": { + "target": { + "name": "outstandingBalance", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "OutstandingBalance", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "FinalizationDate": { + "target": { + "name": "finalizationDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "FinalizationDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "IsFinalized": { + "target": { + "name": "isFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "72f6e4d6-a373-4657-9922-0b0752282ed6" + }, + "f8055db3-d857-4243-9a2e-3ad5d745f8ba": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "purchaseOrders", + "foreignKeyName": "FK_Purchasing_PurchaseOrders_SupplierID_Purchasing_Suppliers" + }, + "fields": { + "PurchaseOrderID": { + "target": { + "name": "purchaseOrderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PurchaseOrderID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OrderDate": { + "target": { + "name": "orderDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "OrderDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ContactPersonID": { + "target": { + "name": "contactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ExpectedDeliveryDate": { + "target": { + "name": "expectedDeliveryDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ExpectedDeliveryDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "SupplierReference": { + "target": { + "name": "supplierReference", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierReference", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsOrderFinalized": { + "target": { + "name": "isOrderFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOrderFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Comments": { + "target": { + "name": "comments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Comments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5ae73f19-fc2c-4cf3-9514-b021684ea118", + "table": "1e704ab1-0f91-464b-bd2e-82453fb1b08d" + }, + "36077cd4-107a-4238-8529-afaccb0d12c5": { + "settings": { + "type": "EMBEDDED_DOCUMENT", + "embeddedPath": "stockItem", + "foreignKeyName": "FK_Sales_OrderLines_StockItemID_Warehouse_StockItems" + }, + "fields": { + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "StockItemName": { + "target": { + "name": "stockItemName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "StockItemName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ColorID": { + "target": { + "name": "colorId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ColorID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPackageID": { + "target": { + "name": "unitPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "UnitPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OuterPackageID": { + "target": { + "name": "outerPackageId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OuterPackageID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Brand": { + "target": { + "name": "brand", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Brand", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Size": { + "target": { + "name": "size", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Size", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LeadTimeDays": { + "target": { + "name": "leadTimeDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LeadTimeDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "QuantityPerOuter": { + "target": { + "name": "quantityPerOuter", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "QuantityPerOuter", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "IsChillerStock": { + "target": { + "name": "isChillerStock", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsChillerStock", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Barcode": { + "target": { + "name": "barcode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Barcode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "RecommendedRetailPrice": { + "target": { + "name": "recommendedRetailPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "RecommendedRetailPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TypicalWeightPerUnit": { + "target": { + "name": "typicalWeightPerUnit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TypicalWeightPerUnit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "MarketingComments": { + "target": { + "name": "marketingComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "MarketingComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Photo": { + "target": { + "name": "photo", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "Photo", + "databaseSpecificType": "varbinary", + "isPrimaryKey": false + } + }, + "CustomFields": { + "target": { + "name": "customFields", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomFields", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "Tags": { + "target": { + "name": "tags", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Tags", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "SearchDetails": { + "target": { + "name": "searchDetails", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SearchDetails", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "67c830a5-d1e2-4067-89a3-940120710933", + "table": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc" + }, + "0e36a268-5d48-434c-9064-41cef0cc9d7f": { + "settings": { + "type": "NEW_DOCUMENT" + }, + "fields": { + "_id": { + "target": { + "name": "_id", + "included": true, + "isNullExcluded": false, + "type": "OBJECT_ID" + }, + "source": { + "name": "_id", + "databaseSpecificType": "NONE", + "isPrimaryKey": false + } + }, + "OrderLineID": { + "target": { + "name": "orderLineId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OrderLineID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "OrderID": { + "target": { + "name": "orderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "OrderID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "StockItemID": { + "target": { + "name": "stockItemId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "StockItemID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Description": { + "target": { + "name": "description", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Description", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PackageTypeID": { + "target": { + "name": "packageTypeId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PackageTypeID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "Quantity": { + "target": { + "name": "quantity", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "Quantity", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "UnitPrice": { + "target": { + "name": "unitPrice", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "UnitPrice", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "TaxRate": { + "target": { + "name": "taxRate", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "TaxRate", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "PickedQuantity": { + "target": { + "name": "pickedQuantity", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PickedQuantity", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PickingCompletedWhen": { + "target": { + "name": "pickingCompletedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "PickingCompletedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "67c830a5-d1e2-4067-89a3-940120710933", + "table": "36254277-7ab4-4e9d-9409-5570705349a3" + }, + "1bf20c42-5de3-452d-80dc-10e3dc127c05": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "customers", + "foreignKeyName": "FK_Sales_Customers_Application_People" + }, + "fields": { + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerName": { + "target": { + "name": "customerName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomerName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BillToCustomerID": { + "target": { + "name": "billToCustomerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BillToCustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CustomerCategoryID": { + "target": { + "name": "customerCategoryId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerCategoryID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "BuyingGroupID": { + "target": { + "name": "buyingGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BuyingGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PrimaryContactPersonID": { + "target": { + "name": "primaryContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PrimaryContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "AlternateContactPersonID": { + "target": { + "name": "alternateContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "AlternateContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryCityID": { + "target": { + "name": "deliveryCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PostalCityID": { + "target": { + "name": "postalCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PostalCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CreditLimit": { + "target": { + "name": "creditLimit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "CreditLimit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "AccountOpenedDate": { + "target": { + "name": "accountOpenedDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "AccountOpenedDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "StandardDiscountPercentage": { + "target": { + "name": "standardDiscountPercentage", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "StandardDiscountPercentage", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "IsStatementSent": { + "target": { + "name": "isStatementSent", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsStatementSent", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsOnCreditHold": { + "target": { + "name": "isOnCreditHold", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOnCreditHold", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "PaymentDays": { + "target": { + "name": "paymentDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryRun": { + "target": { + "name": "deliveryRun", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryRun", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "RunPosition": { + "target": { + "name": "runPosition", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "RunPosition", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "WebsiteURL": { + "target": { + "name": "websiteUrl", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "WebsiteURL", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine1": { + "target": { + "name": "deliveryAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine2": { + "target": { + "name": "deliveryAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryPostalCode": { + "target": { + "name": "deliveryPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryLocation": { + "target": { + "name": "deliveryLocation", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "DeliveryLocation", + "databaseSpecificType": "geography", + "isPrimaryKey": false + } + }, + "PostalAddressLine1": { + "target": { + "name": "postalAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalAddressLine2": { + "target": { + "name": "postalAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalPostalCode": { + "target": { + "name": "postalPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "b86d8cee-b856-4461-8766-cfc4cb41d67f" + }, + "8ae46573-a830-4364-8227-a9d0923eb860": { + "settings": { + "type": "EMBEDDED_DOCUMENT", + "embeddedPath": "customer", + "foreignKeyName": "FK_Sales_Orders_CustomerID_Sales_Customers" + }, + "fields": { + "CustomerID": { + "target": { + "name": "customerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "CustomerName": { + "target": { + "name": "customerName", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "CustomerName", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "BillToCustomerID": { + "target": { + "name": "billToCustomerId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BillToCustomerID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CustomerCategoryID": { + "target": { + "name": "customerCategoryId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "CustomerCategoryID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "BuyingGroupID": { + "target": { + "name": "buyingGroupId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "BuyingGroupID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PrimaryContactPersonID": { + "target": { + "name": "primaryContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PrimaryContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "AlternateContactPersonID": { + "target": { + "name": "alternateContactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "AlternateContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "DeliveryCityID": { + "target": { + "name": "deliveryCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PostalCityID": { + "target": { + "name": "postalCityId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PostalCityID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "CreditLimit": { + "target": { + "name": "creditLimit", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "CreditLimit", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "AccountOpenedDate": { + "target": { + "name": "accountOpenedDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "AccountOpenedDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "StandardDiscountPercentage": { + "target": { + "name": "standardDiscountPercentage", + "included": true, + "isNullExcluded": false, + "type": "DECIMAL" + }, + "source": { + "name": "StandardDiscountPercentage", + "databaseSpecificType": "decimal", + "isPrimaryKey": false + } + }, + "IsStatementSent": { + "target": { + "name": "isStatementSent", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsStatementSent", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "IsOnCreditHold": { + "target": { + "name": "isOnCreditHold", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOnCreditHold", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "PaymentDays": { + "target": { + "name": "paymentDays", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PaymentDays", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "PhoneNumber": { + "target": { + "name": "phoneNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PhoneNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "FaxNumber": { + "target": { + "name": "faxNumber", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "FaxNumber", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryRun": { + "target": { + "name": "deliveryRun", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryRun", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "RunPosition": { + "target": { + "name": "runPosition", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "RunPosition", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "WebsiteURL": { + "target": { + "name": "websiteUrl", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "WebsiteURL", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine1": { + "target": { + "name": "deliveryAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryAddressLine2": { + "target": { + "name": "deliveryAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryPostalCode": { + "target": { + "name": "deliveryPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "DeliveryPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "DeliveryLocation": { + "target": { + "name": "deliveryLocation", + "included": true, + "isNullExcluded": false, + "type": "BIN_DATA" + }, + "source": { + "name": "DeliveryLocation", + "databaseSpecificType": "geography", + "isPrimaryKey": false + } + }, + "PostalAddressLine1": { + "target": { + "name": "postalAddressLine1", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine1", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalAddressLine2": { + "target": { + "name": "postalAddressLine2", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalAddressLine2", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "PostalPostalCode": { + "target": { + "name": "postalPostalCode", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "PostalPostalCode", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ValidFrom": { + "target": { + "name": "validFrom", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidFrom", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + }, + "ValidTo": { + "target": { + "name": "validTo", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ValidTo", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "5bd7402f-e4cd-4912-8577-7bc6488ae535", + "table": "b86d8cee-b856-4461-8766-cfc4cb41d67f" + }, + "f793abcd-17fa-48cd-b9f0-e0226de2421f": { + "settings": { + "type": "EMBEDDED_DOCUMENT_ARRAY", + "embeddedPath": "purchaseOrders", + "foreignKeyName": "FK_Purchasing_PurchaseOrders_Application_People" + }, + "fields": { + "PurchaseOrderID": { + "target": { + "name": "purchaseOrderId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "PurchaseOrderID", + "databaseSpecificType": "int", + "isPrimaryKey": true + } + }, + "SupplierID": { + "target": { + "name": "supplierId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "SupplierID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "OrderDate": { + "target": { + "name": "orderDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "OrderDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "DeliveryMethodID": { + "target": { + "name": "deliveryMethodId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "DeliveryMethodID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ContactPersonID": { + "target": { + "name": "contactPersonId", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "ContactPersonID", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "ExpectedDeliveryDate": { + "target": { + "name": "expectedDeliveryDate", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "ExpectedDeliveryDate", + "databaseSpecificType": "date", + "isPrimaryKey": false + } + }, + "SupplierReference": { + "target": { + "name": "supplierReference", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "SupplierReference", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "IsOrderFinalized": { + "target": { + "name": "isOrderFinalized", + "included": true, + "isNullExcluded": false, + "type": "BOOL" + }, + "source": { + "name": "IsOrderFinalized", + "databaseSpecificType": "bit", + "isPrimaryKey": false + } + }, + "Comments": { + "target": { + "name": "comments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "Comments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "InternalComments": { + "target": { + "name": "internalComments", + "included": true, + "isNullExcluded": false, + "type": "STRING" + }, + "source": { + "name": "InternalComments", + "databaseSpecificType": "nvarchar", + "isPrimaryKey": false + } + }, + "LastEditedBy": { + "target": { + "name": "lastEditedBy", + "included": true, + "isNullExcluded": false, + "type": "INTEGER" + }, + "source": { + "name": "LastEditedBy", + "databaseSpecificType": "int", + "isPrimaryKey": false + } + }, + "LastEditedWhen": { + "target": { + "name": "lastEditedWhen", + "included": true, + "isNullExcluded": false, + "type": "DATE" + }, + "source": { + "name": "LastEditedWhen", + "databaseSpecificType": "datetime2", + "isPrimaryKey": false + } + } + }, + "calculatedFields": {}, + "collectionId": "0050fc88-de6c-402c-a96d-f7c8805e0526", + "table": "1e704ab1-0f91-464b-bd2e-82453fb1b08d" + } + }, + "relationships": { + "tables": { + "WideWorldImporters.Sales.Orders": { + "mappings": [ + "1b45b1e6-c743-412c-8ff7-04f63402c385" + ] + }, + "WideWorldImporters.Sales.Customers": { + "mappings": [ + "1bf20c42-5de3-452d-80dc-10e3dc127c05", + "8ae46573-a830-4364-8227-a9d0923eb860" + ] + }, + "WideWorldImporters.Purchasing.Suppliers": { + "mappings": [ + "44de7a1e-e986-4c92-8a88-6c7b6879eb89" + ] + }, + "WideWorldImporters.Warehouse.StockGroups": { + "mappings": [ + "9f196589-7438-4f25-bb13-8192bb30e9f8" + ] + }, + "WideWorldImporters.Purchasing.PurchaseOrders": { + "mappings": [ + "f8055db3-d857-4243-9a2e-3ad5d745f8ba", + "f793abcd-17fa-48cd-b9f0-e0226de2421f" + ] + }, + "WideWorldImporters.Sales.CustomerTransactions": { + "mappings": [ + "7c6c2855-a520-4c17-84f8-b073f8bf9c83", + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f" + ] + }, + "WideWorldImporters.Application.People": { + "mappings": [ + "414ebc35-d5a9-4055-b976-aae8fbb07014" + ] + }, + "WideWorldImporters.Warehouse.StockItemStockGroups": { + "mappings": [ + "f59c4734-7712-43a7-98a9-7d4b81e08a79", + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c" + ] + }, + "WideWorldImporters.Warehouse.StockItems": { + "mappings": [ + "6ea7e1fe-2957-4481-bea7-c29313e65f43", + "63db12cb-a4e7-47fa-9812-c8545aef2483", + "36077cd4-107a-4238-8529-afaccb0d12c5" + ] + }, + "WideWorldImporters.Sales.OrderLines": { + "mappings": [ + "0e36a268-5d48-434c-9064-41cef0cc9d7f" + ] + } + }, + "collections": { + "5ae73f19-fc2c-4cf3-9514-b021684ea118": { + "mappings": [ + "44de7a1e-e986-4c92-8a88-6c7b6879eb89", + "6ea7e1fe-2957-4481-bea7-c29313e65f43", + "f8055db3-d857-4243-9a2e-3ad5d745f8ba" + ] + }, + "67c830a5-d1e2-4067-89a3-940120710933": { + "mappings": [ + "0e36a268-5d48-434c-9064-41cef0cc9d7f", + "36077cd4-107a-4238-8529-afaccb0d12c5", + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c" + ] + }, + "0050fc88-de6c-402c-a96d-f7c8805e0526": { + "mappings": [ + "414ebc35-d5a9-4055-b976-aae8fbb07014", + "9f196589-7438-4f25-bb13-8192bb30e9f8", + "63db12cb-a4e7-47fa-9812-c8545aef2483", + "f793abcd-17fa-48cd-b9f0-e0226de2421f", + "1bf20c42-5de3-452d-80dc-10e3dc127c05", + "f59c4734-7712-43a7-98a9-7d4b81e08a79", + "7c6c2855-a520-4c17-84f8-b073f8bf9c83" + ] + }, + "5bd7402f-e4cd-4912-8577-7bc6488ae535": { + "mappings": [ + "1b45b1e6-c743-412c-8ff7-04f63402c385", + "8ae46573-a830-4364-8227-a9d0923eb860", + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f" + ] + } + }, + "mappings": { + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c": { + "children": [] + }, + "9f196589-7438-4f25-bb13-8192bb30e9f8": { + "children": [] + }, + "63db12cb-a4e7-47fa-9812-c8545aef2483": { + "children": [] + }, + "f59c4734-7712-43a7-98a9-7d4b81e08a79": { + "children": [] + }, + "6ea7e1fe-2957-4481-bea7-c29313e65f43": { + "children": [] + }, + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f": { + "children": [] + }, + "1b45b1e6-c743-412c-8ff7-04f63402c385": { + "children": [ + "8ae46573-a830-4364-8227-a9d0923eb860" + ] + }, + "44de7a1e-e986-4c92-8a88-6c7b6879eb89": { + "children": [ + "6ea7e1fe-2957-4481-bea7-c29313e65f43", + "f8055db3-d857-4243-9a2e-3ad5d745f8ba" + ] + }, + "414ebc35-d5a9-4055-b976-aae8fbb07014": { + "children": [ + "9f196589-7438-4f25-bb13-8192bb30e9f8", + "63db12cb-a4e7-47fa-9812-c8545aef2483", + "f793abcd-17fa-48cd-b9f0-e0226de2421f", + "1bf20c42-5de3-452d-80dc-10e3dc127c05", + "f59c4734-7712-43a7-98a9-7d4b81e08a79", + "7c6c2855-a520-4c17-84f8-b073f8bf9c83" + ] + }, + "7c6c2855-a520-4c17-84f8-b073f8bf9c83": { + "children": [] + }, + "f8055db3-d857-4243-9a2e-3ad5d745f8ba": { + "children": [] + }, + "36077cd4-107a-4238-8529-afaccb0d12c5": { + "children": [ + "84a19f24-5938-4ac5-bab3-e5a1d5d31a1c" + ] + }, + "0e36a268-5d48-434c-9064-41cef0cc9d7f": { + "children": [ + "36077cd4-107a-4238-8529-afaccb0d12c5" + ] + }, + "1bf20c42-5de3-452d-80dc-10e3dc127c05": { + "children": [] + }, + "8ae46573-a830-4364-8227-a9d0923eb860": { + "children": [ + "5d2191d3-e4c0-46aa-885a-a1b85f2ede0f" + ] + }, + "f793abcd-17fa-48cd-b9f0-e0226de2421f": { + "children": [] + } + } + }, + "diagrams": { + "activeTab": "df44aaa5-0a16-402e-a9d3-cd1bc87078c7", + "tabs": [ + { + "id": "df44aaa5-0a16-402e-a9d3-cd1bc87078c7", + "name": "Main Diagram", + "relational": { + "nodes": [ + { + "id": "425f7d7d-d603-454d-9b0c-e6da06bce933", + "type": "entityCard", + "position": { + "x": 12, + "y": 1200 + }, + "width": 244, + "height": 134, + "hidden": false + }, + { + "id": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "type": "entityCard", + "position": { + "x": 531, + "y": 372 + }, + "width": 244, + "height": 494, + "hidden": false + }, + { + "id": "9e8b294f-bc57-4cac-a069-428746557a3d", + "type": "entityCard", + "position": { + "x": 316.6666666666667, + "y": 138 + }, + "width": 244, + "height": 134, + "hidden": false + }, + { + "id": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "type": "entityCard", + "position": { + "x": 449.6666666666667, + "y": 978 + }, + "width": 244, + "height": 566, + "hidden": false + }, + { + "id": "1e704ab1-0f91-464b-bd2e-82453fb1b08d", + "type": "entityCard", + "position": { + "x": 184.66666666666669, + "y": 606 + }, + "width": 244, + "height": 260, + "hidden": false + }, + { + "id": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "type": "entityCard", + "position": { + "x": 885, + "y": 480 + }, + "width": 244, + "height": 332, + "hidden": false + }, + { + "id": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "type": "entityCard", + "position": { + "x": 1147.6666666666667, + "y": 966 + }, + "width": 244, + "height": 602, + "hidden": false + }, + { + "id": "36254277-7ab4-4e9d-9409-5570705349a3", + "type": "entityCard", + "position": { + "x": 885, + "y": 12 + }, + "width": 244, + "height": 260, + "hidden": false + }, + { + "id": "72f6e4d6-a373-4657-9922-0b0752282ed6", + "type": "entityCard", + "position": { + "x": 1229, + "y": 570 + }, + "width": 244, + "height": 296, + "hidden": false + }, + { + "id": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "type": "entityCard", + "position": { + "x": 109.6, + "y": 1668 + }, + "width": 244, + "height": 422, + "hidden": false + } + ], + "edges": [ + { + "id": "3fbfb3a9-b2d2-4a3c-8df4-0c68e9b83efd", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "425f7d7d-d603-454d-9b0c-e6da06bce933", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "2af463ac-831a-44b4-ba30-11ff11269e66", + "source": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "target": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "5791b2f2-19c3-4187-a683-0ee7c59dfbb5", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "eb8c695a-8bf4-49d2-b7d7-05d6c105eb4d", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "9e8b294f-bc57-4cac-a069-428746557a3d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "ff7d64f4-25e8-4b85-9a96-b897969291d6", + "source": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "target": "9e8b294f-bc57-4cac-a069-428746557a3d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "928f5f2e-0052-4ea6-b44c-2e9b477f9660", + "source": "425f7d7d-d603-454d-9b0c-e6da06bce933", + "target": "9e8b294f-bc57-4cac-a069-428746557a3d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "f87307b3-e9b4-401e-9cc7-f5c4246caf40", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "11900e22-71b4-448c-9c85-da437e73992f", + "source": "204a2525-4420-4864-b4f3-6aa6b5b87009", + "target": "1e704ab1-0f91-464b-bd2e-82453fb1b08d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "5c496fa9-c0db-40c1-9b19-d86494883c5a", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "1e704ab1-0f91-464b-bd2e-82453fb1b08d", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "52f00cb6-6425-475c-bac7-f556d7203212", + "source": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "target": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "cc48303a-9f36-4685-b369-77694d0dd922", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "54b3a4b8-e07e-42e8-b11c-1fd1ade50b83", + "source": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "target": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "f815a3b0-42a5-4cd7-8841-0cde392cba12", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "371ed3a4-c5b1-4acb-a025-298078c02d38", + "source": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "target": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "3a563a20-dd69-4e5c-9f84-ab4e4e7a600c", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "36254277-7ab4-4e9d-9409-5570705349a3", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "627cf0af-d18c-4fa2-94e0-93fc058ddf5a", + "source": "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc", + "target": "36254277-7ab4-4e9d-9409-5570705349a3", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "098e49ac-d389-4be5-8a65-cd12d527a30d", + "source": "e9551f17-aa01-44d4-b1bd-96ce8d92906f", + "target": "36254277-7ab4-4e9d-9409-5570705349a3", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "d8dd8314-fa4b-402d-a674-f1c09fba04f4", + "source": "b86d8cee-b856-4461-8766-cfc4cb41d67f", + "target": "72f6e4d6-a373-4657-9922-0b0752282ed6", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "88533981-99fd-428d-9b5a-b6f9d5476da0", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "72f6e4d6-a373-4657-9922-0b0752282ed6", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "06ee654a-a1eb-4ab7-8c3a-0b718d1d13dd", + "source": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "target": "cb798bd4-dd41-4e73-a608-7043c3fae427", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + } + ] + }, + "collection": { + "nodes": [ + { + "id": "suppliers", + "type": "entityCard", + "position": { + "x": 23, + "y": 966 + }, + "width": 244, + "height": 1286, + "hidden": false + }, + { + "id": "orderLines", + "type": "entityCard", + "position": { + "x": 23, + "y": 12 + }, + "width": 244, + "height": 854, + "hidden": false + }, + { + "id": "people", + "type": "entityCard", + "position": { + "x": 114.33333333333334, + "y": 2352 + }, + "width": 244, + "height": 2204, + "hidden": false + }, + { + "id": "orders", + "type": "entityCard", + "position": { + "x": 73.66666666666669, + "y": 4656 + }, + "width": 244, + "height": 1196, + "hidden": false + } + ], + "edges": [ + { + "id": "d22072de-1a00-4ab0-9c42-c97fcdd1c871", + "source": "people", + "target": "suppliers", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "c42b02dd-136b-47b9-8152-b151d895d315", + "source": "people", + "target": "orderLines", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "762870a8-1375-44ee-8d17-7aabd798ecb4", + "source": "suppliers", + "target": "orderLines", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "292b166f-2a44-44ea-a633-35cdd82ec15c", + "source": "orders", + "target": "orderLines", + "markerStart": "START_ONE", + "markerEnd": "END_MANY" + }, + { + "id": "e49fd968-df89-4b8f-9da9-d2510484f89c", + "source": "people", + "target": "people", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "c48cc9e8-730e-4288-828c-77783b659ce3", + "source": "orders", + "target": "people", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + }, + { + "id": "0b53107b-191f-492b-af8f-26f388732a95", + "source": "orders", + "target": "orders", + "markerStart": "START_ONE_OR_MANY", + "markerEnd": "END_ONE_OR_MANY" + } + ] + } + } + ] + }, + "tables": { + "425f7d7d-d603-454d-9b0c-e6da06bce933": { + "path": { + "database": "WideWorldImporters", + "schema": "Warehouse", + "table": "StockGroups" + } + }, + "1cc1d41b-81e1-44d8-96d2-972f4b7d1bfc": { + "path": { + "database": "WideWorldImporters", + "schema": "Warehouse", + "table": "StockItems" + } + }, + "9e8b294f-bc57-4cac-a069-428746557a3d": { + "path": { + "database": "WideWorldImporters", + "schema": "Warehouse", + "table": "StockItemStockGroups" + } + }, + "204a2525-4420-4864-b4f3-6aa6b5b87009": { + "path": { + "database": "WideWorldImporters", + "schema": "Purchasing", + "table": "Suppliers" + } + }, + "1e704ab1-0f91-464b-bd2e-82453fb1b08d": { + "path": { + "database": "WideWorldImporters", + "schema": "Purchasing", + "table": "PurchaseOrders" + } + }, + "e9551f17-aa01-44d4-b1bd-96ce8d92906f": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "Orders" + } + }, + "b86d8cee-b856-4461-8766-cfc4cb41d67f": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "Customers" + } + }, + "36254277-7ab4-4e9d-9409-5570705349a3": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "OrderLines" + } + }, + "72f6e4d6-a373-4657-9922-0b0752282ed6": { + "path": { + "database": "WideWorldImporters", + "schema": "Sales", + "table": "CustomerTransactions" + } + }, + "cb798bd4-dd41-4e73-a608-7043c3fae427": { + "path": { + "database": "WideWorldImporters", + "schema": "Application", + "table": "People" + } + } + } + }, + "connectionDetails": {}, + "id": "d2bac673378a4060a14312eaf7c01d91" + }, + "schema": { + "id": "9e929ddbb2fd47a695b24131a75d0af4", + "full": { + "databases": { + "WideWorldImporters": { + "schemas": { + "Warehouse": { + "tables": { + "StockGroups": { + "type": "TABLE", + "size": -1, + "columns": { + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Warehouse_StockGroups_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 5, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Warehouse_StockGroups" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "StockGroupName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 4, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Warehouse_StockGroups": [ + "StockGroupID" + ], + "UQ_Warehouse_StockGroups_StockGroupName": [ + "StockGroupName" + ] + }, + "nonUniqueIndexes": {} + }, + "StockItems": { + "type": "TABLE", + "size": -1, + "columns": { + "RecommendedRetailPrice": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 15, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Size": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Warehouse_StockItems" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "Photo": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "varbinary", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 19, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsChillerStock": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ColorID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Warehouse_StockItems_ColorID_Warehouse_Colors", + "schema": "Warehouse", + "table": "Colors", + "column": "ColorID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "SupplierID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Warehouse_StockItems_SupplierID_Purchasing_Suppliers", + "schema": "Purchasing", + "table": "Suppliers", + "column": "SupplierID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "QuantityPerOuter": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "MarketingComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TaxRate": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 13, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 24, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OuterPackageID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Warehouse_StockItems_OuterPackageID_Warehouse_PackageTypes", + "schema": "Warehouse", + "table": "PackageTypes", + "column": "PackageTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "UnitPackageID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Warehouse_StockItems_UnitPackageID_Warehouse_PackageTypes", + "schema": "Warehouse", + "table": "PackageTypes", + "column": "PackageTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "Tags": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 21, + "nullable": true, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TypicalWeightPerUnit": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 16, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LeadTimeDays": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SearchDetails": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 22, + "nullable": false, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Brand": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "UnitPrice": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 14, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomFields": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 20, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 23, + "foreignKey": { + "name": "FK_Warehouse_StockItems_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Barcode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 25, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + } + }, + "uniqueIndexes": { + "UQ_Warehouse_StockItems_StockItemName": [ + "StockItemName" + ], + "PK_Warehouse_StockItems": [ + "StockItemID" + ] + }, + "nonUniqueIndexes": { + "FK_Warehouse_StockItems_OuterPackageID": [ + "OuterPackageID" + ], + "FK_Warehouse_StockItems_UnitPackageID": [ + "UnitPackageID" + ], + "FK_Warehouse_StockItems_SupplierID": [ + "SupplierID" + ], + "FK_Warehouse_StockItems_ColorID": [ + "ColorID" + ] + } + }, + "StockItemStockGroups": { + "type": "TABLE", + "size": -1, + "columns": { + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Warehouse_StockItemStockGroups_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Warehouse_StockItemStockGroups_StockItemID_Warehouse_StockItems", + "schema": "Warehouse", + "table": "StockItems", + "column": "StockItemID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "StockGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Warehouse_StockItemStockGroups_StockGroupID_Warehouse_StockGroups", + "schema": "Warehouse", + "table": "StockGroups", + "column": "StockGroupID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 5, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "StockItemStockGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Warehouse_StockItemStockGroups" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + } + }, + "uniqueIndexes": { + "UQ_StockItemStockGroups_StockGroupID_Lookup": [ + "StockGroupID", + "StockItemID" + ], + "PK_Warehouse_StockItemStockGroups": [ + "StockItemStockGroupID" + ], + "UQ_StockItemStockGroups_StockItemID_Lookup": [ + "StockItemID", + "StockGroupID" + ] + }, + "nonUniqueIndexes": {} + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + }, + "Purchasing": { + "tables": { + "Suppliers": { + "type": "TABLE", + "size": -1, + "columns": { + "PrimaryContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_PrimaryContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "DeliveryMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_DeliveryMethodID_Application_DeliveryMethods", + "schema": "Application", + "table": "DeliveryMethods", + "column": "DeliveryMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PostalAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 24, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankAccountBranch": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 25, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PaymentDays": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 15, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankInternationalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 14, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Purchasing_Suppliers" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "DeliveryPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 22, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankAccountCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 26, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FaxNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 28, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryLocation": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "geography", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 23, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierReference": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 8, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_PostalCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "DeliveryCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 7, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_DeliveryCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 16, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "WebsiteURL": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 256, + "scale": 0 + }, + "ordinalPosition": 19, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierCategoryID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_SupplierCategoryID_Purchasing_SupplierCategories", + "schema": "Purchasing", + "table": "SupplierCategories", + "column": "SupplierCategoryID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "BankAccountName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 27, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 29, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "AlternateContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Purchasing_Suppliers_AlternateContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PhoneNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 21, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 20, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BankAccountNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 13, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "UQ_Purchasing_Suppliers_SupplierName": [ + "SupplierName" + ], + "PK_Purchasing_Suppliers": [ + "SupplierID" + ] + }, + "nonUniqueIndexes": { + "FK_Purchasing_Suppliers_DeliveryMethodID": [ + "DeliveryMethodID" + ], + "FK_Purchasing_Suppliers_DeliveryCityID": [ + "DeliveryCityID" + ], + "FK_Purchasing_Suppliers_PostalCityID": [ + "PostalCityID" + ], + "FK_Purchasing_Suppliers_PrimaryContactPersonID": [ + "PrimaryContactPersonID" + ], + "FK_Purchasing_Suppliers_SupplierCategoryID": [ + "SupplierCategoryID" + ], + "FK_Purchasing_Suppliers_AlternateContactPersonID": [ + "AlternateContactPersonID" + ] + } + }, + "PurchaseOrders": { + "type": "TABLE", + "size": -1, + "columns": { + "SupplierID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_SupplierID_Purchasing_Suppliers", + "schema": "Purchasing", + "table": "Suppliers", + "column": "SupplierID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 11, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Comments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_DeliveryMethodID_Application_DeliveryMethods", + "schema": "Application", + "table": "DeliveryMethods", + "column": "DeliveryMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PurchaseOrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Purchasing_PurchaseOrders" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "ContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Purchasing_PurchaseOrders_ContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "ExpectedDeliveryDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SupplierReference": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsOrderFinalized": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 12, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OrderDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Purchasing_PurchaseOrders": [ + "PurchaseOrderID" + ] + }, + "nonUniqueIndexes": { + "FK_Purchasing_PurchaseOrders_ContactPersonID": [ + "ContactPersonID" + ], + "FK_Purchasing_PurchaseOrders_DeliveryMethodID": [ + "DeliveryMethodID" + ], + "FK_Purchasing_PurchaseOrders_SupplierID": [ + "SupplierID" + ] + } + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + }, + "Sales": { + "tables": { + "Orders": { + "type": "TABLE", + "size": -1, + "columns": { + "BackorderOrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Sales_Orders_BackorderOrderID_Sales_Orders", + "schema": "Sales", + "table": "Orders", + "column": "OrderID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryInstructions": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsUndersupplyBackordered": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Comments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "InternalComments": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 13, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_Orders_ContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Sales_Orders_CustomerID_Sales_Customers", + "schema": "Sales", + "table": "Customers", + "column": "CustomerID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "OrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_Orders" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "OrderDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SalespersonPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_Orders_SalespersonPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PickingCompletedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 14, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 15, + "foreignKey": { + "name": "FK_Sales_Orders_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PickedByPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Sales_Orders_PickedByPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerPurchaseOrderNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ExpectedDeliveryDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 16, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Sales_Orders": [ + "OrderID" + ] + }, + "nonUniqueIndexes": { + "FK_Sales_Orders_PickedByPersonID": [ + "PickedByPersonID" + ], + "FK_Sales_Orders_SalespersonPersonID": [ + "SalespersonPersonID" + ], + "FK_Sales_Orders_CustomerID": [ + "CustomerID" + ], + "FK_Sales_Orders_ContactPersonID": [ + "ContactPersonID" + ] + } + }, + "Customers": { + "type": "TABLE", + "size": -1, + "columns": { + "PrimaryContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "foreignKey": { + "name": "FK_Sales_Customers_PrimaryContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "DeliveryMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 8, + "foreignKey": { + "name": "FK_Sales_Customers_DeliveryMethodID_Application_DeliveryMethods", + "schema": "Application", + "table": "DeliveryMethods", + "column": "DeliveryMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PostalAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 26, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomerCategoryID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Sales_Customers_CustomerCategoryID_Sales_CustomerCategories", + "schema": "Sales", + "table": "CustomerCategories", + "column": "CustomerCategoryID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PostalAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 27, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_Customers" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "PaymentDays": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 16, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "RunPosition": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 5, + "scale": 0 + }, + "ordinalPosition": 20, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 24, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalPostalCode": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 28, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FaxNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "BuyingGroupID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_Customers_BuyingGroupID_Sales_BuyingGroups", + "schema": "Sales", + "table": "BuyingGroups", + "column": "BuyingGroupID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 30, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsOnCreditHold": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 15, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "DeliveryLocation": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "geography", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 25, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PostalCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 10, + "foreignKey": { + "name": "FK_Sales_Customers_PostalCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "BillToCustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_Customers_BillToCustomerID_Sales_Customers", + "schema": "Sales", + "table": "Customers", + "column": "CustomerID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "AccountOpenedDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryCityID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 9, + "foreignKey": { + "name": "FK_Sales_Customers_DeliveryCityID_Application_Cities", + "schema": "Application", + "table": "Cities", + "column": "CityID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CreditLimit": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "WebsiteURL": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 256, + "scale": 0 + }, + "ordinalPosition": 21, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsStatementSent": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 14, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 29, + "foreignKey": { + "name": "FK_Sales_Customers_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 31, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "AlternateContactPersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 7, + "foreignKey": { + "name": "FK_Sales_Customers_AlternateContactPersonID_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "StandardDiscountPercentage": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 13, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PhoneNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine2": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 23, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryAddressLine1": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 60, + "scale": 0 + }, + "ordinalPosition": 22, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "DeliveryRun": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 5, + "scale": 0 + }, + "ordinalPosition": 19, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "UQ_Sales_Customers_CustomerName": [ + "CustomerName" + ], + "PK_Sales_Customers": [ + "CustomerID" + ] + }, + "nonUniqueIndexes": { + "FK_Sales_Customers_PrimaryContactPersonID": [ + "PrimaryContactPersonID" + ], + "IX_Sales_Customers_Perf_20160301_06": [ + "IsOnCreditHold", + "CustomerID", + "BillToCustomerID" + ], + "FK_Sales_Customers_DeliveryMethodID": [ + "DeliveryMethodID" + ], + "FK_Sales_Customers_CustomerCategoryID": [ + "CustomerCategoryID" + ], + "FK_Sales_Customers_AlternateContactPersonID": [ + "AlternateContactPersonID" + ], + "FK_Sales_Customers_PostalCityID": [ + "PostalCityID" + ], + "FK_Sales_Customers_BuyingGroupID": [ + "BuyingGroupID" + ], + "FK_Sales_Customers_DeliveryCityID": [ + "DeliveryCityID" + ] + } + }, + "OrderLines": { + "type": "TABLE", + "size": -1, + "columns": { + "UnitPrice": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 7, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PickingCompletedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 10, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 11, + "foreignKey": { + "name": "FK_Sales_OrderLines_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Description": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 100, + "scale": 0 + }, + "ordinalPosition": 4, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PickedQuantity": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OrderLineID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_OrderLines" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "StockItemID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_OrderLines_StockItemID_Warehouse_StockItems", + "schema": "Warehouse", + "table": "StockItems", + "column": "StockItemID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "TaxRate": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 3 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Quantity": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OrderID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Sales_OrderLines_OrderID_Sales_Orders", + "schema": "Sales", + "table": "Orders", + "column": "OrderID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "PackageTypeID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_OrderLines_PackageTypeID_Warehouse_PackageTypes", + "schema": "Warehouse", + "table": "PackageTypes", + "column": "PackageTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 12, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Sales_OrderLines": [ + "OrderLineID" + ] + }, + "nonUniqueIndexes": { + "IX_Sales_OrderLines_Perf_20160301_01": [ + "PickingCompletedWhen", + "OrderID", + "OrderLineID" + ], + "FK_Sales_OrderLines_OrderID": [ + "OrderID" + ], + "IX_Sales_OrderLines_AllocatedStockItems": [ + "StockItemID" + ], + "IX_Sales_OrderLines_Perf_20160301_02": [ + "StockItemID", + "PickingCompletedWhen" + ], + "FK_Sales_OrderLines_PackageTypeID": [ + "PackageTypeID" + ] + } + }, + "CustomerTransactions": { + "type": "TABLE", + "size": -1, + "columns": { + "IsFinalized": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "AmountExcludingTax": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 7, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OutstandingBalance": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PaymentMethodID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 5, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_PaymentMethodID_Application_PaymentMethods", + "schema": "Application", + "table": "PaymentMethods", + "column": "PaymentMethodID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "TransactionTypeID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 3, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_TransactionTypeID_Application_TransactionTypes", + "schema": "Application", + "table": "TransactionTypes", + "column": "TransactionTypeID", + "cardinality": "UNKNOWN" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "CustomerTransactionID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Sales_CustomerTransactions" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "CustomerID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 2, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_CustomerID_Sales_Customers", + "schema": "Sales", + "table": "Customers", + "column": "CustomerID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "InvoiceID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 4, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_InvoiceID_Sales_Invoices", + "schema": "Sales", + "table": "Invoices", + "column": "InvoiceID", + "cardinality": "UNKNOWN" + }, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "TransactionAmount": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TaxAmount": { + "type": { + "defaultMongoFieldType": "DECIMAL", + "databaseSpecificType": "decimal", + "precision": 18, + "scale": 2 + }, + "ordinalPosition": 8, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 13, + "foreignKey": { + "name": "FK_Sales_CustomerTransactions_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FinalizationDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedWhen": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 14, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "TransactionDate": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "date", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Sales_CustomerTransactions": [ + "CustomerTransactionID" + ] + }, + "nonUniqueIndexes": { + "CX_Sales_CustomerTransactions": [ + "TransactionDate" + ], + "IX_Sales_CustomerTransactions_IsFinalized": [ + "IsFinalized" + ], + "FK_Sales_CustomerTransactions_CustomerID": [ + "CustomerID" + ], + "FK_Sales_CustomerTransactions_TransactionTypeID": [ + "TransactionTypeID" + ], + "FK_Sales_CustomerTransactions_PaymentMethodID": [ + "PaymentMethodID" + ], + "FK_Sales_CustomerTransactions_InvoiceID": [ + "InvoiceID" + ] + } + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + }, + "Application": { + "tables": { + "People": { + "type": "TABLE", + "size": -1, + "columns": { + "UserPreferences": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 12, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "SearchName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 101, + "scale": 0 + }, + "ordinalPosition": 4, + "nullable": false, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "Photo": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "varbinary", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 16, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsSystemUser": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 9, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "EmailAddress": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 256, + "scale": 0 + }, + "ordinalPosition": 15, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "CustomFields": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 17, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LastEditedBy": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 19, + "foreignKey": { + "name": "FK_Application_People_Application_People", + "schema": "Application", + "table": "People", + "column": "PersonID", + "cardinality": "ZERO_TO_MANY" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PersonID": { + "type": { + "defaultMongoFieldType": "INTEGER", + "databaseSpecificType": "int", + "precision": 10, + "scale": 0 + }, + "ordinalPosition": 1, + "primaryKey": { + "name": "PK_Application_People" + }, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": true + }, + "IsPermittedToLogon": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 5, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "IsEmployee": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 10, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "ValidTo": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 21, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "FullName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 2, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + }, + "FaxNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 14, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "OtherLanguages": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 18, + "nullable": true, + "autoIncremented": false, + "generated": true, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PhoneNumber": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 20, + "scale": 0 + }, + "ordinalPosition": 13, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "ValidFrom": { + "type": { + "defaultMongoFieldType": "DATE", + "databaseSpecificType": "datetime2", + "precision": 27, + "scale": 7 + }, + "ordinalPosition": 20, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsExternalLogonProvider": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 7, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "PreferredName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 3, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "LogonName": { + "type": { + "defaultMongoFieldType": "STRING", + "databaseSpecificType": "nvarchar", + "precision": 50, + "scale": 0 + }, + "ordinalPosition": 6, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "HashedPassword": { + "type": { + "defaultMongoFieldType": "BIN_DATA", + "databaseSpecificType": "varbinary", + "precision": 2147483647, + "scale": 0 + }, + "ordinalPosition": 8, + "nullable": true, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": false, + "uniqueIndexed": false + }, + "IsSalesperson": { + "type": { + "defaultMongoFieldType": "BOOL", + "databaseSpecificType": "bit", + "precision": 1, + "scale": 0 + }, + "ordinalPosition": 11, + "nullable": false, + "autoIncremented": false, + "generated": false, + "hidden": false, + "indexed": true, + "uniqueIndexed": false + } + }, + "uniqueIndexes": { + "PK_Application_People": [ + "PersonID" + ] + }, + "nonUniqueIndexes": { + "IX_Application_People_Perf_20160301_05": [ + "IsPermittedToLogon", + "PersonID" + ], + "IX_Application_People_IsEmployee": [ + "IsEmployee" + ], + "IX_Application_People_IsSalesperson": [ + "IsSalesperson" + ], + "IX_Application_People_FullName": [ + "FullName" + ] + } + } + }, + "views": {}, + "routines": {}, + "triggers": {}, + "packages": {} + } + } + } + }, + "metadata": { + "databaseType": "SQL_SERVER", + "databaseVersion": "16.00.4245" + } + }, + "imported": { + "WideWorldImporters": { + "Warehouse": { + "StockGroups": {}, + "StockItems": {}, + "StockItemStockGroups": {} + }, + "Purchasing": { + "Suppliers": {}, + "PurchaseOrders": {} + }, + "Sales": { + "Orders": {}, + "Customers": {}, + "OrderLines": {}, + "CustomerTransactions": {} + }, + "Application": { + "People": {} + } + } + }, + "all": { + "WideWorldImporters": { + "Warehouse": { + "PackageTypes": {}, + "StockGroups": {}, + "ColdRoomTemperatures_Archive": {}, + "StockItems": {}, + "StockItemStockGroups": {}, + "Colors_Archive": {}, + "Colors": {}, + "PackageTypes_Archive": {}, + "StockItemTransactions": {}, + "StockItems_Archive": {}, + "ColdRoomTemperatures": {}, + "StockItemHoldings": {}, + "StockGroups_Archive": {}, + "VehicleTemperatures": {} + }, + "Purchasing": { + "SupplierTransactions": {}, + "Suppliers": {}, + "PurchaseOrderLines": {}, + "PurchaseOrders": {}, + "SupplierCategories_Archive": {}, + "SupplierCategories": {}, + "Suppliers_Archive": {} + }, + "Sales": { + "BuyingGroups": {}, + "Orders": {}, + "InvoiceLines": {}, + "CustomerCategories": {}, + "CustomerCategories_Archive": {}, + "Customers": {}, + "BuyingGroups_Archive": {}, + "OrderLines": {}, + "SpecialDeals": {}, + "CustomerTransactions": {}, + "Customers_Archive": {}, + "Invoices": {} + }, + "Application": { + "StateProvinces_Archive": {}, + "Countries": {}, + "People": {}, + "TransactionTypes_Archive": {}, + "Countries_Archive": {}, + "PaymentMethods": {}, + "PaymentMethods_Archive": {}, + "People_Archive": {}, + "Cities_Archive": {}, + "StateProvinces": {}, + "DeliveryMethods_Archive": {}, + "SystemParameters": {}, + "TransactionTypes": {}, + "DeliveryMethods": {}, + "Cities": {} + } + } + } + } +} \ No newline at end of file diff --git a/services/orchestrator/src/context/mappings/mssql_neo4j.json b/services/orchestrator/src/context/mappings/mssql_neo4j.json new file mode 100644 index 0000000..9d82775 --- /dev/null +++ b/services/orchestrator/src/context/mappings/mssql_neo4j.json @@ -0,0 +1,5127 @@ +[ + { + "name": "NODE_WideWorldImporters.Sales.OrderLines_ac45fa67-b059-4330-8bd2-a25fef94921b", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"OrderLineID\" AS \"OrderLineID\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"OrderLineID\" AS \"OrderLineID\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"Quantity\" AS \"Quantity\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"Description\" AS \"Description\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"PickedQuantity\" AS \"PickedQuantity\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"UnitPrice\" AS \"UnitPrice\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"PickingCompletedWhen\" AS \"PickingCompletedWhen\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"TaxRate\" AS \"TaxRate\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"LastEditedWhen\" AS \"LastEditedWhen\", 'OrderLine' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Sales\".\"OrderLines\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderLineID", + "alias": "OrderLineID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.sales.orderlines" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderLineID", + "alias": "OrderLineID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "orderLineId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "Quantity", + "alias": "Quantity", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "quantity", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "Description", + "alias": "Description", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "description", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "PickedQuantity", + "alias": "PickedQuantity", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "pickedQuantity", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "UnitPrice", + "alias": "UnitPrice", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "unitPrice", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "PickingCompletedWhen", + "alias": "PickingCompletedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "pickingCompletedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "TaxRate", + "alias": "TaxRate", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "taxRate", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedWhen", + "alias": "LastEditedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastEditedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderLine", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Warehouse.StockGroups_05b83b24-7bba-4a4e-a6ef-c4a604d5912c", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"StockGroupID\" AS \"StockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"StockGroupID\" AS \"StockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"ValidFrom\" AS \"ValidFrom\", \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"ValidTo\" AS \"ValidTo\", \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"StockGroupName\" AS \"StockGroupName\", 'StockGroup' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockGroups\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroupID", + "alias": "StockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.warehouse.stockgroups" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroupID", + "alias": "StockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "stockGroupId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "ValidFrom", + "alias": "ValidFrom", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validFrom", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "ValidTo", + "alias": "ValidTo", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validTo", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroupName", + "alias": "StockGroupName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "stockGroupName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroup", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Purchasing.Suppliers_65818064-8b4a-4603-9f9a-463a808b9772", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierID\" AS \"SupplierID\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierID\" AS \"SupplierID\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"ValidTo\" AS \"ValidTo\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"WebsiteURL\" AS \"WebsiteURL\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"DeliveryPostalCode\" AS \"DeliveryPostalCode\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"BankInternationalCode\" AS \"BankInternationalCode\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"PostalPostalCode\" AS \"PostalPostalCode\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"ValidFrom\" AS \"ValidFrom\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"BankAccountCode\" AS \"BankAccountCode\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"BankAccountBranch\" AS \"BankAccountBranch\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"PostalAddressLine1\" AS \"PostalAddressLine1\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"BankAccountName\" AS \"BankAccountName\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"PaymentDays\" AS \"PaymentDays\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"FaxNumber\" AS \"FaxNumber\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"BankAccountNumber\" AS \"BankAccountNumber\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierName\" AS \"SupplierName\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"PhoneNumber\" AS \"PhoneNumber\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"PostalAddressLine2\" AS \"PostalAddressLine2\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"DeliveryAddressLine1\" AS \"DeliveryAddressLine1\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"DeliveryAddressLine2\" AS \"DeliveryAddressLine2\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"InternalComments\" AS \"InternalComments\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierReference\" AS \"SupplierReference\", 'Supplier' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Purchasing\".\"Suppliers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.purchasing.suppliers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "supplierId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "ValidTo", + "alias": "ValidTo", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validTo", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "WebsiteURL", + "alias": "WebsiteURL", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "websiteUrl", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "DeliveryPostalCode", + "alias": "DeliveryPostalCode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryPostalCode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "BankInternationalCode", + "alias": "BankInternationalCode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "bankInternationalCode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PostalPostalCode", + "alias": "PostalPostalCode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "postalPostalCode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "ValidFrom", + "alias": "ValidFrom", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validFrom", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "BankAccountCode", + "alias": "BankAccountCode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "bankAccountCode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "BankAccountBranch", + "alias": "BankAccountBranch", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "bankAccountBranch", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PostalAddressLine1", + "alias": "PostalAddressLine1", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "postalAddressLine1", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "BankAccountName", + "alias": "BankAccountName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "bankAccountName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PaymentDays", + "alias": "PaymentDays", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "paymentDays", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "FaxNumber", + "alias": "FaxNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "faxNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "BankAccountNumber", + "alias": "BankAccountNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "bankAccountNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierName", + "alias": "SupplierName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "supplierName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PhoneNumber", + "alias": "PhoneNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "phoneNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PostalAddressLine2", + "alias": "PostalAddressLine2", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "postalAddressLine2", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "DeliveryAddressLine1", + "alias": "DeliveryAddressLine1", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryAddressLine1", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "DeliveryAddressLine2", + "alias": "DeliveryAddressLine2", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryAddressLine2", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "InternalComments", + "alias": "InternalComments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "internalComments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierReference", + "alias": "SupplierReference", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "supplierReference", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "Supplier", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Sales.Customers_8ed0008e-d49e-47b1-b2ae-5a903392839e", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerID\" AS \"CustomerID\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerID\" AS \"CustomerID\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"IsOnCreditHold\" AS \"IsOnCreditHold\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"PhoneNumber\" AS \"PhoneNumber\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"WebsiteURL\" AS \"WebsiteURL\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"PostalAddressLine1\" AS \"PostalAddressLine1\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"PostalAddressLine2\" AS \"PostalAddressLine2\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"DeliveryPostalCode\" AS \"DeliveryPostalCode\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"DeliveryRun\" AS \"DeliveryRun\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"AccountOpenedDate\" AS \"AccountOpenedDate\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"PaymentDays\" AS \"PaymentDays\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"RunPosition\" AS \"RunPosition\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"ValidFrom\" AS \"ValidFrom\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"ValidTo\" AS \"ValidTo\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerName\" AS \"CustomerName\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"IsStatementSent\" AS \"IsStatementSent\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"StandardDiscountPercentage\" AS \"StandardDiscountPercentage\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"CreditLimit\" AS \"CreditLimit\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"PostalPostalCode\" AS \"PostalPostalCode\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"DeliveryAddressLine2\" AS \"DeliveryAddressLine2\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"FaxNumber\" AS \"FaxNumber\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"DeliveryAddressLine1\" AS \"DeliveryAddressLine1\", 'Customer' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Sales\".\"Customers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "customerId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "IsOnCreditHold", + "alias": "IsOnCreditHold", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isOnCreditHold", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PhoneNumber", + "alias": "PhoneNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "phoneNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "WebsiteURL", + "alias": "WebsiteURL", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "websiteUrl", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PostalAddressLine1", + "alias": "PostalAddressLine1", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "postalAddressLine1", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PostalAddressLine2", + "alias": "PostalAddressLine2", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "postalAddressLine2", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "DeliveryPostalCode", + "alias": "DeliveryPostalCode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryPostalCode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "DeliveryRun", + "alias": "DeliveryRun", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryRun", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "AccountOpenedDate", + "alias": "AccountOpenedDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "accountOpenedDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PaymentDays", + "alias": "PaymentDays", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "paymentDays", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "RunPosition", + "alias": "RunPosition", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "runPosition", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "ValidFrom", + "alias": "ValidFrom", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validFrom", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "ValidTo", + "alias": "ValidTo", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validTo", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerName", + "alias": "CustomerName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "customerName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "IsStatementSent", + "alias": "IsStatementSent", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isStatementSent", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "StandardDiscountPercentage", + "alias": "StandardDiscountPercentage", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "standardDiscountPercentage", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CreditLimit", + "alias": "CreditLimit", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "creditLimit", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PostalPostalCode", + "alias": "PostalPostalCode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "postalPostalCode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "DeliveryAddressLine2", + "alias": "DeliveryAddressLine2", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryAddressLine2", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "FaxNumber", + "alias": "FaxNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "faxNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "DeliveryAddressLine1", + "alias": "DeliveryAddressLine1", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryAddressLine1", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "Customer", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Purchasing.PurchaseOrders_ccb558d2-a7df-440b-9a30-d7e59f682b43", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"PurchaseOrderID\" AS \"PurchaseOrderID\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"PurchaseOrderID\" AS \"PurchaseOrderID\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"InternalComments\" AS \"InternalComments\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"ExpectedDeliveryDate\" AS \"ExpectedDeliveryDate\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"SupplierReference\" AS \"SupplierReference\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"Comments\" AS \"Comments\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"LastEditedWhen\" AS \"LastEditedWhen\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"OrderDate\" AS \"OrderDate\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"IsOrderFinalized\" AS \"IsOrderFinalized\", 'PurchaseOrder' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PurchaseOrderID", + "alias": "PurchaseOrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.purchasing.purchaseorders" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PurchaseOrderID", + "alias": "PurchaseOrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "purchaseOrderId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "InternalComments", + "alias": "InternalComments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "internalComments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "ExpectedDeliveryDate", + "alias": "ExpectedDeliveryDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "expectedDeliveryDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierReference", + "alias": "SupplierReference", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "supplierReference", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "Comments", + "alias": "Comments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "comments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "LastEditedWhen", + "alias": "LastEditedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastEditedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "OrderDate", + "alias": "OrderDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "orderDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "IsOrderFinalized", + "alias": "IsOrderFinalized", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isOrderFinalized", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PurchaseOrder", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Warehouse.StockItemStockGroups_f0077a15-dd3f-4be7-b5fd-4e7e3bf2f0e8", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockItemStockGroupID\" AS \"StockItemStockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockItemStockGroupID\" AS \"StockItemStockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"LastEditedWhen\" AS \"LastEditedWhen\", 'StockItemStockGroup' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemStockGroupID", + "alias": "StockItemStockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.warehouse.stockitemstockgroups" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemStockGroupID", + "alias": "StockItemStockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "stockItemStockGroupId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "LastEditedWhen", + "alias": "LastEditedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastEditedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemStockGroup", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Sales.Orders_cb3a04f2-cbab-4a45-b850-9f9b386835d5", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"IsUndersupplyBackordered\" AS \"IsUndersupplyBackordered\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"InternalComments\" AS \"InternalComments\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"PickingCompletedWhen\" AS \"PickingCompletedWhen\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"LastEditedWhen\" AS \"LastEditedWhen\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"CustomerPurchaseOrderNumber\" AS \"CustomerPurchaseOrderNumber\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"DeliveryInstructions\" AS \"DeliveryInstructions\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"ExpectedDeliveryDate\" AS \"ExpectedDeliveryDate\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderDate\" AS \"OrderDate\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"Comments\" AS \"Comments\", 'Order' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "orderId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "IsUndersupplyBackordered", + "alias": "IsUndersupplyBackordered", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isUndersupplyBackordered", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "InternalComments", + "alias": "InternalComments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "internalComments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "PickingCompletedWhen", + "alias": "PickingCompletedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "pickingCompletedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedWhen", + "alias": "LastEditedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastEditedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "CustomerPurchaseOrderNumber", + "alias": "CustomerPurchaseOrderNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "customerPurchaseOrderNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "DeliveryInstructions", + "alias": "DeliveryInstructions", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "deliveryInstructions", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "ExpectedDeliveryDate", + "alias": "ExpectedDeliveryDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "expectedDeliveryDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderDate", + "alias": "OrderDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "orderDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "Comments", + "alias": "Comments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "comments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "Order", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Sales.CustomerTransactions_e199192f-66fe-49a7-927d-eb276c48b6ef", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"CustomerTransactionID\" AS \"CustomerTransactionID\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"CustomerTransactionID\" AS \"CustomerTransactionID\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"TransactionAmount\" AS \"TransactionAmount\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"LastEditedWhen\" AS \"LastEditedWhen\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"AmountExcludingTax\" AS \"AmountExcludingTax\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"OutstandingBalance\" AS \"OutstandingBalance\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"TransactionDate\" AS \"TransactionDate\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"TaxAmount\" AS \"TaxAmount\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"FinalizationDate\" AS \"FinalizationDate\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"IsFinalized\" AS \"IsFinalized\", 'CustomerTransaction' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CustomerTransactionID", + "alias": "CustomerTransactionID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.sales.customertransactions" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CustomerTransactionID", + "alias": "CustomerTransactionID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "customerTransactionId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "TransactionAmount", + "alias": "TransactionAmount", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "transactionAmount", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedWhen", + "alias": "LastEditedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastEditedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "AmountExcludingTax", + "alias": "AmountExcludingTax", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "amountExcludingTax", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "OutstandingBalance", + "alias": "OutstandingBalance", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "outstandingBalance", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "TransactionDate", + "alias": "TransactionDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "transactionDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "TaxAmount", + "alias": "TaxAmount", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "taxAmount", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "FinalizationDate", + "alias": "FinalizationDate", + "sql-data-type": "DATE", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "finalizationDate", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "IsFinalized", + "alias": "IsFinalized", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isFinalized", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CustomerTransaction", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Application.People_7ed902f6-335b-4748-aa32-43963a30c74c", + "schema": "WideWorldImporters.Application", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Application\".\"People\".\"PersonID\" AS \"PersonID\", \"WideWorldImporters\".\"Application\".\"People\".\"PersonID\" AS \"PersonID\", \"WideWorldImporters\".\"Application\".\"People\".\"IsSystemUser\" AS \"IsSystemUser\", \"WideWorldImporters\".\"Application\".\"People\".\"FaxNumber\" AS \"FaxNumber\", \"WideWorldImporters\".\"Application\".\"People\".\"EmailAddress\" AS \"EmailAddress\", \"WideWorldImporters\".\"Application\".\"People\".\"IsPermittedToLogon\" AS \"IsPermittedToLogon\", \"WideWorldImporters\".\"Application\".\"People\".\"ValidTo\" AS \"ValidTo\", \"WideWorldImporters\".\"Application\".\"People\".\"PreferredName\" AS \"PreferredName\", \"WideWorldImporters\".\"Application\".\"People\".\"LogonName\" AS \"LogonName\", \"WideWorldImporters\".\"Application\".\"People\".\"SearchName\" AS \"SearchName\", \"WideWorldImporters\".\"Application\".\"People\".\"IsSalesperson\" AS \"IsSalesperson\", \"WideWorldImporters\".\"Application\".\"People\".\"IsEmployee\" AS \"IsEmployee\", \"WideWorldImporters\".\"Application\".\"People\".\"FullName\" AS \"FullName\", \"WideWorldImporters\".\"Application\".\"People\".\"ValidFrom\" AS \"ValidFrom\", \"WideWorldImporters\".\"Application\".\"People\".\"CustomFields\" AS \"CustomFields\", \"WideWorldImporters\".\"Application\".\"People\".\"IsExternalLogonProvider\" AS \"IsExternalLogonProvider\", \"WideWorldImporters\".\"Application\".\"People\".\"UserPreferences\" AS \"UserPreferences\", \"WideWorldImporters\".\"Application\".\"People\".\"OtherLanguages\" AS \"OtherLanguages\", \"WideWorldImporters\".\"Application\".\"People\".\"PhoneNumber\" AS \"PhoneNumber\", 'Person' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Application\".\"People\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "People", + "schema": "WideWorldImporters.Application", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "personId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "IsSystemUser", + "alias": "IsSystemUser", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isSystemUser", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "FaxNumber", + "alias": "FaxNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "faxNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "EmailAddress", + "alias": "EmailAddress", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "emailAddress", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "IsPermittedToLogon", + "alias": "IsPermittedToLogon", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isPermittedToLogon", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "ValidTo", + "alias": "ValidTo", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validTo", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PreferredName", + "alias": "PreferredName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "preferredName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "LogonName", + "alias": "LogonName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "logonName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "SearchName", + "alias": "SearchName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "searchName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "IsSalesperson", + "alias": "IsSalesperson", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isSalesperson", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "IsEmployee", + "alias": "IsEmployee", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isEmployee", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "FullName", + "alias": "FullName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "fullName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "ValidFrom", + "alias": "ValidFrom", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validFrom", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "CustomFields", + "alias": "CustomFields", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "customFields", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "IsExternalLogonProvider", + "alias": "IsExternalLogonProvider", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isExternalLogonProvider", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "UserPreferences", + "alias": "UserPreferences", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "userPreferences", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "OtherLanguages", + "alias": "OtherLanguages", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "otherLanguages", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PhoneNumber", + "alias": "PhoneNumber", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "phoneNumber", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "Person", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "NODE_WideWorldImporters.Warehouse.StockItems_06706002-30c6-4c8d-821a-a7617ca5a24d", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Node", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"StockItemID\" AS \"StockItemID\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"StockItemID\" AS \"StockItemID\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"Size\" AS \"Size\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"Barcode\" AS \"Barcode\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"StockItemName\" AS \"StockItemName\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"InternalComments\" AS \"InternalComments\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"SearchDetails\" AS \"SearchDetails\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"RecommendedRetailPrice\" AS \"RecommendedRetailPrice\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"CustomFields\" AS \"CustomFields\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"QuantityPerOuter\" AS \"QuantityPerOuter\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"UnitPrice\" AS \"UnitPrice\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"TaxRate\" AS \"TaxRate\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"Tags\" AS \"Tags\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"ValidTo\" AS \"ValidTo\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"IsChillerStock\" AS \"IsChillerStock\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"MarketingComments\" AS \"MarketingComments\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"ValidFrom\" AS \"ValidFrom\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"TypicalWeightPerUnit\" AS \"TypicalWeightPerUnit\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"Brand\" AS \"Brand\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"LeadTimeDays\" AS \"LeadTimeDays\", 'StockItem' AS \"_NODE_LABEL_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItems\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "Id", + "name": "", + "id-space": "wideworldimporters.warehouse.stockitems" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "stockItemId", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "Size", + "alias": "Size", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "size", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "Barcode", + "alias": "Barcode", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "barcode", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemName", + "alias": "StockItemName", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "stockItemName", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "InternalComments", + "alias": "InternalComments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "internalComments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "SearchDetails", + "alias": "SearchDetails", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "searchDetails", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "RecommendedRetailPrice", + "alias": "RecommendedRetailPrice", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "recommendedRetailPrice", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "CustomFields", + "alias": "CustomFields", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "customFields", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "QuantityPerOuter", + "alias": "QuantityPerOuter", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "quantityPerOuter", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "UnitPrice", + "alias": "UnitPrice", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "unitPrice", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "TaxRate", + "alias": "TaxRate", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "taxRate", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "Tags", + "alias": "Tags", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "tags", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "ValidTo", + "alias": "ValidTo", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validTo", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "IsChillerStock", + "alias": "IsChillerStock", + "sql-data-type": "BIT", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "isChillerStock", + "neo4j-data-type": "Byte", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "MarketingComments", + "alias": "MarketingComments", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "marketingComments", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "ValidFrom", + "alias": "ValidFrom", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "validFrom", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "TypicalWeightPerUnit", + "alias": "TypicalWeightPerUnit", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "typicalWeightPerUnit", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "Brand", + "alias": "Brand", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "brand", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "LeadTimeDays", + "alias": "LeadTimeDays", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "leadTimeDays", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItem", + "alias": "_NODE_LABEL_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Label" + } + } + ] + }, + { + "name": "REL_People_f09eb9a9-3e8f-43ba-9414-6d4913640dae", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockItemStockGroupID\" AS \"StockItemStockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemStockGroupID", + "alias": "StockItemStockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.warehouse.stockitemstockgroups" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_7fc9a331-e816-4d26-9b0a-ed611f981819", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"SalespersonPersonID\" AS \"SalespersonPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "SalespersonPersonID", + "alias": "SalespersonPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_d44881f0-f1e9-43f4-8042-37f3bce268b3", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerID\" AS \"CustomerID\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Customers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Orders_81195165-c429-4617-b164-f72b2de24357", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"BackorderOrderID\" AS \"BackorderOrderID\", 'ORDERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "BackorderOrderID", + "alias": "BackorderOrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "ORDERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_e8e86525-52eb-4f09-9735-04b793d04bed", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"ContactPersonID\" AS \"ContactPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "ContactPersonID", + "alias": "ContactPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_2faad973-f9fe-4f07-bc20-7b8ca5c9c177", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"StockGroupID\" AS \"StockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockGroups\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockGroups\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroupID", + "alias": "StockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.warehouse.stockgroups" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_9ece9be9-667d-43d3-b310-19c72a665f25", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"CustomerTransactionID\" AS \"CustomerTransactionID\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CustomerTransactionID", + "alias": "CustomerTransactionID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.customertransactions" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_9ea82b60-d46a-4a05-bec0-602b82deaecc", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierID\" AS \"SupplierID\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Purchasing\".\"Suppliers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.purchasing.suppliers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_1fefb4fa-624b-4673-9d62-b5d9a3ef8ee0", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierID\" AS \"SupplierID\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"PrimaryContactPersonID\" AS \"PrimaryContactPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Purchasing\".\"Suppliers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.purchasing.suppliers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PrimaryContactPersonID", + "alias": "PrimaryContactPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_StockGroups_64ed4aef-d688-4475-8c14-faeae0e0992a", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockItemStockGroupID\" AS \"StockItemStockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockGroupID\" AS \"StockGroupID\", 'STOCK_GROUPS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemStockGroupID", + "alias": "StockItemStockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.warehouse.stockitemstockgroups" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroupID", + "alias": "StockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockGroupID", + "alias": "StockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.warehouse.stockgroups" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "STOCK_GROUPS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_StockItems_dbf10308-bb4f-4ac6-ab62-c036ad23562e", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"OrderLineID\" AS \"OrderLineID\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"StockItemID\" AS \"StockItemID\", 'STOCK_ITEMS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"OrderLines\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderLineID", + "alias": "OrderLineID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orderlines" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.warehouse.stockitems" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "STOCK_ITEMS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Orders_df1c6805-c630-403f-943f-4c49b67e8a02", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"OrderLineID\" AS \"OrderLineID\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"OrderID\" AS \"OrderID\", 'ORDERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"OrderLines\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderLineID", + "alias": "OrderLineID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orderlines" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "ORDERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_7389a136-5880-48a7-9892-2e1bb1780bed", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerID\" AS \"CustomerID\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"AlternateContactPersonID\" AS \"AlternateContactPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Customers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "AlternateContactPersonID", + "alias": "AlternateContactPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Customers_c9844227-88f2-417c-81db-36937e4e23d1", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"CustomerTransactionID\" AS \"CustomerTransactionID\", \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\".\"CustomerID\" AS \"CustomerID\", 'CUSTOMERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"CustomerTransactions\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CustomerTransactionID", + "alias": "CustomerTransactionID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.customertransactions" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "CustomerTransactions", + "schema": "WideWorldImporters.Sales", + "name": "CUSTOMERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_e95cbb51-f44b-4fad-b7c0-1b271859918b", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"PurchaseOrderID\" AS \"PurchaseOrderID\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"ContactPersonID\" AS \"ContactPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PurchaseOrderID", + "alias": "PurchaseOrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.purchasing.purchaseorders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "ContactPersonID", + "alias": "ContactPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_cd058df8-86a0-4aa8-906f-92db0c8dd669", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_StockItems_a9362f12-eaf2-4cc2-afff-5a3c908188ae", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockItemStockGroupID\" AS \"StockItemStockGroupID\", \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\".\"StockItemID\" AS \"StockItemID\", 'STOCK_ITEMS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItemStockGroups\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemStockGroupID", + "alias": "StockItemStockGroupID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.warehouse.stockitemstockgroups" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.warehouse.stockitems" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItemStockGroups", + "schema": "WideWorldImporters.Warehouse", + "name": "STOCK_ITEMS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Customers_9fb45881-4a6a-4011-8ece-551af4971f4e", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"CustomerID\" AS \"CustomerID\", 'CUSTOMERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "CUSTOMERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_6dd11ddd-4ea1-4a42-af9a-1df8c049156b", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"OrderLineID\" AS \"OrderLineID\", \"WideWorldImporters\".\"Sales\".\"OrderLines\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"OrderLines\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "OrderLineID", + "alias": "OrderLineID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orderlines" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "OrderLines", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Customers_fb727152-3be7-44ec-b503-377df9206f99", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerID\" AS \"CustomerID\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"BillToCustomerID\" AS \"BillToCustomerID\", 'CUSTOMERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Customers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "BillToCustomerID", + "alias": "BillToCustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CUSTOMERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_1bc2a7dd-4964-4d09-ab32-00c62056bd8a", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"PurchaseOrderID\" AS \"PurchaseOrderID\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PurchaseOrderID", + "alias": "PurchaseOrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.purchasing.purchaseorders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_6925960b-9913-433a-b216-95e45d08015f", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"SupplierID\" AS \"SupplierID\", \"WideWorldImporters\".\"Purchasing\".\"Suppliers\".\"AlternateContactPersonID\" AS \"AlternateContactPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Purchasing\".\"Suppliers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.purchasing.suppliers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "AlternateContactPersonID", + "alias": "AlternateContactPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_0a9ba6ad-2c22-4733-b401-bc94386e92e0", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Orders\".\"OrderID\" AS \"OrderID\", \"WideWorldImporters\".\"Sales\".\"Orders\".\"PickedByPersonID\" AS \"PickedByPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Orders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "OrderID", + "alias": "OrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.orders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "PickedByPersonID", + "alias": "PickedByPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Orders", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Suppliers_09a502b6-8ccf-4969-83f8-dbc78889ed7b", + "schema": "WideWorldImporters.Purchasing", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"PurchaseOrderID\" AS \"PurchaseOrderID\", \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\".\"SupplierID\" AS \"SupplierID\", 'SUPPLIERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Purchasing\".\"PurchaseOrders\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "PurchaseOrderID", + "alias": "PurchaseOrderID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.purchasing.purchaseorders" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.purchasing.suppliers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "PurchaseOrders", + "schema": "WideWorldImporters.Purchasing", + "name": "SUPPLIERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_Suppliers_0f3254fd-1605-4b8b-a59f-143604e25079", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"StockItemID\" AS \"StockItemID\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"SupplierID\" AS \"SupplierID\", 'SUPPLIERS' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItems\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.warehouse.stockitems" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "Suppliers", + "schema": "WideWorldImporters.Purchasing", + "name": "SupplierID", + "alias": "SupplierID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.purchasing.suppliers" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "SUPPLIERS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_fbf49009-2b57-4273-ae4c-69b699430945", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"StockItemID\" AS \"StockItemID\", \"WideWorldImporters\".\"Warehouse\".\"StockItems\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItems\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.warehouse.stockitems" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_9742a15a-d3d8-4a0a-a28e-726f0be78d35", + "schema": "WideWorldImporters.Application", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Application\".\"People\".\"PersonID\" AS \"PersonID\", \"WideWorldImporters\".\"Application\".\"People\".\"LastEditedBy\" AS \"LastEditedBy\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Application\".\"People\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "People", + "schema": "WideWorldImporters.Application", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "People", + "schema": "WideWorldImporters.Application", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_People_a12d8bff-7490-4acc-92ba-e88b1583cd71", + "schema": "WideWorldImporters.Sales", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Sales\".\"Customers\".\"CustomerID\" AS \"CustomerID\", \"WideWorldImporters\".\"Sales\".\"Customers\".\"PrimaryContactPersonID\" AS \"PrimaryContactPersonID\", 'PEOPLE' AS \"_RELATIONSHIP_TYPE_\" FROM \"WideWorldImporters\".\"Sales\".\"Customers\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "PrimaryKey", + "columns": [ + { + "type": "SimpleColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "CustomerID", + "alias": "CustomerID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.sales.customers" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PrimaryContactPersonID", + "alias": "PrimaryContactPersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "Customers", + "schema": "WideWorldImporters.Sales", + "name": "PEOPLE", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + } + ] + }, + { + "name": "REL_STOCKITEMHOLDINGS_5a8924cf-4e07-4c24-8727-c3a44c7ffe00", + "schema": "WideWorldImporters.Warehouse", + "graph-object-type": "Relationship", + "sql": "SELECT \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"LastEditedBy\" AS \"LastEditedBy\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"StockItemID\" AS \"StockItemID\", 'STOCK_ITEM_HOLDINGS' AS \"_RELATIONSHIP_TYPE_\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"QuantityOnHand\" AS \"QuantityOnHand\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"LastEditedWhen\" AS \"LastEditedWhen\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"LastStocktakeQuantity\" AS \"LastStocktakeQuantity\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"BinLocation\" AS \"BinLocation\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"LastCostPrice\" AS \"LastCostPrice\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"ReorderLevel\" AS \"ReorderLevel\", \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\".\"TargetStockLevel\" AS \"TargetStockLevel\" FROM \"WideWorldImporters\".\"Warehouse\".\"StockItemHoldings\"", + "mappings": [ + { + "column": { + "type": "CompositeColumn", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "LastEditedBy", + "alias": "LastEditedBy", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "People", + "schema": "WideWorldImporters.Application", + "name": "PersonID", + "alias": "PersonID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "StartId", + "id-space": "wideworldimporters.application.people" + } + }, + { + "column": { + "type": "CompositeColumn", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "role": "ForeignKey", + "columns": [ + { + "type": "JoinColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue", + "referenced-column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItems", + "schema": "WideWorldImporters.Warehouse", + "name": "StockItemID", + "alias": "StockItemID", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + } + } + ] + }, + "field": { + "type": "EndId", + "id-space": "wideworldimporters.warehouse.stockitems" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Literal", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "STOCK_ITEM_HOLDINGS", + "alias": "_RELATIONSHIP_TYPE_", + "sql-data-type": "VARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "RelationshipType" + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "QuantityOnHand", + "alias": "QuantityOnHand", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "quantityOnHand", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "LastEditedWhen", + "alias": "LastEditedWhen", + "sql-data-type": "TIMESTAMP", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastEditedWhen", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "LastStocktakeQuantity", + "alias": "LastStocktakeQuantity", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastStocktakeQuantity", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "BinLocation", + "alias": "BinLocation", + "sql-data-type": "NVARCHAR", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "binLocation", + "neo4j-data-type": "String", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "LastCostPrice", + "alias": "LastCostPrice", + "sql-data-type": "DECIMAL", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "lastCostPrice", + "neo4j-data-type": "Double", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "ReorderLevel", + "alias": "ReorderLevel", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "reorderLevel", + "neo4j-data-type": "Long", + "is-array": false + } + }, + { + "column": { + "type": "SimpleColumn", + "role": "Data", + "table": "StockItemHoldings", + "schema": "WideWorldImporters.Warehouse", + "name": "TargetStockLevel", + "alias": "TargetStockLevel", + "sql-data-type": "INTEGER", + "column-value-selection-strategy": "SelectColumnValue" + }, + "field": { + "type": "Data", + "name": "targetStockLevel", + "neo4j-data-type": "Long", + "is-array": false + } + } + ] + } +] \ No newline at end of file diff --git a/services/orchestrator/src/context/snippets/DapperQueryEntrypoint.cs b/services/orchestrator/src/context/snippets/DapperQueryEntrypoint.cs new file mode 100644 index 0000000..fa95bd7 --- /dev/null +++ b/services/orchestrator/src/context/snippets/DapperQueryEntrypoint.cs @@ -0,0 +1,380 @@ +using System; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using System.Collections.Generic; +using Dapper; +using Microsoft.Data.SqlClient; + +namespace dapper_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +public class Customer +{ + public required int CustomerID { get; set; } + public required string CustomerName { get; set; } + public required DateTime AccountOpenedDate { get; set; } + public decimal? CreditLimit { get; set; } + public List CustomerTransactions { get; set; } = []; +} + +public class CustomerTransaction +{ + public int CustomerTransactionID { get; set; } + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +public class Order +{ + public int OrderID { get; set; } + public int CustomerID { get; set; } + public Customer Customer { get; set; } = null!; + public List OrderLines { get; set; } = []; +} + +public class OrderLine +{ + public int OrderLineID { get; set; } + public int OrderID { get; set; } + public required string Description { get; set; } + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +// --- Queries --- + +public static class Query1 +{ + public static IEnumerable Query(SqlConnection conn) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + string sql = @"SELECT * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To"; + return conn.Query(sql, new { From = from, To = to }); + } + + public static object Harness(SqlConnection conn) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + var sql = @" + SELECT TOP 1 * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To ORDER BY OrderLineID ASC; + SELECT TOP 1 * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To ORDER BY OrderLineID DESC; + SELECT COUNT(*) FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To;"; + using var multi = conn.QueryMultiple(sql, new { From = from, To = to }); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +public static class Query2 +{ + static Order Query2mapRow(Order o, Customer c, CustomerTransaction t, OrderLine ol) + { + o.Customer = c; + o.OrderLines.Add(ol); + if (c != null) { + if (t != null) + { + c.CustomerTransactions.Add(t); + } + } + return o; + } + + public static IEnumerable Query(SqlConnection conn) + { + string sql = @" + SELECT o.*, c.*, t.*, ol.* + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.CustomerID = 1"; + var duplicates = conn.Query( + sql, Query2mapRow, splitOn: "CustomerID,CustomerTransactionID,OrderLineID"); + var result = duplicates.GroupBy(o => o.OrderID).Select(g => { + var order = g.First(); + order.OrderLines = g.SelectMany(o => o.OrderLines).Where(ol => ol != null).GroupBy(ol => ol.OrderLineID).Select(g2 => g2.First()).ToList(); + order.Customer?.CustomerTransactions = g.SelectMany(o => o.Customer.CustomerTransactions).Where(t => t != null).GroupBy(t => t.CustomerTransactionID).Select(g2 => g2.First()).ToList(); + return order; + }); + return result; + } + + public static object Harness(SqlConnection conn) + { + string sql = @" + SELECT TOP 1 o.*, c.*, t.*, ol.* + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.CustomerID = 1 + ORDER BY o.OrderID ASC; + SELECT TOP 1 o.*, c.*, t.*, ol.* + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.CustomerID = 1 + ORDER BY o.OrderID DESC; + SELECT COUNT(*) + FROM Sales.Orders o + LEFT JOIN Sales.Customers c ON o.CustomerID = c.CustomerID + LEFT JOIN Sales.CustomerTransactions t ON c.CustomerID = t.CustomerID + LEFT JOIN Sales.OrderLines ol ON o.OrderID = ol.OrderID + WHERE o.OrderID = 1;"; + using var multi = conn.QueryMultiple(sql); + var firstSample = multi.Read(Query2mapRow, splitOn: "CustomerID,CustomerTransactionID,OrderLineID").FirstOrDefault(); + var lastSample = multi.Read(Query2mapRow, splitOn: "CustomerID,CustomerTransactionID,OrderLineID").FirstOrDefault(); + var rowCount = multi.ReadSingle(); + return new { firstSample, lastSample, count = rowCount }; + } +} + +public static class Query3 +{ + public static IEnumerable<(decimal TaxRate, int Count)> Query(SqlConnection conn) + { + string sql = @"SELECT TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate ORDER BY Count DESC"; + return conn.Query<(decimal TaxRate, int Count)>(sql); + } + + public static object Harness(SqlConnection conn) + { + var sql = @"SELECT TOP 1 TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate ORDER BY Count DESC; + SELECT TOP 1 TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate ORDER BY Count ASC; + SELECT COUNT(*) FROM (SELECT TaxRate, COUNT(*) as Count FROM Sales.OrderLines GROUP BY TaxRate) AS SubQuery;"; + using var multi = conn.QueryMultiple(sql); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +public static class Query4 +{ + public static IEnumerable Query(SqlConnection conn) + { + string sql = @"SELECT TOP 50 * FROM Sales.OrderLines ORDER BY Quantity DESC"; + return conn.Query(sql); + } + + public static object Harness(SqlConnection conn) + { + var sql = @"SELECT TOP 1 * FROM Sales.OrderLines ORDER BY Quantity DESC; + SELECT TOP 1 * FROM Sales.OrderLines ORDER BY Quantity ASC; + SELECT COUNT(*) FROM (SELECT TOP 50 * FROM Sales.OrderLines ORDER BY Quantity DESC) AS SubQuery;"; + using var multi = conn.QueryMultiple(sql); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +public static class Query5 +{ + public static IEnumerable<(int OrderLineID, int Quantity)> Query(SqlConnection conn) + { + string sql = @"SELECT OrderLineID, Quantity FROM Sales.OrderLines"; + return conn.Query<(int OrderLineID, int Quantity)>(sql); + } + + public static object Harness(SqlConnection conn) + { + var sql = @" + SELECT TOP 1 OrderLineID, Quantity FROM Sales.OrderLines ORDER BY OrderLineID ASC; + SELECT TOP 1 OrderLineID, Quantity FROM Sales.OrderLines ORDER BY OrderLineID DESC; + SELECT COUNT(*) FROM Sales.OrderLines;"; + using var multi = conn.QueryMultiple(sql); + return new { firstSample = multi.ReadSingle(), lastSample = multi.ReadSingle(), count = multi.ReadSingle() }; + } +} + +// --- Query Entrypoint --- + +public static class DapperQueryEntrypoint +{ + + public static void ValidateEntity(SqlConnection conn, string schema, string table) + { + Console.WriteLine($"Validating Dapper entity: {typeof(T).Name}"); + var sql = $"SELECT TOP 1 * FROM {schema}.{table}"; + Console.WriteLine(CustomJsonSerializer.Serialize(conn.QueryFirstOrDefault(sql))); + Console.WriteLine($"Successfully validated Dapper entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + string connectionString = args.ElementAtOrDefault(0) + ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + using var conn = new SqlConnection(connectionString); + conn.Open(); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + ValidateEntity(conn, "Sales", "Customers"); + ValidateEntity(conn, "Sales", "CustomerTransactions"); + ValidateEntity(conn, "Sales", "Orders"); + ValidateEntity(conn, "Sales", "OrderLines"); + + // Now create and execute the queries and capture results + var results = new Dictionary(); + var harnesses = new Func[] { + () => Query1.Harness(conn), + () => Query2.Harness(conn), + () => Query3.Harness(conn), + () => Query4.Harness(conn), + () => Query5.Harness(conn) + }; + + for (int i = 0; i < harnesses.Length; i++) { + var qid = i+1; + Console.WriteLine($"Running Query {qid}..."); + try { + results[$"query{qid}"] = harnesses[i](); + Console.WriteLine($"Successfully ran Query {qid}"); + } catch (Exception ex) { + results[$"query{qid}"] = new { error = ex.Message }; + Console.Error.WriteLine($"Error occurred while running Query{qid}: {ex}"); + } + } + + var resultsPath = Environment.GetEnvironmentVariable("DAPPER_RESULTS_PATH"); + if (!string.IsNullOrEmpty(resultsPath)) + { + System.IO.File.WriteAllText($"{resultsPath}/dapper_results_{DateTime.Now:yyyyMMdd_HHmmss}.json", CustomJsonSerializer.Serialize(results)); + } + else + { + Console.WriteLine(CustomJsonSerializer.Serialize(results)); + } + } +} diff --git a/services/orchestrator/src/context/snippets/DapperSchemaValidationEntrypoint.cs b/services/orchestrator/src/context/snippets/DapperSchemaValidationEntrypoint.cs new file mode 100644 index 0000000..6b39422 --- /dev/null +++ b/services/orchestrator/src/context/snippets/DapperSchemaValidationEntrypoint.cs @@ -0,0 +1,215 @@ +using System; +using System.Linq; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Collections.Generic; +using Dapper; +using Microsoft.Data.SqlClient; +using System.Text.Json.Serialization.Metadata; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Globalization; +using System.Text.Encodings.Web; + +namespace dapper_sandbox; + +// --- Schema and Related Settings --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +[Table("Customers", Schema = "Sales")] +public class Customer +{ + [Key] + public int CustomerID { get; set; } + public string CustomerName { get; set; } = string.Empty; + public DateTime AccountOpenedDate { get; set; } + public decimal? CreditLimit { get; set; } +} + +[Table("CustomerTransactions", Schema = "Sales")] +public class CustomerTransaction +{ + [Key] + public int CustomerTransactionID { get; set; } + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +[Table("Orders", Schema = "Sales")] +public class Order +{ + [Key] + public int OrderID { get; set; } + public int CustomerID { get; set; } +} + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{ + [Key] + public int OrderLineID { get; set; } + public int OrderID { get; set; } + public int StockItemID { get; set; } + public string Description { get; set; } = string.Empty; + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +// --- Schema Validation Entrypoint --- + +public static class DapperSchemaValidationEntrypoint +{ + public static void ValidateEntity(SqlConnection conn, string schema, string table) + { + Console.WriteLine($"Validating Dapper entity: {typeof(T).Name}"); + var sql = $"SELECT TOP 1 * FROM {schema}.{table}"; + Console.WriteLine(CustomJsonSerializer.Serialize(conn.QueryFirstOrDefault(sql))); + Console.WriteLine($"Successfully validated Dapper entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + var connectionString = args.ElementAtOrDefault(0) ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + using var conn = new SqlConnection(connectionString); + conn.Open(); + + var queries = new Action[] { + () => ValidateEntity(conn, "Sales", "Customers"), + () => ValidateEntity(conn, "Sales", "CustomerTransactions"), + () => ValidateEntity(conn, "Sales", "Orders"), + () => ValidateEntity(conn, "Sales", "OrderLines") + }; + + foreach (var query in queries) { + try { + query(); + } catch (Exception ex) { + Console.Error.WriteLine($"Error occurred while validating entity: {ex}"); + } + } + } +} diff --git a/services/orchestrator/src/context/snippets/EFCoreQueryEntrypoint.cs b/services/orchestrator/src/context/snippets/EFCoreQueryEntrypoint.cs new file mode 100644 index 0000000..311331c --- /dev/null +++ b/services/orchestrator/src/context/snippets/EFCoreQueryEntrypoint.cs @@ -0,0 +1,305 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace efcore_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +[Table("Customers", Schema = "Sales")] +public class Customer +{ + [Key] + [JsonPropertyName("customerId")] + public required int CustomerID { get; set; } + [MaxLength(200)] + public required string CustomerName { get; set; } + [Column(TypeName="datetime2")] + [Precision(7)] + public required DateTime AccountOpenedDate { get; set; } + [Column(TypeName="decimal")] + [Precision(18, 2)] + public decimal? CreditLimit { get; set; } + + public List CustomerTransactions { get; set; } = []; +} + +[Table("CustomerTransactions", Schema = "Sales")] +public class CustomerTransaction +{ + [Key] + [JsonPropertyName("customerTransactionId")] + public int CustomerTransactionID { get; set; } + [ForeignKey(nameof(Customer))] + [JsonPropertyName("customerId")] + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +[Table("Orders", Schema = "Sales")] +public class Order +{ + [Key] + [JsonPropertyName("orderId")] + public int OrderID { get; set; } + [ForeignKey(nameof(Customer))] + [JsonPropertyName("customerId")] + public int CustomerID { get; set; } + + public Customer Customer { get; set; } = null!; + public List OrderLines { get; set; } = []; +} + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{ + [Key] + [JsonPropertyName("orderLineId")] + public int OrderLineID { get; set; } + [ForeignKey(nameof(Order))] + [JsonPropertyName("orderId")] + public int OrderID { get; set; } + [JsonPropertyName("stockItemId")] + public int StockItemID { get; set; } + public required string Description { get; set; } + [JsonPropertyName("packageTypeId")] + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +public class SandboxDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Customers => Set(); + public DbSet Orders => Set(); + public DbSet CustomerTransactions => Set(); + public DbSet OrderLines => Set(); +} + +// --- Query Entrypoint --- + +public static class EFCoreQueryEntrypoint +{ + public static IQueryable Query1(SandboxDbContext ctx) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + return ctx.OrderLines.Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to); + } + + public static IQueryable Query2(SandboxDbContext ctx) + { + return ctx.Orders + .Include(o => o.Customer) + .ThenInclude(c => c.CustomerTransactions) + .Include(o => o.OrderLines) + .AsSplitQuery() + .Where(c => c.CustomerID == 1); + } + + public static IQueryable Query3(SandboxDbContext ctx) + { + return ctx.OrderLines.GroupBy(ol => ol.TaxRate).Select(g => new { TaxRate = g.Key, Count = g.Count() }).OrderByDescending(x => x.Count); + } + + public static IQueryable Query4(SandboxDbContext ctx) + { + return ctx.OrderLines.OrderByDescending(ol => ol.Quantity).Take(50); + } + + public static IQueryable Query5(SandboxDbContext ctx) + { + return ctx.OrderLines.Select(ol => new { ol.OrderLineID, ol.Quantity }); + } + + private static object RunQuery(Func> q, Func? orderBySelector = null) + { + var query = q(); + var count = query.Count(); + return new { sqlString = query.ToQueryString(), count, + firstSample = count > 0 ? (orderBySelector != null ? q().OrderBy(orderBySelector).FirstOrDefault() : query.FirstOrDefault()) : default, + lastSample = count > 1 ? (orderBySelector != null ? q().OrderByDescending(orderBySelector).FirstOrDefault() : query.LastOrDefault()) : default }; + } + + public static void ValidateEntity(SandboxDbContext ctx) where T : class + { + Console.WriteLine($"Validating EF Core entity: {typeof(T).Name}"); + Console.WriteLine(CustomJsonSerializer.Serialize(ctx.Set().FirstOrDefault())); + Console.WriteLine($"Successfully validated EF Core entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + using var context = new SandboxDbContext( + new DbContextOptionsBuilder() + .UseSqlServer( + args.ElementAtOrDefault(0) ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + ) + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .EnableSensitiveDataLogging() + .LogTo(Console.WriteLine, [DbLoggerCategory.Database.Command.Name], minimumLevel: LogLevel.Information, options: DbContextLoggerOptions.SingleLine).Options + ); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + ValidateEntity(context); + ValidateEntity(context); + ValidateEntity(context); + ValidateEntity(context); + + // Now create and execute the queries and capture results + var results = new Dictionary(); + var harnesses = new Func[] { + () => RunQuery(() => Query1(context), ol => ol.OrderLineID), + () => RunQuery(() => Query2(context), o => o.OrderID), + () => RunQuery(() => Query3(context), x => x.TaxRate), // sorting by TaxRate, since it's unique in a groupBy result + () => RunQuery(() => Query4(context)), // sorting by non-unique column with limiting (.Take) is present; top results may not be consistent accross DBMSs; sorting by unique-column is not possible + () => RunQuery(() => Query5(context), ol => ol.OrderLineID) + }; + + for (int i = 0; i < harnesses.Length; i++) { + var qid = i+1; + Console.WriteLine($"Running Query {qid}..."); + try { + results[$"query{qid}"] = harnesses[i](); + } catch (Exception ex) { + results[$"query{qid}"] = new { error = ex.Message }; + Console.Error.WriteLine($"Error occurred while running Query{qid}: {ex}"); + } + } + File.WriteAllText($"{Environment.GetEnvironmentVariable("EFCORE_RESULTS_PATH")}/efcore_results_{DateTime.Now:yyyyMMdd_HHmmss}.json", CustomJsonSerializer.Serialize(results)); + } +} diff --git a/services/orchestrator/src/context/snippets/EFCoreSchemaValidationEntrypoint.cs b/services/orchestrator/src/context/snippets/EFCoreSchemaValidationEntrypoint.cs new file mode 100644 index 0000000..f377d28 --- /dev/null +++ b/services/orchestrator/src/context/snippets/EFCoreSchemaValidationEntrypoint.cs @@ -0,0 +1,238 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; + +namespace efcore_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +[Table("Customers", Schema = "Sales")] +public class Customer +{ + [Key] + public required int CustomerID { get; set; } + [MaxLength(200)] + public required string CustomerName { get; set; } + [Column(TypeName="datetime2")] + [Precision(7)] + public required DateTime AccountOpenedDate { get; set; } + [Column(TypeName="decimal")] + [Precision(18, 2)] + public decimal? CreditLimit { get; set; } + public List CustomerTransactions { get; set; } = []; +} + +[Table("CustomerTransactions", Schema = "Sales")] +public class CustomerTransaction +{ + [Key] + public int CustomerTransactionID { get; set; } + [ForeignKey(nameof(Customer))] + public int CustomerID { get; set; } + public DateTime TransactionDate { get; set; } + public decimal TransactionAmount { get; set; } +} + +[Table("Orders", Schema = "Sales")] +public class Order +{ + [Key] + public int OrderID { get; set; } + [ForeignKey(nameof(Customer))] + public int CustomerID { get; set; } + public Customer Customer { get; set; } = null!; + public List OrderLines { get; set; } = []; +} + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{ + [Key] + public int OrderLineID { get; set; } + [ForeignKey(nameof(Order))] + public int OrderID { get; set; } + public int StockItemID { get; set; } + public required string Description { get; set; } + public int PackageTypeID { get; set; } + public int Quantity { get; set; } + public decimal? UnitPrice { get; set; } + public decimal TaxRate { get; set; } + public int PickedQuantity { get; set; } + public DateTime? PickingCompletedWhen { get; set; } + public int LastEditedBy { get; set; } + public DateTime LastEditedWhen { get; set; } +} + +public class SandboxDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Customers => Set(); + public DbSet Orders => Set(); + public DbSet CustomerTransactions => Set(); + public DbSet OrderLines => Set(); +} + +public static class EFCoreSchemaValidationEntrypoint +{ + public static void ValidateEntity(SandboxDbContext ctx) where T : class + { + Console.WriteLine($"Validating EF Core entity: {typeof(T).Name}"); + Console.WriteLine(CustomJsonSerializer.Serialize(ctx.Set().FirstOrDefault())); + Console.WriteLine($"Successfully validated EF Core entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + using var context = new SandboxDbContext( + new DbContextOptionsBuilder() + .UseSqlServer( + args.ElementAtOrDefault(0) ?? Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True" + ) + .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking) + .EnableSensitiveDataLogging() + .LogTo(Console.WriteLine, [DbLoggerCategory.Database.Command.Name], minimumLevel: LogLevel.Information, options: DbContextLoggerOptions.SingleLine).Options + ); + + var queries = new Action[] { + () => ValidateEntity(context), + () => ValidateEntity(context), + () => ValidateEntity(context), + () => ValidateEntity(context) + }; + + foreach (var query in queries) { + try { + query(); + } catch (Exception ex) { + Console.Error.WriteLine($"Error occurred while validating entity: {ex}"); + } + } + } +} \ No newline at end of file diff --git a/services/orchestrator/src/context/snippets/MongoQueryEntrypoint.java b/services/orchestrator/src/context/snippets/MongoQueryEntrypoint.java new file mode 100644 index 0000000..876be6d --- /dev/null +++ b/services/orchestrator/src/context/snippets/MongoQueryEntrypoint.java @@ -0,0 +1,500 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; +import java.util.function.*; + +import org.slf4j.*; +import org.springframework.data.annotation.*; +import org.springframework.data.domain.*; +import org.springframework.data.mongodb.*; +import org.springframework.data.mongodb.core.*; +import org.springframework.data.mongodb.core.aggregation.*; +import org.springframework.data.mongodb.core.convert.*; +import org.springframework.data.mongodb.core.mapping.*; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.query.*; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.*; +import com.mongodb.client.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_MONGO_URI = "mongodb://uom_readonly:uom_readonly@mongodb:27017/uom"; + private static final String DEFAULT_MONGO_DATABASE = "uom"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.ALWAYS)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.mongodb.core.MongoTemplate"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String defaultMongoUri() { + return System.getenv("MONGODB_URI") != null ? System.getenv("MONGODB_URI") : DEFAULT_MONGO_URI; + } + + static String defaultMongoDatabase() { + return System.getenv("MONGODB_DATABASE") != null ? System.getenv("MONGODB_DATABASE") : DEFAULT_MONGO_DATABASE; + } +} + +// --- Schema and Related Settings --- + +/** + * Order document with embedded Customer and CustomerTransactions. + * + * TRANSLATED FROM: C# EFCore Customer, CustomerTransaction entities + * ARCHITECTURAL SHIFT: Denormalized - Customers no longer a root collection. + * Instead, Customer and its transactions are embedded within Order. + */ +@Document(collection = "orders") +@JsonIgnoreProperties({ "id" }) +class Order { + + @Id + private String id; + + @Field("orderId") + private Integer orderId; + + @Field("customerId") + private Integer customerId; + + // Embedded Customer document (denormalized from Sales.Customers table) + private Customer customer; + + @ReadOnlyProperty + @DocumentReference(lazy = true, lookup = "{ 'orderId': ?#{#self.orderId} }", sort = "{ 'orderLineId': 1 }") + private List orderLines; + + // Constructors + public Order() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +/** + * Embedded Customer document within Order. + * Represents the denormalized Sales.Customers table data. + * + * TRANSLATED FROM: C# Customer entity (no longer a @Document root) + * NOTE: No @Document annotation - this is a value object embedded in Order + */ +class Customer { + + private Integer customerId; + private String customerName; + private LocalDate accountOpenedDate; + private BigDecimal creditLimit; + + // Embedded CustomerTransactions array (denormalized from Sales.CustomerTransactions table) + private List customerTransactions = new ArrayList<>(); + + // Constructors + public Customer() { + } + + // Getters and Setters + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public BigDecimal getCreditLimit() { return creditLimit; } + public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +/** + * Embedded CustomerTransaction document within Customer.customerTransactions array. + * Represents the denormalized Sales.CustomerTransactions table data. + * + * TRANSLATED FROM: C# CustomerTransaction entity + * NOTE: No @Document annotation - this is a value object embedded in Customer + */ +class CustomerTransaction { + + private Integer customerTransactionId; + private Integer customerId; + private LocalDate transactionDate; + private BigDecimal transactionAmount; + + // Constructors + public CustomerTransaction() { + } + + // Getters and Setters + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public BigDecimal getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(BigDecimal transactionAmount) { this.transactionAmount = transactionAmount; } +} + +/** + * OrderLine document. + * Maps to the 'orderLines' collection in MongoDB. + * + * TRANSLATED FROM: C# OrderLine entity + */ +@Document(collection = "orderLines") +@JsonIgnoreProperties({ "id" }) +class OrderLine { + + @Id + private String id; + + @Field("orderLineId") + private Integer orderLineId; + + @Field("orderId") + private Integer orderId; + + @Field("stockItemId") + private Integer stockItemId; + + private String description; + + @Field("packageTypeId") + private Integer packageTypeId; + + private Integer quantity; + + private BigDecimal unitPrice; + + @Field("taxRate") + private BigDecimal taxRate; + + @Field("pickedQuantity") + private Integer pickedQuantity; + + @Field("pickingCompletedWhen") + private LocalDateTime pickingCompletedWhen; + + @Field("lastEditedBy") + private Integer lastEditedBy; + + @Field("lastEditedWhen") + private LocalDateTime lastEditedWhen; + + // Constructors + public OrderLine() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getStockItemId() { return stockItemId; } + public void setStockItemId(Integer stockItemId) { this.stockItemId = stockItemId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getPackageTypeId() { return packageTypeId; } + public void setPackageTypeId(Integer packageTypeId) { this.packageTypeId = packageTypeId; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public BigDecimal getUnitPrice() { return unitPrice; } + public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; } + public BigDecimal getTaxRate() { return taxRate; } + public void setTaxRate(BigDecimal taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public LocalDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(LocalDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public Integer getLastEditedBy() { return lastEditedBy; } + public void setLastEditedBy(Integer lastEditedBy) { this.lastEditedBy = lastEditedBy; } + public LocalDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(LocalDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class MongoTemplateFactory { + private MongoTemplateFactory() { + } + + static MongoTemplate create(String mongoUri, String mongoDatabase) { + MongoDatabaseFactory databaseFactory = new SimpleMongoClientDatabaseFactory(MongoClients.create(mongoUri), mongoDatabase); + MongoCustomConversions customConversions = MongoCustomConversions.create(configuration -> {}); + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); + mappingContext.afterPropertiesSet(); + + MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(databaseFactory), mappingContext); + converter.setCustomConversions(customConversions); + converter.setTypeMapper(new DefaultMongoTypeMapper(null)); + converter.afterPropertiesSet(); + + return new MongoTemplate(databaseFactory, converter); + } +} + +// --- Query Entrypoint --- + +record CountProjection(Long count) { +} + +interface Query5Projection { + Integer getOrderLineId(); + Integer getQuantity(); +} + +final class Query1 { + public static Query query() { + LocalDate from = LocalDate.of(2014, 12, 20); + LocalDate to = LocalDate.of(2014, 12, 31); + return new Query(Criteria.where("pickingCompletedWhen").gte(from).lte(to)); + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, OrderLine.class); + Object first = null; + if (count > 0) { + first = template.findOne(query().with(Sort.by(Sort.Direction.ASC, "orderLineId")).limit(1), OrderLine.class); + } + Object last = null; + if (count > 1) { + last = template.findOne(query().with(Sort.by(Sort.Direction.DESC, "orderLineId")).limit(1), OrderLine.class); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(OrderLine.class), "filter", q.getQueryObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query2 { + public static Query query() { + return new Query(Criteria.where("customerId").is(1)); + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, Order.class); + Object first = null; + if (count > 0) { + first = template.findOne(query().with(Sort.by(Sort.Direction.ASC, "orderId")).limit(1), Order.class); + } + Object last = null; + if (count > 1) { + last = template.findOne(query().with(Sort.by(Sort.Direction.DESC, "orderId")).limit(1), Order.class); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(Order.class), "filter", q.getQueryObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query3 { + public static TypedAggregation query() { + return Aggregation.newAggregation( + OrderLine.class, + Aggregation.group("taxRate").count().as("count"), + Aggregation.project("count").and("taxRate").previousOperation(), + Aggregation.sort(Sort.Direction.DESC, "count") + ); + } + + public static Map harness(MongoTemplate template) { + var baseAgg = query(); + + var countOps = new ArrayList<>(baseAgg.getPipeline().getOperations()); + countOps.add(Aggregation.count().as("count")); + var countAgg = Aggregation.newAggregation(OrderLine.class, countOps); + var countResult = template.aggregate(countAgg, OrderLine.class, CountProjection.class).getUniqueMappedResult(); + var count = countResult != null ? countResult.count() : 0L; + + Object first = null; + if (count > 0) { + var agg = Aggregation.newAggregation(query().getPipeline().add(Aggregation.sort(Sort.Direction.ASC, "taxRate")).add(Aggregation.limit(1)).getOperations()); + first = template.aggregate(agg, template.getCollectionName(OrderLine.class), Object.class).getUniqueMappedResult(); + } + Object last = null; + if (count > 1) { + var desc = Aggregation.newAggregation(query().getPipeline().add(Aggregation.sort(Sort.Direction.DESC, "taxRate")).add(Aggregation.limit(1)).getOperations()); + last = template.aggregate(desc, template.getCollectionName(OrderLine.class), Object.class).getUniqueMappedResult(); + } + return Map.of("mongoAggregation", Map.of("collection", template.getCollectionName(OrderLine.class), "pipeline", baseAgg.toString()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query4 { + public static Query query() { + return new Query().with(Sort.by(Sort.Direction.DESC, "quantity")).limit(50); + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, OrderLine.class); + + Object first = null; + if (count > 0) { + Query firstQ = q.with(Sort.by(Sort.Direction.ASC, "orderLineId")).limit(1); + first = template.findOne(firstQ, OrderLine.class); + } + Object last = null; + if (count > 1) { + Query lastQ = q.with(Sort.by(Sort.Direction.ASC, "orderLineId")).skip(count - 1).limit(1); + last = template.findOne(lastQ, OrderLine.class); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(OrderLine.class), "filter", q.getQueryObject(), "sort", q.getSortObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query5 { + public static Query query() { + Query q = new Query(); + q.fields().include("orderLineId", "quantity"); + return q; + } + + public static Map harness(MongoTemplate template) { + Query q = query(); + long count = template.count(q, OrderLine.class); + + Object first = null; + if (count > 0) { + Query asc = query().with(Sort.by(Sort.Direction.ASC, "orderLineId")).limit(1); + first = template.query(OrderLine.class).as(Query5Projection.class).matching(asc).firstValue(); + } + Object last = null; + if (count > 1) { + Query desc = query().with(Sort.by(Sort.Direction.DESC, "orderLineId")).limit(1); + last = template.query(OrderLine.class).as(Query5Projection.class).matching(desc).firstValue(); + } + return Map.of("mongoQuery", Map.of("collection", template.getCollectionName(OrderLine.class), "filter", q.getQueryObject(), "fields", q.getFieldsObject()), "count", count, "firstSample", first, "lastSample", last); + } +} + +public class MongoQueryEntrypoint { + public static void validateMongoEntity(Class entityClass, MongoTemplate mongoTemplate) { + System.out.println("Validating MongoDB entity: " + entityClass.getSimpleName()); + var jsonMapper = QueryRuntimeSupport.createJsonMapper().rebuild().changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.NON_EMPTY)).build(); + Query query = new Query().limit(1); + // This will throw MappingException or similar if the entity mapping is invalid or if the collection doesn't exist / has incompatible data + System.out.println(jsonMapper.writeValueAsString(mongoTemplate.findOne(query, entityClass))); + System.out.println("Successfully validated MongoDB entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + var mongoUri = args.length > 0 ? args[0] : QueryRuntimeSupport.defaultMongoUri(); + var mongoDatabase = args.length > 1 ? args[1] : QueryRuntimeSupport.defaultMongoDatabase(); + QueryRuntimeSupport.configureLogger(); + var template = MongoTemplateFactory.create(mongoUri, mongoDatabase); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + // Only validate @Document aggregate roots - Order and OrderLine. Customer and CustomerTransaction are embedded value objects. + validateMongoEntity(Order.class, template); + validateMongoEntity(OrderLine.class, template); + + // Now create and execute the queries and capture results + var results = new LinkedHashMap(); + List>> harnesses = List.of( + () -> Query1.harness(template), + () -> Query2.harness(template), + () -> Query3.harness(template), + () -> Query4.harness(template), + () -> Query5.harness(template) + ); + + int idx = 0; + for (var harness : harnesses) { + idx += 1; + System.out.println("Executing query" + idx + "..."); + try { + results.put("query" + idx, harness.get()); + System.out.println("Successfully executed query" + idx); + } catch (Exception e) { + System.err.println("Error occurred while executing query" + idx); + e.printStackTrace(); + results.put("query" + idx, Map.of("error", e.toString())); + } + } + QueryRuntimeSupport.createJsonMapper().writeValue(new java.io.File(System.getenv("MONGO_RESULTS_PATH") + "/mongo_results_" + System.currentTimeMillis() + ".json"), results); + } +} diff --git a/services/orchestrator/src/context/snippets/MongoSchemaValidationEntrypoint.java b/services/orchestrator/src/context/snippets/MongoSchemaValidationEntrypoint.java new file mode 100644 index 0000000..dec0a5a --- /dev/null +++ b/services/orchestrator/src/context/snippets/MongoSchemaValidationEntrypoint.java @@ -0,0 +1,356 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; + +import org.slf4j.*; +import org.springframework.data.annotation.*; +import org.springframework.data.mongodb.*; +import org.springframework.data.mongodb.core.*; +import org.springframework.data.mongodb.core.convert.*; +import org.springframework.data.mongodb.core.mapping.*; +import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.query.*; + +import com.fasterxml.jackson.annotation.JsonInclude.*; +import com.mongodb.client.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_MONGO_URI = "mongodb://uom_readonly:uom_readonly@mongodb:27017/uom"; + private static final String DEFAULT_MONGO_DATABASE = "uom"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.NON_EMPTY)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.mongodb.core.MongoTemplate"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String defaultMongoUri() { + return System.getenv("MONGODB_URI") != null ? System.getenv("MONGODB_URI") : DEFAULT_MONGO_URI; + } + + static String defaultMongoDatabase() { + return System.getenv("MONGODB_DATABASE") != null ? System.getenv("MONGODB_DATABASE") : DEFAULT_MONGO_DATABASE; + } +} + +// --- Schema and Related Settings --- + +/** + * Order document with embedded Customer and CustomerTransactions. + * + * TRANSLATED FROM: C# EFCore Customer, CustomerTransaction entities + * ARCHITECTURAL SHIFT: Denormalized - Customers no longer a root collection. + * Instead, Customer and its transactions are embedded within Order. + */ +@Document(collection = "orders") +class Order { + + @Id + private String id; + + @Field("orderId") + private Integer orderId; + + @Field("customerId") + private Integer customerId; + + // Embedded Customer document (denormalized from Sales.Customers table) + private Customer customer; + + @ReadOnlyProperty + @DocumentReference(lazy = true, lookup = "{ 'orderId': ?#{#self.orderId} }", sort = "{ 'orderLineId': 1 }") + private List orderLines; + + // Constructors + public Order() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +/** + * Embedded Customer document within Order. + * Represents the denormalized Sales.Customers table data. + * + * TRANSLATED FROM: C# Customer entity (no longer a @Document root) + * NOTE: No @Document annotation - this is a value object embedded in Order + */ +class Customer { + + private Integer customerId; + private String customerName; + private LocalDate accountOpenedDate; + private BigDecimal creditLimit; + + // Embedded CustomerTransactions array (denormalized from Sales.CustomerTransactions table) + private List customerTransactions = new ArrayList<>(); + + // Constructors + public Customer() { + } + + // Getters and Setters + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public BigDecimal getCreditLimit() { return creditLimit; } + public void setCreditLimit(BigDecimal creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +/** + * Embedded CustomerTransaction document within Customer.customerTransactions array. + * Represents the denormalized Sales.CustomerTransactions table data. + * + * TRANSLATED FROM: C# CustomerTransaction entity + * NOTE: No @Document annotation - this is a value object embedded in Customer + */ +class CustomerTransaction { + + private Integer customerTransactionId; + private Integer customerId; + private LocalDate transactionDate; + private BigDecimal transactionAmount; + + // Constructors + public CustomerTransaction() { + } + + // Getters and Setters + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public BigDecimal getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(BigDecimal transactionAmount) { this.transactionAmount = transactionAmount; } +} + +/** + * OrderLine document. + * Maps to the 'orderLines' collection in MongoDB. + * + * TRANSLATED FROM: C# OrderLine entity + */ +@Document(collection = "orderLines") +class OrderLine { + + @Id + private String id; + + @Field("orderLineId") + private Integer orderLineId; + + @Field("orderId") + private Integer orderId; + + @Field("stockItemId") + private Integer stockItemId; + + private String description; + + @Field("packageTypeId") + private Integer packageTypeId; + + private Integer quantity; + + private BigDecimal unitPrice; + + @Field("taxRate") + private BigDecimal taxRate; + + @Field("pickedQuantity") + private Integer pickedQuantity; + + @Field("pickingCompletedWhen") + private LocalDateTime pickingCompletedWhen; + + @Field("lastEditedBy") + private Integer lastEditedBy; + + @Field("lastEditedWhen") + private LocalDateTime lastEditedWhen; + + // Constructors + public OrderLine() { + } + + // Getters and Setters + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Integer getStockItemId() { return stockItemId; } + public void setStockItemId(Integer stockItemId) { this.stockItemId = stockItemId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getPackageTypeId() { return packageTypeId; } + public void setPackageTypeId(Integer packageTypeId) { this.packageTypeId = packageTypeId; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public BigDecimal getUnitPrice() { return unitPrice; } + public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; } + public BigDecimal getTaxRate() { return taxRate; } + public void setTaxRate(BigDecimal taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public LocalDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(LocalDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public Integer getLastEditedBy() { return lastEditedBy; } + public void setLastEditedBy(Integer lastEditedBy) { this.lastEditedBy = lastEditedBy; } + public LocalDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(LocalDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class MongoTemplateFactory { + private MongoTemplateFactory() { + } + + static MongoTemplate create(String mongoUri, String mongoDatabase) { + MongoDatabaseFactory databaseFactory = new SimpleMongoClientDatabaseFactory( + MongoClients.create(mongoUri), + mongoDatabase); + MongoCustomConversions customConversions = MongoCustomConversions.create(configuration -> { + }); + MongoMappingContext mappingContext = new MongoMappingContext(); + mappingContext.setSimpleTypeHolder(customConversions.getSimpleTypeHolder()); + mappingContext.afterPropertiesSet(); + + MappingMongoConverter converter = new MappingMongoConverter( + new DefaultDbRefResolver(databaseFactory), + mappingContext); + converter.setCustomConversions(customConversions); + converter.setTypeMapper(new DefaultMongoTypeMapper(null)); + converter.afterPropertiesSet(); + + return new MongoTemplate(databaseFactory, converter); + } +} + +// --- Schema Validation Entrypoint --- + +public class MongoSchemaValidationEntrypoint { + /** + * Validates a MongoDB entity by running + * {@code mongoTemplate.findOne(query, entityClass)} + * with a limit of 1. If the mapping is invalid, Spring Data MongoDB will throw. + */ + public static void validateMongoEntity(Class entityClass, MongoTemplate mongoTemplate, JsonMapper jsonMapper) { + System.out.println("Validating MongoDB entity: " + entityClass.getSimpleName()); + Query query = new Query().limit(1); + // This will throw MappingException or similar if the entity mapping is invalid or if the collection doesn't exist / has incompatible data + System.out.println(jsonMapper.writeValueAsString(mongoTemplate.findOne(query, entityClass))); + System.out.println("Successfully validated MongoDB entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + var mongoUri = args.length > 0 ? args[0] : QueryRuntimeSupport.defaultMongoUri(); + var mongoDatabase = args.length > 1 ? args[1] : QueryRuntimeSupport.defaultMongoDatabase(); + QueryRuntimeSupport.configureLogger(); + var jsonMapper = QueryRuntimeSupport.createJsonMapper(); + MongoTemplate template = MongoTemplateFactory.create(mongoUri, mongoDatabase); + + // Only validate @Document aggregate roots - Order and OrderLine. Customer and CustomerTransaction are embedded value objects. + List queries = List.of( + () -> validateMongoEntity(Order.class, template, jsonMapper), + () -> validateMongoEntity(OrderLine.class, template, jsonMapper)); + + for (var query : queries) { + try { + query.run(); + } catch (Exception e) { + System.err.println("Error occurred while validating entity"); + e.printStackTrace(); + } + } + } +} diff --git a/services/orchestrator/src/context/snippets/NHibernateQueryEntrypoint.cs b/services/orchestrator/src/context/snippets/NHibernateQueryEntrypoint.cs new file mode 100644 index 0000000..b234706 --- /dev/null +++ b/services/orchestrator/src/context/snippets/NHibernateQueryEntrypoint.cs @@ -0,0 +1,356 @@ +using NHibernate.Linq; +using System; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Driver; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; +using NHibernate.Dialect; + +namespace nhibernate_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +public class Customer +{ + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual required string CustomerName { get; set; } + public virtual DateTime AccountOpenedDate { get; set; } + public virtual decimal? CreditLimit { get; set; } + public virtual IList CustomerTransactions { get; set; } = []; +} + +public class CustomerTransaction +{ + [JsonPropertyName("customerTransactionId")] + public virtual int CustomerTransactionID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual DateTime TransactionDate { get; set; } + public virtual decimal TransactionAmount { get; set; } +} + +public class Order +{ + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual Customer Customer { get; set; } = null!; + public virtual IList OrderLines { get; set; } = []; +} + +public class OrderLine +{ + [JsonPropertyName("orderLineId")] + public virtual int OrderLineID { get; set; } + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("stockItemId")] + public virtual int StockItemID { get; set; } + public virtual required string Description { get; set; } + [JsonPropertyName("packageTypeId")] + public virtual int PackageTypeID { get; set; } + public virtual int Quantity { get; set; } + public virtual decimal? UnitPrice { get; set; } + public virtual decimal TaxRate { get; set; } + public virtual int PickedQuantity { get; set; } + public virtual DateTime? PickingCompletedWhen { get; set; } + public virtual int LastEditedBy { get; set; } + public virtual DateTime LastEditedWhen { get; set; } +} + +public class CustomerMap : ClassMapping { + public CustomerMap() { + Table("Customers"); Schema("Sales"); + Id(x => x.CustomerID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerName); + Property(x => x.AccountOpenedDate); + Property(x => x.CreditLimit); + Bag(x => x.CustomerTransactions, map => { map.Key(k => k.Column("CustomerID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} +public class CustomerTransactionMap : ClassMapping { + public CustomerTransactionMap() { + Table("CustomerTransactions"); Schema("Sales"); + Id(x => x.CustomerTransactionID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.TransactionDate); + Property(x => x.TransactionAmount); + } +} +public class OrderMap : ClassMapping { + public OrderMap() { + Table("Orders"); Schema("Sales"); + Id(x => x.OrderID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + ManyToOne(x => x.Customer, m => m.Column("CustomerID")); + Bag(x => x.OrderLines, map => { map.Key(k => k.Column("OrderID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} +public class OrderLineMap : ClassMapping { + public OrderLineMap() { + Table("OrderLines"); Schema("Sales"); + Id(x => x.OrderLineID, m => m.Generator(Generators.Identity)); + Property(x => x.OrderID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.StockItemID); + Property(x => x.Description); + Property(x => x.PackageTypeID); + Property(x => x.Quantity); + Property(x => x.UnitPrice); + Property(x => x.TaxRate); + Property(x => x.PickedQuantity); + Property(x => x.PickingCompletedWhen); + Property(x => x.LastEditedBy); + Property(x => x.LastEditedWhen); + } +} + +public record Query3Projection +{ + public decimal TaxRate { get; set; } + public long Count { get; set; } +} + +public record Query5Projection +{ + public int OrderLineID { get; set; } + public int Quantity { get; set; } +} + +// --- Query Entrypoint --- + +public static class NHibernateQueryEntrypoint +{ + public static IQueryable Query1(NHibernate.ISession session) + { + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + return session.Query().Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to); + } + + public static IEnumerable Query2(NHibernate.ISession session) + { + // Cartesian product cannot be done NHibernate + var q = session.Query() + .Where(o => o.CustomerID == 1); + + q.Fetch(o => o.Customer) + .ThenFetchMany(c => c.CustomerTransactions) + .ToFuture(); + + return q.FetchMany(o => o.OrderLines) + .Distinct() + .ToFuture(); + } + + public static IQueryable Query3(NHibernate.ISession session) + { + return session.Query().GroupBy(ol => ol.TaxRate).Select(g => new Query3Projection { TaxRate = g.Key, Count = g.Count() }).OrderByDescending(x => x.Count); + } + + public static IQueryable Query4(NHibernate.ISession session) + { + return session.Query().OrderByDescending(ol => ol.Quantity).Take(50); + } + + public static IQueryable Query5(NHibernate.ISession session) + { + return session.Query().Select(ol => new Query5Projection { OrderLineID = ol.OrderLineID, Quantity = ol.Quantity }); + } + + private static object RunQuery(Func> q, Func? orderBySelector = null) + { + var query = q(); + var count = query.Count(); + return new { sqlString = "logged in console", count, + firstSample = count > 0 ? (orderBySelector != null ? query.OrderBy(orderBySelector).FirstOrDefault() : query.FirstOrDefault()) : default, + lastSample = count > 1 ? (orderBySelector != null ? query.OrderByDescending(orderBySelector).FirstOrDefault() : query.LastOrDefault()) : default }; + } + + public static void ValidateEntity(NHibernate.ISession session) where T : class + { + Console.WriteLine($"Validating NHibernate entity: {typeof(T).Name}"); + session.Query().FirstOrDefault(); + Console.WriteLine($"Successfully validated NHibernate entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + string connectionString = args.ElementAtOrDefault(0) + ?? System.Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + var configuration = new Configuration() + .DataBaseIntegration(db => + { + db.ConnectionString = connectionString; + db.Dialect(); + db.Driver(); + db.LogSqlInConsole = true; + }); + + var mapper = new ModelMapper(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + mapper.AddMapping(); + configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities()); + + using var sessionFactory = configuration.BuildSessionFactory(); + using var session = sessionFactory.OpenSession(); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + ValidateEntity(session); + ValidateEntity(session); + ValidateEntity(session); + ValidateEntity(session); + + // Now create and execute the queries and capture results + var results = new Dictionary(); + var harnesses = new Func[] { + () => RunQuery(() => Query1(session), ol => ol.OrderLineID), + () => RunQuery(() => Query2(session), o => o.OrderID), + () => RunQuery(() => Query3(session), x => x.TaxRate), + () => RunQuery(() => Query4(session)), + () => RunQuery(() => Query5(session), ol => ol.OrderLineID) + }; + + for (int i = 0; i < harnesses.Length; i++) { + var qid = i+1; + Console.WriteLine($"Running Query {qid}..."); + try { + results[$"query{qid}"] = harnesses[i](); + } catch (Exception ex) { + results[$"query{qid}"] = new { error = ex.Message }; + Console.Error.WriteLine($"Error occurred while running Query{qid}: {ex}"); + } + } + File.WriteAllText($"{System.Environment.GetEnvironmentVariable("NHIBERNATE_RESULTS_PATH")}/nhibernate_results_{DateTime.Now:yyyyMMdd_HHmmss}.json", CustomJsonSerializer.Serialize(results)); + } +} diff --git a/services/orchestrator/src/context/snippets/NHibernateSchemaValidationEntrypoint.cs b/services/orchestrator/src/context/snippets/NHibernateSchemaValidationEntrypoint.cs new file mode 100644 index 0000000..364100f --- /dev/null +++ b/services/orchestrator/src/context/snippets/NHibernateSchemaValidationEntrypoint.cs @@ -0,0 +1,297 @@ +using System; +using System.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; +using System.Text.Encodings.Web; +using System.Globalization; +using System.Text.Json.Serialization.Metadata; +using System.Collections.Generic; +using NHibernate.Cfg; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace nhibernate_sandbox; + +// --- Harness and Utilities --- + +public static class CustomJsonSerializer +{ + public class StrictIsoDateTimeConverter : JsonConverter + { + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTime.Parse( + reader.GetString()!, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); + } + + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class StrictIsoDateTimeOffsetConverter : JsonConverter + { + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateTimeOffset.Parse(reader.GetString()!, CultureInfo.InvariantCulture); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteStringValue( + value.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture)); + } + } + + public class FixedDecimalConverter : JsonConverter + { + public override decimal Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDecimal(); + } + + public override void Write(Utf8JsonWriter writer, decimal value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class FixedDoubleConverter : JsonConverter + { + public override double Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetDouble(); + } + + public override void Write(Utf8JsonWriter writer, double value, JsonSerializerOptions options) + { + writer.WriteRawValue(value.ToString("0.000", CultureInfo.InvariantCulture)); + } + } + + public class CustomCamelCaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name)) return name; + string camelCased = CamelCase.ConvertName(name); + if (camelCased.EndsWith("ID")) + { + return camelCased[..^2] + "Id"; + } else if (camelCased.EndsWith("URL")) + { + return camelCased[..^3] + "Url"; + } + return camelCased; + } + } + + public static Action AlphabetizeProperties() + { + return static typeInfo => + { + if (typeInfo.Kind!= JsonTypeInfoKind.Object) return; + var properties = typeInfo.Properties.OrderBy(p => p.Name, StringComparer.Ordinal).ToList(); + typeInfo.Properties.Clear(); + for (int i = 0; i < properties.Count; i++) + { + properties[i].Order = i; + typeInfo.Properties.Add(properties[i]); + } + }; + } + + public static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = new CustomCamelCaseNamingPolicy(), + DictionaryKeyPolicy = new CustomCamelCaseNamingPolicy(), + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + ReferenceHandler = ReferenceHandler.IgnoreCycles, + TypeInfoResolver = new DefaultJsonTypeInfoResolver + { + Modifiers = { AlphabetizeProperties() } + }, + Converters = { + new StrictIsoDateTimeConverter(), + new StrictIsoDateTimeOffsetConverter(), + new FixedDecimalConverter(), + new FixedDoubleConverter(), + new JsonStringEnumConverter(new CustomCamelCaseNamingPolicy()) + } + }; + + public static string Serialize(object? entity) + { + return JsonSerializer.Serialize(entity, Options); + } +} + +// --- Schema and Related Settings --- + +public class Customer +{ + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual required string CustomerName { get; set; } + public virtual DateTime AccountOpenedDate { get; set; } + public virtual decimal? CreditLimit { get; set; } + [JsonIgnore] // Don't serialize this, cause it's large + public virtual IList CustomerTransactions { get; set; } = []; +} + +public class CustomerTransaction +{ + [JsonPropertyName("customerTransactionId")] + public virtual int CustomerTransactionID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual DateTime TransactionDate { get; set; } + public virtual decimal TransactionAmount { get; set; } +} + +public class Order +{ + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("customerId")] + public virtual int CustomerID { get; set; } + public virtual Customer Customer { get; set; } = null!; + [JsonIgnore] // Don't serialize this, cause it's large + public virtual IList OrderLines { get; set; } = []; +} + +public class OrderLine +{ + [JsonPropertyName("orderLineId")] + public virtual int OrderLineID { get; set; } + [JsonPropertyName("orderId")] + public virtual int OrderID { get; set; } + [JsonPropertyName("stockItemId")] + public virtual int StockItemID { get; set; } + public virtual required string Description { get; set; } + [JsonPropertyName("packageTypeId")] + public virtual int PackageTypeID { get; set; } + public virtual int Quantity { get; set; } + public virtual decimal? UnitPrice { get; set; } + public virtual decimal TaxRate { get; set; } + public virtual int PickedQuantity { get; set; } + public virtual DateTime? PickingCompletedWhen { get; set; } + public virtual int LastEditedBy { get; set; } + public virtual DateTime LastEditedWhen { get; set; } +} + +public class CustomerMap : ClassMapping { + public CustomerMap() { + Table("Customers"); Schema("Sales"); + Id(x => x.CustomerID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerName); + Property(x => x.AccountOpenedDate); + Property(x => x.CreditLimit); + Bag(x => x.CustomerTransactions, map => { map.Key(k => k.Column("CustomerID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} + +public class CustomerTransactionMap : ClassMapping { + public CustomerTransactionMap() { + Table("CustomerTransactions"); Schema("Sales"); + Id(x => x.CustomerTransactionID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.TransactionDate); + Property(x => x.TransactionAmount); + } +} + +public class OrderMap : ClassMapping { + public OrderMap() { + Table("Orders"); Schema("Sales"); + Id(x => x.OrderID, m => m.Generator(Generators.Identity)); + Property(x => x.CustomerID, m => { m.Insert(false); m.Update(false); }); + ManyToOne(x => x.Customer, m => m.Column("CustomerID")); + Bag(x => x.OrderLines, map => { map.Key(k => k.Column("OrderID")); map.Inverse(true); }, rel => rel.OneToMany()); + } +} + +public class OrderLineMap : ClassMapping { + public OrderLineMap() { + Table("OrderLines"); Schema("Sales"); + Id(x => x.OrderLineID, m => m.Generator(Generators.Identity)); + Property(x => x.OrderID, m => { m.Insert(false); m.Update(false); }); + Property(x => x.StockItemID); + Property(x => x.Description); + Property(x => x.PackageTypeID); + Property(x => x.Quantity); + Property(x => x.UnitPrice); + Property(x => x.TaxRate); + Property(x => x.PickedQuantity); + Property(x => x.PickingCompletedWhen); + Property(x => x.LastEditedBy); + Property(x => x.LastEditedWhen); + } +} + +// --- Schema Validation Entrypoint --- + +public static class NHibernateSchemaValidationEntrypoint +{ + public static void ValidateEntity(NHibernate.ISession session) where T : class + { + Console.WriteLine($"Validating NHibernate entity: {typeof(T).Name}"); + Console.WriteLine(CustomJsonSerializer.Serialize(session.Query().FirstOrDefault())); + Console.WriteLine($"Successfully validated NHibernate entity: {typeof(T).Name}"); + } + + public static void Main(string[] args) + { + var connectionString = args.ElementAtOrDefault(0) ?? + System.Environment.GetEnvironmentVariable("CONNECTION_STRING") + ?? + "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"; + + var configuration = new Configuration(); + configuration.DataBaseIntegration(c => + { + c.Driver(); + c.Dialect(); + c.ConnectionString = connectionString; + c.LogSqlInConsole = true; + }); + + var mapper = new ModelMapper(); + mapper.AddMappings(new[] + { + typeof(CustomerMap), + typeof(CustomerTransactionMap), + typeof(OrderMap), + typeof(OrderLineMap) + }); + + configuration.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities()); + + var sessionFactory = configuration.BuildSessionFactory(); + using var session = sessionFactory.OpenSession(); + + var queries = new Action[] + { + () => ValidateEntity(session), + () => ValidateEntity(session), + () => ValidateEntity(session), + () => ValidateEntity(session) + }; + + foreach (var query in queries) + { + try + { + query(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error occurred while validating entity: {ex}"); + } + } + } +} \ No newline at end of file diff --git a/services/orchestrator/src/context/snippets/Neo4jQueryEntrypoint.java b/services/orchestrator/src/context/snippets/Neo4jQueryEntrypoint.java new file mode 100644 index 0000000..8186c79 --- /dev/null +++ b/services/orchestrator/src/context/snippets/Neo4jQueryEntrypoint.java @@ -0,0 +1,529 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; +import java.util.Set; +import java.util.function.*; + +import org.neo4j.cypherdsl.core.*; +import org.neo4j.cypherdsl.core.SortItem.*; +import org.neo4j.cypherdsl.core.StatementBuilder.*; +import org.neo4j.driver.*; +import org.slf4j.*; +import org.springframework.data.neo4j.core.*; +import org.springframework.data.neo4j.core.mapping.*; +import org.springframework.data.neo4j.core.schema.*; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.transaction.*; + +import com.fasterxml.jackson.annotation.*; +import com.fasterxml.jackson.annotation.JsonInclude.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_NEO4J_URI = "neo4j://neo4j:7687"; + private static final String DEFAULT_NEO4J_USER = "neo4j"; + private static final String DEFAULT_NEO4J_PASS = "password"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.ALWAYS)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.neo4j.cypher"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String getNeo4jUri() { + String uri = System.getenv("NEO4J_URI"); + return uri != null ? uri : DEFAULT_NEO4J_URI; + } + + static String getNeo4jUsername() { + String user = System.getenv("NEO4J_USERNAME"); + return user != null ? user : DEFAULT_NEO4J_USER; + } + + static String getNeo4jPassword() { + String pass = System.getenv("NEO4J_PASSWORD"); + return pass != null ? pass : DEFAULT_NEO4J_PASS; + } +} + +// --- Schema and Related Settings --- + +@Node("Order") +@JsonIgnoreProperties({ "id" }) +class Order { + + @Id @GeneratedValue + private String id; + + @Property("orderId") + private Integer orderId; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.OUTGOING) + private Customer customer; + + @Relationship(type = "ORDERS", direction = Relationship.Direction.INCOMING) + private List orderLines; + + public Order() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +@Node("Customer") +@JsonIgnoreProperties({ "id" }) +class Customer { + + @Id @GeneratedValue + private String id; + + @Property("customerId") + private Integer customerId; + + @Property("customerName") + private String customerName; + + @Property("accountOpenedDate") + private LocalDate accountOpenedDate; + + @Property("creditLimit") + private Double creditLimit; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.INCOMING) + private List customerTransactions; + + public Customer() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public Double getCreditLimit() { return creditLimit; } + public void setCreditLimit(Double creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +@Node("CustomerTransaction") +@JsonIgnoreProperties({ "id" }) +class CustomerTransaction { + + @Id @GeneratedValue + private String id; + + @Property("customerTransactionId") + private Integer customerTransactionId; + + @Property("transactionDate") + private LocalDate transactionDate; + + @Property("transactionAmount") + private Double transactionAmount; + + public CustomerTransaction() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public Double getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(Double transactionAmount) { this.transactionAmount = transactionAmount; } +} + +@Node("OrderLine") +@JsonIgnoreProperties({ "id" }) +class OrderLine { + + @Id @GeneratedValue + private String id; + + @Property("orderLineId") + private Integer orderLineId; + + @Property("description") + private String description; + + @Property("quantity") + private Integer quantity; + + @Property("unitPrice") + private Double unitPrice; + + @Property("taxRate") + private Double taxRate; + + @Property("pickedQuantity") + private Integer pickedQuantity; + + @Property("pickingCompletedWhen") + private ZonedDateTime pickingCompletedWhen; + + @Property("lastEditedWhen") + private ZonedDateTime lastEditedWhen; + + public OrderLine() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public Double getUnitPrice() { return unitPrice; } + public void setUnitPrice(Double unitPrice) { this.unitPrice = unitPrice; } + public Double getTaxRate() { return taxRate; } + public void setTaxRate(Double taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public ZonedDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(ZonedDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public ZonedDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(ZonedDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class Neo4jTemplateFactory { + private Neo4jTemplateFactory() { + } + + static Neo4jTemplate create(Driver driver) { + Neo4jClient client = Neo4jClient.create(driver); + var mappingContext = new Neo4jMappingContext(); + mappingContext.setInitialEntitySet(Set.of(Order.class, Customer.class, CustomerTransaction.class, OrderLine.class)); + mappingContext.afterPropertiesSet(); + + Neo4jTransactionManager transactionManager = new Neo4jTransactionManager(driver); + return new Neo4jTemplate(client, mappingContext, transactionManager); + } +} + +// --- Queries --- + +record Query3Projection(Double taxRate, Long count) { +} + +record Query5Projection(Integer orderLineId, Integer quantity) { +} + +final class Query1 { + public static BuildableStatement query(boolean returnCount) { + ZonedDateTime from = ZonedDateTime.of(2014, 12, 20, 0, 0, 0, 0, ZoneOffset.UTC); + ZonedDateTime to = ZonedDateTime.of(2014, 12, 31, 0, 0, 0, 0, ZoneOffset.UTC); + var orderLine = Cypher.node("OrderLine").named("ol"); + var partialStatement = Cypher.match(orderLine) + .where(orderLine.property("pickingCompletedWhen").gte(Cypher.parameter("from", from))) + .and(orderLine.property("pickingCompletedWhen").lte(Cypher.parameter("to", to))); + if (returnCount) return partialStatement.returning(Cypher.count(orderLine)); + return partialStatement.returning(orderLine); + } + + public static Map harness(Neo4jTemplate template) { + long count = template.count(query(true).build()); + var q = query(false); + Object first = null; + if (count > 0) { + var asc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.ASC)).limit(1).build(); + first = template.findOne(asc, asc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + Object last = null; + if (count > 1) { + var desc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.DESC)).limit(1).build(); + last = template.findOne(desc, desc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query2 { + // Note: Avoid cartesian products, collect nodes and relationships and their properties into SDN aggregate roots/projections, see https://docs.spring.io/spring-data/neo4j/reference/appendix/custom-queries.html + public static BuildableStatement query(boolean returnCount) { + var order = Cypher.node("Order").named("o"); + var customer = Cypher.node("Customer").named("c"); + var rel1 = order.relationshipTo(customer, "CUSTOMERS").named("r1"); + var orderLine = Cypher.node("OrderLine").named("ol"); + var rel2 = order.relationshipFrom(orderLine, "ORDERS").named("r2"); + var transaction = Cypher.node("CustomerTransaction").named("ct"); + var rel3 = customer.relationshipFrom(transaction, "CUSTOMERS").named("r3"); + + var orderLines = Cypher.name("orderLines"); + var rel2List = Cypher.name("rel2List"); + var customerTransactions = Cypher.name("customerTransactions"); + var rel3List = Cypher.name("rel3List"); + + var partial = Cypher.match(rel2, rel1, rel3) + .where(customer.property("customerId").isEqualTo(Cypher.literalOf(1))) + .with(order, Cypher.collect(rel2).as(rel2List), Cypher.collect(orderLine).as(orderLines), rel1, customer, rel3, transaction) + .with(order, rel2List, orderLines, rel1, customer, Cypher.collect(rel3).as(rel3List), Cypher.collect(transaction).as(customerTransactions)); + + if (returnCount) return partial.returning(Cypher.count(order)); + return partial.returning(order.getRequiredSymbolicName(), rel2List, orderLines, rel1.getRequiredSymbolicName(), customer.getRequiredSymbolicName(), rel3List, customerTransactions); + } + + public static Map harness(Neo4jTemplate template) { + long count = template.count(query(true).build()); + var q = query(false); + Object first = null; + if (count > 0) { + var asc = ((OngoingReadingAndReturn)query(false)).orderBy(Cypher.sort(Cypher.property("o", "orderId"), Direction.ASC)).limit(1).build(); + first = template.findOne(asc, asc.getCatalog().getParameters(), Order.class).orElse(null); + } + Object last = null; + if (count > 1) { + var desc = ((OngoingReadingAndReturn)query(false)).orderBy(Cypher.sort(Cypher.property("o", "orderId"), Direction.DESC)).limit(1).build(); + last = template.findOne(desc, desc.getCatalog().getParameters(), Order.class).orElse(null); + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query3 { + public static BuildableStatement query(boolean returnCount) { + var orderLine = Cypher.node("OrderLine").named("ol"); + var withClause = Cypher.match(orderLine) + .with(orderLine.property("taxRate").as("taxRate"), Cypher.count(orderLine).as("count")); + if (returnCount) return withClause.returning(Cypher.count(Cypher.asterisk())); + return withClause.returning(Cypher.name("taxRate"), Cypher.name("count")).orderBy(Cypher.sort(Cypher.name("count"), Direction.DESC)); + } + + public static Map harness(Neo4jTemplate template, Neo4jClient client) { + long count = template.count(query(true).build()); + Object first = null; + if (count > 0) { + var asc = Cypher.call(query(false).build()).returning(Cypher.asterisk()).orderBy(Cypher.sort(Cypher.name("taxRate"), Direction.ASC)).limit(1).build(); + var firstMap = client.query(asc.getCypher()).bindAll(asc.getCatalog().getParameters()).fetch().one().orElse(null); + first = new Query3Projection((Double) firstMap.get("taxRate"), (Long) firstMap.get("count")); + } + Object last = null; + if (count > 1) { + var desc = Cypher.call(query(false).build()).returning(Cypher.asterisk()).orderBy(Cypher.sort(Cypher.name("taxRate"), Direction.DESC)).limit(1).build(); + var lastMap = client.query(desc.getCypher()).bindAll(desc.getCatalog().getParameters()).fetch().one().orElse(null); + last = new Query3Projection((Double) lastMap.get("taxRate"), (Long) lastMap.get("count")); + } + var stmt = query(false).build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +final class Query4 { + public static BuildableStatement query(boolean returnCount) { + var orderLine = Cypher.node("OrderLine").named("ol"); + var partial = Cypher.match(orderLine); + if (returnCount) return partial.returning(Cypher.count(orderLine)); + return partial.returning(orderLine).orderBy(Cypher.sort(orderLine.property("quantity"), Direction.DESC)).limit(50); + } + + public static Map harness(Neo4jTemplate template) { + long count = template.count(query(true).build()); + long actualCount = Math.min(count, 50); + + var q = query(false); + Object first = null; + if (actualCount > 0) { + var asc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.ASC)).limit(1).build(); + first = template.findOne(asc, asc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + Object last = null; + if (actualCount > 1) { + var desc = ((OngoingReadingAndReturn)q).orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.DESC)).limit(1).build(); + last = template.findOne(desc, desc.getCatalog().getParameters(), OrderLine.class).orElse(null); + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", actualCount, "firstSample", first, "lastSample", last); + } +} + +final class Query5 { + public static BuildableStatement query(boolean returnCount) { + var orderLine = Cypher.node("OrderLine").named("ol"); + if (returnCount) return Cypher.match(orderLine).returning(Cypher.count(orderLine)); + return Cypher.match(orderLine).returning( + orderLine.property("orderLineId").as("orderLineId"), + orderLine.property("quantity").as("quantity") + ); + } + + public static Map harness(Neo4jTemplate template, Neo4jClient client) { + long count = template.count(query(true).build()); + + var q = query(false); + Object first = null; + if (count > 0) { + var asc = ((OngoingReadingAndReturn)q) + .orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.ASC)) + .limit(1).build(); + var firstMap = client.query(asc.getCypher()) + .bindAll(asc.getCatalog().getParameters()) + .fetch().one().orElse(null); + if (firstMap != null) { + Number id = (Number) firstMap.get("orderLineId"); + Number quantity = (Number) firstMap.get("quantity"); + first = new Query5Projection(id != null ? id.intValue() : null, quantity != null ? quantity.intValue() : null); + } + } + Object last = null; + if (count > 1) { + var desc = ((OngoingReadingAndReturn)q) + .orderBy(Cypher.sort(Cypher.property("ol", "orderLineId"), Direction.DESC)) + .limit(1).build(); + var lastMap = client.query(desc.getCypher()) + .bindAll(desc.getCatalog().getParameters()) + .fetch().one().orElse(null); + if (lastMap != null) { + Number id = (Number) lastMap.get("orderLineId"); + Number quantity = (Number) lastMap.get("quantity"); + last = new Query5Projection(id != null ? id.intValue() : null, quantity != null ? quantity.intValue() : null); + } + } + var stmt = q.build(); + return Map.of("cypher", Map.of("query", stmt.getCypher(), "parameters", stmt.getCatalog().getParameters()), "count", count, "firstSample", first, "lastSample", last); + } +} + +// --- Query Entrypoint --- + +public class Neo4jQueryEntrypoint { + + public static void validateNeo4jEntity(Class entityClass, Neo4jTemplate neo4jTemplate, JsonMapper jsonMapper) { + System.out.println("Validating Neo4j entity: " + entityClass.getSimpleName()); + var node = Cypher.node(entityClass.getSimpleName()); + System.out.println(jsonMapper.writeValueAsString(neo4jTemplate.findOne(Cypher.match(node).returning(node).limit(1).build(), Map.of(), entityClass))); + System.out.println("Successfully validated Neo4j entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + String uri = args.length > 0 ? args[0] : QueryRuntimeSupport.getNeo4jUri(); + String user = args.length > 1 ? args[1] : QueryRuntimeSupport.getNeo4jUsername(); + String pass = args.length > 2 ? args[2] : QueryRuntimeSupport.getNeo4jPassword(); + QueryRuntimeSupport.configureLogger(); + + try (Driver driver = GraphDatabase.driver(uri, AuthTokens.basic(user, pass))) { + Neo4jTemplate template = Neo4jTemplateFactory.create(driver); + Neo4jClient client = Neo4jClient.create(driver); + var jsonMapper = QueryRuntimeSupport.createJsonMapper(); + + // First, validate that our entity mappings are correct and can be used to query the database without errors + // Validate all entities annotated with @Node + validateNeo4jEntity(Order.class, template, jsonMapper); + validateNeo4jEntity(Customer.class, template, jsonMapper); + validateNeo4jEntity(CustomerTransaction.class, template, jsonMapper); + validateNeo4jEntity(OrderLine.class, template, jsonMapper); + + // Now create and execute the queries and capture results + var results = new LinkedHashMap(); + List>> harnesses = List.of( + () -> Query1.harness(template), + () -> Query2.harness(template), + () -> Query3.harness(template, client), + () -> Query4.harness(template), + () -> Query5.harness(template, client) + ); + + int idx = 0; + for (var harness : harnesses) { + idx += 1; + System.out.println("Executing query" + idx + "..."); + try { + results.put("query" + idx, harness.get()); + System.out.println("Successfully executed query" + idx); + } catch (Exception e) { + System.err.println("Error occurred while executing query" + idx); + e.printStackTrace(); + results.put("query" + idx, Map.of("error", e.toString())); + } + } + QueryRuntimeSupport.createJsonMapper().writeValue(new java.io.File(System.getenv("NEO4J_RESULTS_PATH") + "/neo4j_results_" + System.currentTimeMillis() + ".json"), results); + } + } +} diff --git a/services/orchestrator/src/context/snippets/Neo4jSchemaValidationEntrypoint.java b/services/orchestrator/src/context/snippets/Neo4jSchemaValidationEntrypoint.java new file mode 100644 index 0000000..a5ff757 --- /dev/null +++ b/services/orchestrator/src/context/snippets/Neo4jSchemaValidationEntrypoint.java @@ -0,0 +1,323 @@ +package uom.services; + +import java.math.*; +import java.time.*; +import java.time.format.*; +import java.time.temporal.*; +import java.util.*; +import java.util.Set; + +import org.neo4j.cypherdsl.core.*; +import org.neo4j.driver.*; +import org.slf4j.*; +import org.springframework.data.neo4j.core.*; +import org.springframework.data.neo4j.core.mapping.*; +import org.springframework.data.neo4j.core.schema.*; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; +import org.springframework.data.neo4j.core.schema.Relationship; +import org.springframework.data.neo4j.core.transaction.*; + +import com.fasterxml.jackson.annotation.JsonInclude.*; + +import ch.qos.logback.classic.*; +import tools.jackson.core.*; +import tools.jackson.databind.*; +import tools.jackson.databind.cfg.*; +import tools.jackson.databind.json.*; +import tools.jackson.databind.module.*; +import tools.jackson.databind.ser.std.*; + +// --- Harness and Utilities --- + +class CustomJsonSerializer extends StdSerializer { + + private static final DateTimeFormatter DATETIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC); + + public CustomJsonSerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializationContext ctx) { + if (value == null) { + gen.writeNull(); + } else if (value instanceof BigDecimal) { + gen.writeNumber(((BigDecimal) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Double) { + gen.writeNumber(BigDecimal.valueOf((Double) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof Float) { + gen.writeNumber(BigDecimal.valueOf((Float) value).setScale(3, RoundingMode.HALF_UP).toPlainString()); + } else if (value instanceof LocalDate) { + gen.writeString(DATETIME_FORMATTER.format(((LocalDate) value).atStartOfDay())); + } else if (value instanceof LocalDateTime) { + gen.writeString(DATETIME_FORMATTER.format((LocalDateTime) value)); + } else if (value instanceof Date) { + gen.writeString(DATETIME_FORMATTER.format(((Date) value).toInstant())); + } else if (value instanceof TemporalAccessor) { + gen.writeString(DATETIME_FORMATTER.format((TemporalAccessor) value)); + } else { + gen.writeString(value.toString()); + } + } +} + +final class QueryRuntimeSupport { + private static final String DEFAULT_NEO4J_URI = "neo4j://neo4j:7687"; + private static final String DEFAULT_NEO4J_USER = "neo4j"; + private static final String DEFAULT_NEO4J_PASS = "password"; + + private QueryRuntimeSupport() { + } + + static JsonMapper createJsonMapper() { + SimpleModule customModule = new SimpleModule(); + CustomJsonSerializer customJsonSerializer = new CustomJsonSerializer(); + customModule.addSerializer(LocalDate.class, customJsonSerializer); + customModule.addSerializer(LocalDateTime.class, customJsonSerializer); + customModule.addSerializer(ZonedDateTime.class, customJsonSerializer); + customModule.addSerializer(OffsetDateTime.class, customJsonSerializer); + customModule.addSerializer(Instant.class, customJsonSerializer); + customModule.addSerializer(Date.class, customJsonSerializer); + customModule.addSerializer(BigDecimal.class, customJsonSerializer); + customModule.addSerializer(Double.class, customJsonSerializer); + customModule.addSerializer(Float.class, customJsonSerializer); + return JsonMapper.builder() + .changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(Include.ALWAYS)) + .disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS) + .enable(StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN) + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS) + .addModule(customModule) + .build(); + } + + static void configureLogger() { + var loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + var mongoLogger = loggerContext.getLogger("org.springframework.data.neo4j.cypher"); + mongoLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + static String getNeo4jUri() { + String uri = System.getenv("NEO4J_URI"); + return uri != null ? uri : DEFAULT_NEO4J_URI; + } + + static String getNeo4jUsername() { + String user = System.getenv("NEO4J_USERNAME"); + return user != null ? user : DEFAULT_NEO4J_USER; + } + + static String getNeo4jPassword() { + String pass = System.getenv("NEO4J_PASSWORD"); + return pass != null ? pass : DEFAULT_NEO4J_PASS; + } +} + +// --- Schema and Related Settings --- + +@Node("Order") +class Order { + + @Id @GeneratedValue + private String id; + + @Property("orderId") + private Integer orderId; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.OUTGOING) + private Customer customer; + + @Relationship(type = "ORDERS", direction = Relationship.Direction.INCOMING) + private List orderLines; + + public Order() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderId() { return orderId; } + public void setOrderId(Integer orderId) { this.orderId = orderId; } + public Customer getCustomer() { return customer; } + public void setCustomer(Customer customer) { this.customer = customer; } + public List getOrderLines() { return orderLines; } + public void setOrderLines(List orderLines) { this.orderLines = orderLines; } +} + +@Node("Customer") +class Customer { + + @Id @GeneratedValue + private String id; + + @Property("customerId") + private Integer customerId; + + @Property("customerName") + private String customerName; + + @Property("accountOpenedDate") + private LocalDate accountOpenedDate; + + @Property("creditLimit") + private Double creditLimit; + + @Relationship(type = "CUSTOMERS", direction = Relationship.Direction.INCOMING) + private List customerTransactions; + + public Customer() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerId() { return customerId; } + public void setCustomerId(Integer customerId) { this.customerId = customerId; } + public String getCustomerName() { return customerName; } + public void setCustomerName(String customerName) { this.customerName = customerName; } + public LocalDate getAccountOpenedDate() { return accountOpenedDate; } + public void setAccountOpenedDate(LocalDate accountOpenedDate) { this.accountOpenedDate = accountOpenedDate; } + public Double getCreditLimit() { return creditLimit; } + public void setCreditLimit(Double creditLimit) { this.creditLimit = creditLimit; } + public List getCustomerTransactions() { return customerTransactions; } + public void setCustomerTransactions(List customerTransactions) { this.customerTransactions = customerTransactions; } +} + +@Node("CustomerTransaction") +class CustomerTransaction { + + @Id @GeneratedValue + private String id; + + @Property("customerTransactionId") + private Integer customerTransactionId; + + @Property("transactionDate") + private LocalDate transactionDate; + + @Property("transactionAmount") + private Double transactionAmount; + + public CustomerTransaction() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getCustomerTransactionId() { return customerTransactionId; } + public void setCustomerTransactionId(Integer customerTransactionId) { this.customerTransactionId = customerTransactionId; } + public LocalDate getTransactionDate() { return transactionDate; } + public void setTransactionDate(LocalDate transactionDate) { this.transactionDate = transactionDate; } + public Double getTransactionAmount() { return transactionAmount; } + public void setTransactionAmount(Double transactionAmount) { this.transactionAmount = transactionAmount; } +} + +@Node("OrderLine") +class OrderLine { + + @Id @GeneratedValue + private String id; + + @Property("orderLineId") + private Integer orderLineId; + + @Property("description") + private String description; + + @Property("quantity") + private Integer quantity; + + @Property("unitPrice") + private Double unitPrice; + + @Property("taxRate") + private Double taxRate; + + @Property("pickedQuantity") + private Integer pickedQuantity; + + @Property("pickingCompletedWhen") + private ZonedDateTime pickingCompletedWhen; + + @Property("lastEditedWhen") + private ZonedDateTime lastEditedWhen; + + public OrderLine() { + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public Integer getOrderLineId() { return orderLineId; } + public void setOrderLineId(Integer orderLineId) { this.orderLineId = orderLineId; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public Double getUnitPrice() { return unitPrice; } + public void setUnitPrice(Double unitPrice) { this.unitPrice = unitPrice; } + public Double getTaxRate() { return taxRate; } + public void setTaxRate(Double taxRate) { this.taxRate = taxRate; } + public Integer getPickedQuantity() { return pickedQuantity; } + public void setPickedQuantity(Integer pickedQuantity) { this.pickedQuantity = pickedQuantity; } + public ZonedDateTime getPickingCompletedWhen() { return pickingCompletedWhen; } + public void setPickingCompletedWhen(ZonedDateTime pickingCompletedWhen) { this.pickingCompletedWhen = pickingCompletedWhen; } + public ZonedDateTime getLastEditedWhen() { return lastEditedWhen; } + public void setLastEditedWhen(ZonedDateTime lastEditedWhen) { this.lastEditedWhen = lastEditedWhen; } +} + +final class Neo4jTemplateFactory { + private Neo4jTemplateFactory() { + } + + static Neo4jTemplate create(Driver driver) { + Neo4jClient client = Neo4jClient.create(driver); + var mappingContext = new Neo4jMappingContext(); + mappingContext.setInitialEntitySet(Set.of(Order.class, Customer.class, CustomerTransaction.class, OrderLine.class)); + mappingContext.afterPropertiesSet(); + + Neo4jTransactionManager transactionManager = new Neo4jTransactionManager(driver); + return new Neo4jTemplate(client, mappingContext, transactionManager); + } +} + +// --- Query Entrypoint --- + +public class Neo4jSchemaValidationEntrypoint { + /** + * Validates a Neo4j entity by running + * {@code neo4jTemplate.findAll(entityClass)} + * which translates to a MATCH query. If the mapping is invalid, Spring Data + * Neo4j + * will throw a MappingException. + */ + public static void validateNeo4jEntity(Class entityClass, Neo4jTemplate neo4jTemplate, JsonMapper jsonMapper) { + System.out.println("Validating Neo4j entity: " + entityClass.getSimpleName()); + var node = Cypher.node(entityClass.getSimpleName()); + System.out.println(jsonMapper.writeValueAsString(neo4jTemplate.findOne(Cypher.match(node).returning(node).limit(1).build(), Map.of(), entityClass))); + System.out.println("Successfully validated Neo4j entity: " + entityClass.getSimpleName()); + } + + public static void main(String[] args) throws Exception { + String uri = args.length > 0 ? args[0] : QueryRuntimeSupport.getNeo4jUri(); + String user = args.length > 1 ? args[1] : QueryRuntimeSupport.getNeo4jUsername(); + String pass = args.length > 2 ? args[2] : QueryRuntimeSupport.getNeo4jPassword(); + QueryRuntimeSupport.configureLogger(); + + try (Driver driver = GraphDatabase.driver(uri, AuthTokens.basic(user, pass))) { + Neo4jTemplate template = Neo4jTemplateFactory.create(driver); + var jsonMapper = QueryRuntimeSupport.createJsonMapper(); + List queries = List.of( + () -> validateNeo4jEntity(Order.class, template, jsonMapper), + () -> validateNeo4jEntity(Customer.class, template, jsonMapper), + () -> validateNeo4jEntity(CustomerTransaction.class, template, jsonMapper), + () -> validateNeo4jEntity(OrderLine.class, template, jsonMapper)); + for (var query : queries) { + try { + query.run(); + } catch (Exception e) { + System.err.println("Error occurred while validating entity"); + e.printStackTrace(); + } + } + } + } +} diff --git a/services/orchestrator/src/context/snippets/dapper-sandbox.csproj b/services/orchestrator/src/context/snippets/dapper-sandbox.csproj new file mode 100644 index 0000000..30bfae4 --- /dev/null +++ b/services/orchestrator/src/context/snippets/dapper-sandbox.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + dapper_sandbox + enable + enable + + + + + + diff --git a/services/orchestrator/src/context/snippets/efcore-sandbox.csproj b/services/orchestrator/src/context/snippets/efcore-sandbox.csproj new file mode 100644 index 0000000..d348e95 --- /dev/null +++ b/services/orchestrator/src/context/snippets/efcore-sandbox.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + efcore_sandbox + enable + enable + + + + + + diff --git a/services/orchestrator/src/context/snippets/mongo-pom.xml b/services/orchestrator/src/context/snippets/mongo-pom.xml new file mode 100644 index 0000000..e492469 --- /dev/null +++ b/services/orchestrator/src/context/snippets/mongo-pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + uom.services + mongo_sandbox + 1.0-SNAPSHOT + + Minimal Maven project for MongoDB sandbox compilation of LLM-generated Java code. + + + 25 + 25 + 25 + UTF-8 + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + org.springframework.boot + spring-boot-starter-json + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + + + diff --git a/services/orchestrator/src/context/snippets/neo4j-pom.xml b/services/orchestrator/src/context/snippets/neo4j-pom.xml new file mode 100644 index 0000000..972404c --- /dev/null +++ b/services/orchestrator/src/context/snippets/neo4j-pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.3 + + + uom.services + neo4j_sandbox + 1.0-SNAPSHOT + + Minimal Maven project for Neo4j sandbox compilation of LLM-generated Java code. + + + 25 + 25 + 25 + UTF-8 + + + + org.springframework.boot + spring-boot-starter-data-neo4j + + + org.springframework.boot + spring-boot-starter-json + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.6.3 + + + + diff --git a/services/orchestrator/src/context/snippets/nhibernate-sandbox.csproj b/services/orchestrator/src/context/snippets/nhibernate-sandbox.csproj new file mode 100644 index 0000000..07f6d5b --- /dev/null +++ b/services/orchestrator/src/context/snippets/nhibernate-sandbox.csproj @@ -0,0 +1,13 @@ + + + Exe + net10.0 + nhibernate_sandbox + enable + enable + + + + + + diff --git a/services/orchestrator/src/react_agent/__init__.py b/services/orchestrator/src/react_agent/__init__.py new file mode 100644 index 0000000..7664d5c --- /dev/null +++ b/services/orchestrator/src/react_agent/__init__.py @@ -0,0 +1,14 @@ +"""React Agent. + +This module defines a custom reasoning and action agent graph. +It invokes tools in a simple loop. +""" +import os + +from dotenv import find_dotenv, load_dotenv + +load_dotenv(find_dotenv(".env.dev" if os.getenv("DEVELOPMENT") else ".env")) + +from react_agent.graph import graph + +__all__ = ["graph"] diff --git a/services/orchestrator/src/react_agent/constants.py b/services/orchestrator/src/react_agent/constants.py new file mode 100644 index 0000000..d77799d --- /dev/null +++ b/services/orchestrator/src/react_agent/constants.py @@ -0,0 +1,109 @@ +"""Constants and enumerations for the React Agent Orchestrator service.""" +import os +from enum import Enum + +from daytona import Image + + +class AvailableModel(str, Enum): + """Available models from Ollama and EINFRA for UI dropdown selection.""" + + # # Local Models + # LOCAL_OLLAMA_GEMMA3_4B = "ollama/gemma3:4b" + # LOCAL_OLLAMA_SMOLLM2 = "ollama/smollm2" + + # Ollama Models + OLLAMA_GPT_OSS = "ollama/gpt-oss:latest" + OLLAMA_QWEN3_CODER_30B = "ollama/qwen3-coder:30b" + OLLAMA_MISTRAL_SMALL_3_2 = "ollama/mistral-small3.2:latest" + OLLAMA_QWEN3_EMBEDDING = "ollama/qwen3-embedding:latest" + + # EINFRA Models (OpenAI compatible) + EINFRA_MINI = "einfra/mini" + EINFRA_CODER = "einfra/coder" + EINFRA_AGENTIC = "einfra/agentic" + EINFRA_THINKER = "einfra/thinker" + EINFRA_QWEN3_CODER = "einfra/qwen3-coder" + EINFRA_QWEN3_CODER_30B = "einfra/qwen3-coder-30b" + EINFRA_GPT_OSS_120B = "einfra/gpt-oss-120b" + EINFRA_QWEN3_RERANKER_4B = "einfra/qwen3-reranker-4b" + EINFRA_QWEN3_EMBEDDING_4B = "einfra/qwen3-embedding-4b" + EINFRA_LLAMA_4_SCOUT_17B = "einfra/llama-4-scout-17b-16e-instruct" + EINFRA_MXBAI_EMBED_LARGE = "einfra/mxbai-embed-large:latest" + EINFRA_MULTILINGUAL_E5 = "einfra/multilingual-e5-large-instruct" + EINFRA_NOMIC_EMBED_V2 = "einfra/nomic-embed-text-v2-moe" + EINFRA_NOMIC_EMBED_V1_5 = "einfra/nomic-embed-text-v1.5" + EINFRA_DEEPSEEK_V3_2 = "einfra/deepseek-v3.2" + EINFRA_DEEPSEEK_V4_PRO = "einfra/deepseek-v4-pro" + EINFRA_DEEPSEEK_V4_PRO_THINKING = "einfra/deepseek-v4-pro-thinking" + + EINFRA_MISTRAL_LARGE = "einfra/mistral-large" + EINFRA_DEEPSEEK_V3_2_THINKING = "einfra/deepseek-v3.2-thinking" + EINFRA_KIMI_K2_5 = "einfra/kimi-k2.5" + EINFRA_KIMI_K2_6 = "einfra/kimi-k2.6" + EINFRA_QWEN3_5 = "einfra/qwen3.5" + EINFRA_QWEN3_CODER_NEXT = "einfra/qwen3-coder-next" + EINFRA_QWEN3_5_122B = "einfra/qwen3.5-122b" + EINFRA_GLM_4_7 = "einfra/glm-4.7" + EINFRA_GLM_5 = "einfra/glm-5" + EINFRA_GLM_5_1 = "einfra/glm-5.1" + + +class TranslationType(str, Enum): + SCHEMA = "schema" + QUERY = "query" + BOTH = "both" + + +class FrameworkEnum(str, Enum): + """Supported Object-Relational/Graph/Document Mapping targets.""" + + DOTNET_EFCORE = ".NET Entity Framework Core" + DOTNET_DAPPER = ".NET Dapper" + DOTNET_NHIBERNATE = ".NET NHibernate" + JAVA_SPRING_DATA_MONGODB = "Java Spring Data MongoDB" + JAVA_SPRING_DATA_NEO4J = "Java Spring Data Neo4j" + + +class DotnetFramework(str, Enum): + DOTNET_EFCORE = FrameworkEnum.DOTNET_EFCORE.value + DOTNET_DAPPER = FrameworkEnum.DOTNET_DAPPER.value + DOTNET_NHIBERNATE = FrameworkEnum.DOTNET_NHIBERNATE.value + + +class JavaFramework(str, Enum): + JAVA_SPRING_DATA_MONGODB = FrameworkEnum.JAVA_SPRING_DATA_MONGODB.value + JAVA_SPRING_DATA_NEO4J = FrameworkEnum.JAVA_SPRING_DATA_NEO4J.value + + +class SourceFramework(str, Enum): + DOTNET_EFCORE = FrameworkEnum.DOTNET_EFCORE.value + DOTNET_DAPPER = FrameworkEnum.DOTNET_DAPPER.value + DOTNET_NHIBERNATE = FrameworkEnum.DOTNET_NHIBERNATE.value + + +class TargetFramework(str, Enum): + JAVA_SPRING_DATA_MONGODB = FrameworkEnum.JAVA_SPRING_DATA_MONGODB.value + JAVA_SPRING_DATA_NEO4J = FrameworkEnum.JAVA_SPRING_DATA_NEO4J.value + + +FRAMEWORK_TO_NORMALIZED_NAME = { + FrameworkEnum.DOTNET_EFCORE: "dotnet_efcore", + FrameworkEnum.DOTNET_DAPPER: "dotnet_dapper", + FrameworkEnum.DOTNET_NHIBERNATE: "dotnet_nhibernate", + FrameworkEnum.JAVA_SPRING_DATA_MONGODB: "java_spring_data_mongodb", + FrameworkEnum.JAVA_SPRING_DATA_NEO4J: "java_spring_data_neo4j", +} + +NORMALIZED_FRAMEWORK_TO_FRAMEWORK = { + "dotnet_efcore": FrameworkEnum.DOTNET_EFCORE, + "dotnet_dapper": FrameworkEnum.DOTNET_DAPPER, + "dotnet_nhibernate": FrameworkEnum.DOTNET_NHIBERNATE, + "java_spring_data_mongodb": FrameworkEnum.JAVA_SPRING_DATA_MONGODB, + "java_spring_data_neo4j": FrameworkEnum.JAVA_SPRING_DATA_NEO4J, +} + + +class SandboxType(str, Enum): + DOTNET_10_SANDBOX = "dotnet-10-sandbox" + JAVA_25_SANDBOX = "java-25-sandbox" diff --git a/services/orchestrator/src/react_agent/context.py b/services/orchestrator/src/react_agent/context.py new file mode 100644 index 0000000..ab5c3d0 --- /dev/null +++ b/services/orchestrator/src/react_agent/context.py @@ -0,0 +1,232 @@ +"""Define the configurable parameters for the agent.""" + +from __future__ import annotations + +import asyncio +import logging +import os +from dataclasses import dataclass, field, fields +from typing import Annotated + +from daytona import ( + AsyncDaytona, + AsyncSandbox, + CreateSandboxFromImageParams, + Image, + SandboxState, +) + +from react_agent.constants import AvailableModel, SandboxType + +logger = logging.getLogger(__name__) + +@dataclass(kw_only=True) +class Context: + """The context for the agent.""" + + system_prompt: str = field( + default="", + metadata={ + "description": "The system prompt to use for the agent's interactions. " + "This prompt sets the context and behavior for the agent." + }, + ) + + model: Annotated[AvailableModel, {"__template_metadata__": {"kind": "llm"}}] = ( + field( + default=AvailableModel.EINFRA_DEEPSEEK_V4_PRO_THINKING, + metadata={ + "description": "The name of the language model to use for the agent's main translation agent." + }, + ) + ) + + openai_api_url: str = field( + default=os.environ.get("OPENAI_API_URL", "https://llm.ai.e-infra.cz/v1"), + metadata={ + "description": "Base URL for OpenAI-compatible providers (like EINFRA)." + }, + ) + + openai_api_key: str = field( + default=os.environ.get("OPENAI_API_KEY", ""), + metadata={"description": "API Key for the OpenAI-compatible provider."}, + ) + + max_search_results: int = field( + default=10, + metadata={ + "description": "The maximum number of search results to return for each search query." + }, + ) + + db_toolbox_uri: str = field( + default=os.environ.get("DB_TOOLBOX_URI", "http://localhost:5010"), + metadata={"description": "URI of the MCP Toolbox for Databases server."}, + ) + + mongodb_mcp_uri: str = field( + default=os.environ.get("MONGODB_MCP_URI", "http://localhost:3010/mcp"), + metadata={"description": "URI of the MongoDB MCP server."}, + ) + + mongodb_uri: str = field( + default=os.environ.get("MONGODB_URI", "mongodb://localhost:27027"), + metadata={"description": "Connection URI for MongoDB."}, + ) + + mongodb_database: str = field( + default=os.environ.get("MONGODB_DATABASE", "uom"), + metadata={"description": "Name of the MongoDB database to use."}, + ) + + dotnet_service_uri: str = field( + default=os.environ.get("DOTNET_SERVICE_URI", "http://localhost:5083"), + metadata={"description": "URI of the .NET service."}, + ) + + dotnet_service_ssh_uri: str = field( + default=os.environ.get("DOTNET_SERVICE_SSH_URI", "ssh://localhost:5022"), + metadata={"description": "SSH URI of the .NET service."}, + ) + + java_service_uri: str = field( + default=os.environ.get("JAVA_SERVICE_URI", "http://localhost:8090"), + metadata={"description": "URI of the Java service."}, + ) + + java_service_ssh_uri: str = field( + default=os.environ.get("JAVA_SERVICE_SSH_URI", "ssh://localhost:8022"), + metadata={"description": "SSH URI of the Java service."}, + ) + + # dotnet_sandbox_dockerfile: str = field( + # default=DAYTONA_SANDBOX_IMAGES[SandboxType.DOTNET_10_SANDBOX].dockerfile(), + # metadata={"description": "Dockerfile content for the .NET sandbox environment."}, + # ) + + # java_sandbox_dockerfile: str = field( + # default=DAYTONA_SANDBOX_IMAGES[SandboxType.JAVA_25_SANDBOX].dockerfile(), + # metadata={"description": "Dockerfile content for the Java sandbox environment."}, + # ) + + def __post_init__(self) -> None: + """Fetch env vars for attributes that were not passed as args.""" + for f in fields(self): + if not f.init: + continue + + if getattr(self, f.name) == f.default: + setattr(self, f.name, os.environ.get(f.name.upper(), f.default)) + + +class ValidationSandbox: + SANDBOXES: dict[SandboxType, AsyncSandbox] = {} + + DAYTONA_SANDBOX_IMAGES: dict[SandboxType, Image] = { + SandboxType.DOTNET_10_SANDBOX: Image + .base(os.getenv("DOTNET_SANDBOX_IMAGE", "mcr.microsoft.com/dotnet/sdk:10.0")), + # .workdir("/sandbox") + # .add_local_file(os.path.join(os.getenv("CONTEXT_ABSOLUTE_PATH", ""), "/snippets/efcore-sandbox.csproj") if os.getenv("CONTEXT_ABSOLUTE_PATH") else os.path.join(os.getcwd(), "src/context/snippets/efcore-sandbox.csproj"), "/app/efcore-sandbox.csproj") + # .dockerfile(), + SandboxType.JAVA_25_SANDBOX: Image + .base(os.getenv("JAVA_SANDBOX_IMAGE", "bellsoft/liberica-openjdk-debian:25-cds")) + .run_commands("apt-get update && apt-get install -y --no-install-recommends maven && rm -rf /var/lib/apt/lists/*"), + # .workdir("/sandbox") + # .add_local_file(os.path.join(os.getenv("CONTEXT_ABSOLUTE_PATH", ""), "/snippets/mongo-pom.xml") if os.getenv("CONTEXT_ABSOLUTE_PATH") else os.path.join(os.getcwd(), "src/context/snippets/mongo-pom.xml"), "mongo-pom.xml") + # .dockerfile(), + } + + @staticmethod + async def get_sandbox(daytona: AsyncDaytona, sandbox_type: SandboxType) -> AsyncSandbox: + await ValidationSandbox.initialize_validation_sandbox(daytona, sandbox_type) + return ValidationSandbox.SANDBOXES[sandbox_type] + + @staticmethod + async def initialize_validation_sandbox(daytona: AsyncDaytona, sandbox_type: SandboxType) -> None: + """Initialize a validation sandbox.""" + params = CreateSandboxFromImageParams( + image=ValidationSandbox.DAYTONA_SANDBOX_IMAGES[sandbox_type], + auto_stop_interval=10, # Sandbox will be stopped after 5 minutes + auto_archive_interval=5, # Auto-archive after a Sandbox has been stopped for 5 minutes + name=f"validation-sandbox-{sandbox_type.value.lower()}", + ) + assert params.name is not None + + max_retries = 5 + + for attempt in range(max_retries): + try: + sandbox_instance = None + try: + sandbox_instance = await daytona.get(params.name) + except Exception as e: + logger.info(f"Sandbox '{params.name}' not found or error retrieving: {e}") + + if sandbox_instance is None or sandbox_instance.state == SandboxState.DESTROYED: + logger.info(f"Creating sandbox '{params.name}'...") + sandbox_instance = await daytona.create(params, on_snapshot_create_logs=logger.info, timeout=180) + logger.info(f"Sandbox created with ID: {sandbox_instance.id}") + + state = sandbox_instance.state + logger.info(f"Sandbox '{params.name}' current state: {state}") + + if state in (SandboxState.ERROR, SandboxState.BUILD_FAILED, SandboxState.ARCHIVED): + logger.warning(f"Sandbox '{params.name}' is in state '{state}'. Deleting and recreating...") + try: + await daytona.delete(sandbox_instance) + await asyncio.sleep(5) + except Exception as e: + logger.warning(f"Failed to delete sandbox: {e}") + # Force recreate in next iteration + continue + + if state in (SandboxState.DESTROYING, SandboxState.ARCHIVING): + logger.warning(f"Sandbox '{params.name}' is in '{state}' state. Waiting before recreating...") + await asyncio.sleep(5) + try: + if state in (SandboxState.DESTROYED, SandboxState.ARCHIVED): + await daytona.delete(sandbox_instance) + await asyncio.sleep(5) + except Exception: + pass + continue + + if state in (SandboxState.STOPPED, SandboxState.STOPPING): + if state == SandboxState.STOPPING: + logger.info(f"Sandbox '{params.name}' is stopping. Waiting...") + await sandbox_instance.wait_for_sandbox_stop(timeout=180) + sandbox_instance = await daytona.get(params.name) + + logger.info(f"Starting sandbox '{params.name}'...") + await sandbox_instance.start(timeout=60) + await sandbox_instance.wait_for_sandbox_start(timeout=180) + + elif state in (SandboxState.CREATING, SandboxState.PENDING_BUILD, SandboxState.BUILDING_SNAPSHOT, SandboxState.STARTING, SandboxState.PULLING_SNAPSHOT, SandboxState.RESTORING, SandboxState.RESIZING, SandboxState.SNAPSHOTTING, SandboxState.FORKING, SandboxState.UNKNOWN): + logger.info(f"Sandbox '{params.name}' is in progress state '{state}'. Waiting to start...") + await sandbox_instance.wait_for_sandbox_start(timeout=180) + + elif state == SandboxState.STARTED: + logger.info(f"Sandbox '{params.name}' is already started.") + + # Final check to ensure it's started + sandbox_instance = await daytona.get(params.name) + if sandbox_instance.state == SandboxState.STARTED: + ValidationSandbox.SANDBOXES[sandbox_type] = sandbox_instance + logger.debug("Sandbox details:\n%s", sandbox_instance.model_dump_json(indent=2)) + return + else: + logger.warning(f"Sandbox '{params.name}' failed to reach STARTED state. Current state: {sandbox_instance.state}") + + except Exception as e: + logger.error(f"Error handling sandbox '{params.name}' on attempt {attempt + 1}/{max_retries}: {e}") + if attempt < max_retries - 1: + wait_time = 2 ** attempt + logger.info(f"Retrying in {wait_time} seconds...") + await asyncio.sleep(wait_time) + else: + logger.error(f"Failed to handle sandbox '{params.name}' after {max_retries} attempts.") + logger.warning("Continuing gracefully without the sandbox.") + return + + logger.warning(f"Exhausted retries for sandbox '{params.name}'. Continuing gracefully.") diff --git a/services/orchestrator/src/react_agent/custom_tools/docs_search.py b/services/orchestrator/src/react_agent/custom_tools/docs_search.py new file mode 100644 index 0000000..27632ca --- /dev/null +++ b/services/orchestrator/src/react_agent/custom_tools/docs_search.py @@ -0,0 +1,160 @@ +"""Documentation search tools via MCP servers and fallback fetching. + +Provides tools for fetching framework documentation from: +1. Microsoft Learn MCP (streamable HTTP) +2. Spring Docs MCP (stdio via npx) +3. Fallback: TavilySearch (if TAVILY_API_KEY is set) or basic httpx fetching +""" + +import logging +import os +from contextlib import asynccontextmanager +from typing import Any, AsyncGenerator + +import httpx +from langchain_core.tools import BaseTool, tool +from langchain_mcp_adapters.client import MultiServerMCPClient +from langchain_mcp_adapters.tools import load_mcp_tools +from pydantic import BaseModel, Field + +logger = logging.getLogger(__name__) + + +class DocsSearchInput(BaseModel): + """Input schema for documentation search tools.""" + + query: str = Field(description="The search query for documentation.") + framework_version: str = Field( + default="", + description="Optional framework version to refine the search (e.g., 'EF Core 9', 'Spring Data Neo4j 7.x').", + ) + + +@tool("fetch_web_docs", args_schema=DocsSearchInput) +async def fetch_web_docs(query: str, framework_version: str = "") -> str: + """Fetch documentation from the web for any framework. + + Uses Tavily if TAVILY_API_KEY is available, otherwise falls back to + basic HTTP fetching from official documentation sites. + """ + search_query = f"{query} {framework_version}".strip() + tavily_key = os.getenv("TAVILY_API_KEY") + + if tavily_key: + try: + from langchain_tavily import TavilySearch + + search = TavilySearch(max_results=5) + results = await search.ainvoke(search_query) + return str(results) + except Exception: + logger.warning( + "TavilySearch failed, falling back to HTTP fetch.", exc_info=True + ) + + # Basic HTTP fallback: fetch from official docs sites + urls = _build_fallback_urls(query, framework_version) + results = [] + + async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client: + for url in urls[:3]: + try: + resp = await client.get(url) + if resp.status_code == 200: + # Take a reasonable snippet of the text content + text = resp.text[:5000] + results.append(f"--- {url} ---\n{text}") + except Exception: + logger.debug("Failed to fetch %s", url, exc_info=True) + + if results: + return "\n\n".join(results) + return f"No documentation found for query: {search_query}" + + +def _build_fallback_urls(query: str, framework_version: str) -> list[str]: + """Build fallback URLs for common framework documentation sites.""" + encoded_query = query.replace(" ", "+") + urls = [] + + # Detect framework type from query/version + q_lower = query.lower() + " " + framework_version.lower() + + if any(kw in q_lower for kw in ["spring", "jpa", "mongodb", "neo4j", "java"]): + urls.append(f"https://docs.spring.io/spring-data/search?q={encoded_query}") + if any( + kw in q_lower + for kw in [ + "efcore", + "ef core", + "entity framework", + "dapper", + "nhibernate", + "dotnet", + ".net", + "c#", + ] + ): + urls.append(f"https://learn.microsoft.com/en-us/search/?terms={encoded_query}") + + # Always include a generic search as last resort + urls.append(f"https://learn.microsoft.com/en-us/search/?terms={encoded_query}") + + return urls + + +@asynccontextmanager +async def load_docs_mcp_tools() -> AsyncGenerator[list[BaseTool], None]: + """Load documentation tools from MCP servers. + + Connects to: + 1. Microsoft Learn MCP (streamable HTTP) + 2. Spring Docs MCP (stdio via npx) + + Returns loaded MCP tools. + """ + servers: dict[str, Any] = { + "microsoft_learn": { + "url": "https://learn.microsoft.com/api/mcp", + "transport": "streamable_http", + }, + # "spring_docs": { + # "command": "npx", + # "args": ["@enokdev/springdocs-mcp@latest"], + # "transport": "stdio", + # }, + } + + tools: list[BaseTool] = [] + try: + client = MultiServerMCPClient(servers, tool_name_prefix=True) + async with client.session("microsoft_learn") as docs_mcp_session: + mcp_tools = await load_mcp_tools(docs_mcp_session) + tools.extend(mcp_tools) + logger.info( + "Loaded MCP documentation tools: %s", [tool.name for tool in mcp_tools] + ) + spring_docs_mcp_yielded = False + try: + async with client.session("spring_docs") as spring_docs_session: + spring_tools = await load_mcp_tools(spring_docs_session) + tools.extend(spring_tools) + logger.info( + "Loaded Spring Docs MCP tools: %s", + [tool.name for tool in spring_tools], + ) + yield tools + spring_docs_mcp_yielded = True + except Exception: + logger.warning( + "Failed to load Spring Docs MCP tools.", + exc_info=True, + ) + if not spring_docs_mcp_yielded: + yield tools + except Exception: + logger.warning( + "Failed to load MCP documentation tools. " + "Only fallback fetch_web_docs will be available.", + exc_info=True, + ) diff --git a/services/orchestrator/src/react_agent/custom_tools/dotnet_validator.py b/services/orchestrator/src/react_agent/custom_tools/dotnet_validator.py new file mode 100644 index 0000000..d02b53f --- /dev/null +++ b/services/orchestrator/src/react_agent/custom_tools/dotnet_validator.py @@ -0,0 +1,117 @@ +"""Dotnet compilation and validation tool using execute_in_sandbox.""" +import base64 +import logging +import os +from datetime import datetime +from typing import cast + +from langchain.tools import ToolRuntime +from langchain_core.tools import tool +from pydantic import BaseModel, Field + +from react_agent.constants import ( + DotnetFramework, + FrameworkEnum, + SandboxType, + TranslationType, +) +from react_agent.custom_tools.sandbox_tools import ( + download_file_from_sandbox, + execute_in_sandbox, +) +from react_agent.utils.utils import get_framework_config_content + +logger = logging.getLogger(__name__) + + +class DotnetValidationInput(BaseModel): + source_code: str = Field(description="The C# code to validate.") + framework: DotnetFramework = Field(description="The target .NET framework.") + + +async def compile_and_run_dotnet( + source_code: str, + framework: DotnetFramework, + translation_type: TranslationType, +) -> tuple[str, str | None]: + """Helper to compile and run C# source code, returning (output, json_part).""" + if "class " not in source_code and "record " not in source_code: + return "Compilation Error: No class or record defined in C# source code.", None + + try: + csproj_content = await get_framework_config_content(FrameworkEnum(framework.value)) + except ValueError as e: + return f"[Error] {e}", None + + csproj_b64 = base64.b64encode(csproj_content.encode()).decode() + cs_b64 = base64.b64encode(source_code.encode()).decode() + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + sandbox_dir = f"/sandbox/sandbox-{timestamp}" + results_dir = f"{sandbox_dir}/results" + + script = f""" +export CONNECTION_STRING="{os.getenv('MSSQL_CONNECTION_STRING', 'Server=mssql_db,1433;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True')}" +export EFCORE_RESULTS_PATH="{results_dir}" +export DAPPER_RESULTS_PATH="{results_dir}" +export NHIBERNATE_RESULTS_PATH="{results_dir}" +mkdir -p "{sandbox_dir}" +mkdir -p "{results_dir}" +echo "{csproj_b64}" | base64 -d > "{sandbox_dir}/sandbox.csproj" +echo "{cs_b64}" | base64 -d > "{sandbox_dir}/Program.cs" +cd "{sandbox_dir}" +dotnet build +""" + if source_code.strip(): + script += "dotnet run\n" + if translation_type in [TranslationType.QUERY, TranslationType.BOTH]: + script += f""" +# Output newest json file path +NEWEST_JSON=$(ls -t "{results_dir}"/*.json 2>/dev/null | head -n 1) +if [ -n "$NEWEST_JSON" ]; then + printf "\nJSON_PATH=%s\n" "$NEWEST_JSON" +fi +""" + + try: + result = await execute_in_sandbox.ainvoke( + {"sandbox_type": SandboxType.DOTNET_10_SANDBOX, "command": script} + ) + output: str = result[0] + exit_code: int = result[1] + except Exception as e: + logger.error("[Error] Dotnet sandbox execution failed", exc_info=True) + return f"[Error] Dotnet sandbox execution failed: {e}", None + + json_path_line = next((line for line in reversed(output.splitlines()) if line.startswith("JSON_PATH=")), None) + if exit_code == 0: + if json_path_line is not None: + remote_path = json_path_line.split("=")[1].strip() + json_content = await download_file_from_sandbox.ainvoke( + {"sandbox_type": SandboxType.DOTNET_10_SANDBOX, "remote_path": remote_path} + ) + if not json_content.startswith("[Daytona Error]"): + json_part = json_content + else: + json_part = f"\n===JSON ERROR===\nFailed to fetch JSON from {remote_path}." + + return f"[Dotnet Validation Passed] Validation successful. Framework targeted: {framework.value}\n{output}", json_part + else: + return f"[Dotnet Validation Failed] No JSON path found in output.\n{output}", None + else: + return f"[Dotnet Validation Failed]\n{output}", None + + +@tool("validate_dotnet_code", args_schema=DotnetValidationInput) +async def validate_dotnet_code( + source_code: str, + framework: DotnetFramework, + runtime: ToolRuntime, +) -> str: + """Compile and validate C# source code through dotnet-service CLI.""" + translation_type = cast(TranslationType, runtime.state.get("translation_type")) + output, json_part = await compile_and_run_dotnet(source_code, framework, translation_type) + + if json_part: + return f"{output}\n\n[JSON Results]\n{json_part}" + return output diff --git a/services/orchestrator/src/react_agent/custom_tools/java_validator.py b/services/orchestrator/src/react_agent/custom_tools/java_validator.py new file mode 100644 index 0000000..7dc84d9 --- /dev/null +++ b/services/orchestrator/src/react_agent/custom_tools/java_validator.py @@ -0,0 +1,127 @@ +"""Java compilation and validation tool using execute_in_sandbox.""" +import base64 +import logging +import os +from datetime import datetime +from typing import cast + +from langchain.tools import ToolRuntime +from langchain_core.tools import tool +from pydantic import BaseModel, Field + +from react_agent.constants import ( + FrameworkEnum, + JavaFramework, + SandboxType, + TranslationType, +) +from react_agent.custom_tools.sandbox_tools import ( + download_file_from_sandbox, + execute_in_sandbox, +) +from react_agent.utils.utils import get_framework_config_content + +logger = logging.getLogger(__name__) + + +class JavaValidationInput(BaseModel): + source_code: str = Field( + min_length=1, + description="The Java source code to compile and validate." + ) + framework: JavaFramework = Field( + description="The Java framework.", + ) + entry_type_name: str = Field( + min_length=1, + description="Entrypoint type class name declared in the Java source code.", + ) + + +async def compile_and_run_java( + source_code: str, + framework: JavaFramework, + entry_type_name: str, + translation_type: TranslationType, +) -> tuple[str, str | None]: + """Helper to compile and run Java source code, returning (output, json_part).""" + try: + pom_content = await get_framework_config_content(FrameworkEnum(framework.value)) + except ValueError as e: + return f"[Error] {e}", None + + pom_b64 = base64.b64encode(pom_content.encode()).decode() + java_b64 = base64.b64encode(source_code.encode()).decode() + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") + sandbox_dir = f"/sandbox/sandbox-{timestamp}" + src_dir = f"{sandbox_dir}/src/main/java/uom/services" + results_dir = f"{sandbox_dir}/results" + + script = f""" +export MONGODB_URI="{os.getenv('MONGODB_URI', 'mongodb://uom_readonly:uom_readonly@mongodb:27017/uom')}" +export NEO4J_URI="{os.getenv('NEO4J_URI', 'neo4j://neo4j:7687')}" +export NEO4J_USERNAME="{os.getenv('NEO4J_USERNAME', 'neo4j')}" +export NEO4J_PASSWORD="{os.getenv('NEO4J_PASSWORD', 'password')}" +export MONGO_RESULTS_PATH="{results_dir}" +export NEO4J_RESULTS_PATH="{results_dir}" +mkdir -p "{src_dir}" +mkdir -p "{results_dir}" +echo "{pom_b64}" | base64 -d > "{sandbox_dir}/pom.xml" +echo "{java_b64}" | base64 -d > "{src_dir}/{entry_type_name}.java" +cd "{sandbox_dir}" +mvn -q -B --no-transfer-progress dependency:resolve clean compile +""" + if source_code.strip(): + script += f"mvn -q -B --no-transfer-progress exec:java -Dexec.mainClass=\"uom.services.{entry_type_name}\" -Dexec.classpathScope=compile" + if translation_type in [TranslationType.QUERY, TranslationType.BOTH]: + script += f""" +NEWEST_JSON=$(ls -t "{results_dir}"/*.json 2>/dev/null | head -n 1) +if [ -n "$NEWEST_JSON" ]; then + printf "\nJSON_PATH=%s\n" "$NEWEST_JSON" +fi +""" + + try: + result = await execute_in_sandbox.ainvoke( + {"sandbox_type": SandboxType.JAVA_25_SANDBOX, "command": script} + ) + output: str = result[0] + exit_code: int = result[1] + except Exception as e: + logger.error("[Error] Java sandbox execution failed", exc_info=True) + return f"[Error] Java sandbox execution failed: {e}", None + + json_path_line = next((line for line in reversed(output.splitlines()) if line.startswith("JSON_PATH=")), None) + if exit_code == 0: + if json_path_line is not None: + remote_path = json_path_line.split("=")[1].strip() + json_content = await download_file_from_sandbox.ainvoke( + {"sandbox_type": SandboxType.JAVA_25_SANDBOX, "remote_path": remote_path} + ) + if not json_content.startswith("[Daytona Error]"): + json_part = json_content + else: + json_part = f"\n===JSON ERROR===\nFailed to fetch JSON from {remote_path}." + + return f"[Java Validation Passed] Validation successful. Framework targeted: {framework.value}\n{output}", json_part + else: + return f"[Java Validation Failed] No JSON path found in output.\n{output}", None + else: + return f"[Java Validation Failed]\n{output}", None + + +@tool("validate_java_code", args_schema=JavaValidationInput) +async def validate_java_code( + source_code: str, + framework: JavaFramework, + entry_type_name: str, + runtime: ToolRuntime, +) -> str: + """Compile and validate Java source code through java-service CLI.""" + translation_type = cast(TranslationType, runtime.state.get("translation_type")) + output, json_part = await compile_and_run_java(source_code, framework, entry_type_name, translation_type) + + if json_part: + return f"{output}\n\n[JSON Results]\n{json_part}" + return output diff --git a/services/orchestrator/src/react_agent/custom_tools/mcp_database.py b/services/orchestrator/src/react_agent/custom_tools/mcp_database.py new file mode 100644 index 0000000..56d16d6 --- /dev/null +++ b/services/orchestrator/src/react_agent/custom_tools/mcp_database.py @@ -0,0 +1,90 @@ +"""Database tool functions powered by the googleapis/genai-toolbox MCP. + +Loads tools from the running MCP Toolbox for Databases server, which provides +prebuilt tools for MSSQL and Neo4j, plus custom MongoDB tools defined in +database_tools.yaml. +""" + +import logging +from asyncio import CancelledError +from contextlib import asynccontextmanager +from typing import Any, AsyncGenerator + +from anyio import BrokenResourceError +from langchain_core.tools import BaseTool +from langchain_mcp_adapters.client import MultiServerMCPClient +from langchain_mcp_adapters.tools import load_mcp_tools +from langgraph.runtime import get_runtime +from toolbox_langchain import ToolboxClient + +from react_agent.context import Context + +logger = logging.getLogger(__name__) + + +@asynccontextmanager +async def load_database_tools() -> AsyncGenerator[list[BaseTool], None]: + """Load all database tools from the MCP Toolbox for Databases Server and MongoDB MCP Server. + + Connects to the toolbox and mongodb mcp server and loads every available tool + (prebuilt mssql/neo4j + mongodb tools). + + Returns: + A list of LangChain-compatible tool objects. + Returns fallback tools if the toolbox server is unreachable. + """ + runtime = get_runtime(Context) + toolbox_uri = runtime.context.db_toolbox_uri + mongodb_mcp_uri = runtime.context.mongodb_mcp_uri + + custom_db_mcp_servers: dict[str, Any] = { + "mongodb": { + "transport": "streamable_http", + "url": mongodb_mcp_uri, + } + } + + tools: list[BaseTool] = [] + try: + async with ToolboxClient(toolbox_uri) as toolbox_client: + toolbox_tools = await toolbox_client.aload_toolset() + tools.extend(toolbox_tools) + logger.info( + "Loaded database tools from toolbox at %s: %s", + toolbox_uri, + [tool.name for tool in toolbox_tools], + ) + mcp_client_yielded = False + try: + db_mcp_client = MultiServerMCPClient( + custom_db_mcp_servers, tool_name_prefix=True + ) + async with db_mcp_client.session("mongodb") as db_mcp_session: + db_tools = await load_mcp_tools(db_mcp_session) + tools.extend(db_tools) + logger.info( + "Loaded database tools from mongodb mcp server: %s", + [tool.name for tool in db_tools], + ) + yield tools + mcp_client_yielded = True + except* (BrokenResourceError, CancelledError, RuntimeError, Exception): + if not mcp_client_yielded: + logger.warning( + "Failed to load tools from MongoDB MCP server with URI %s.", + mongodb_mcp_uri, + exc_info=True, + ) + yield tools # Yield toolbox tools even if MCP tools failed + else: + logger.debug( + "Error details for MongoDB MCP server failure with URI %s.", + mongodb_mcp_uri, + exc_info=True, + ) + except Exception: + logger.warning( + "Failed to load database tools from toolbox server at %s.", + toolbox_uri, + exc_info=True, + ) diff --git a/services/orchestrator/src/react_agent/custom_tools/query_validator.py b/services/orchestrator/src/react_agent/custom_tools/query_validator.py new file mode 100644 index 0000000..1a03dac --- /dev/null +++ b/services/orchestrator/src/react_agent/custom_tools/query_validator.py @@ -0,0 +1,308 @@ +"""Query validation and equivalence tools for source and target query execution metadata.""" + +from __future__ import annotations + +import asyncio +import json +import logging +from collections import OrderedDict +from typing import Any, Awaitable, Literal, cast + +import orjson +from deepdiff import DeepDiff +from langchain.messages import HumanMessage, ToolMessage +from langchain.tools import ToolRuntime +from langchain_core.tools import tool +from langgraph.types import Command +from pydantic import BaseModel, Field + +from react_agent.constants import ( + DotnetFramework, + FrameworkEnum, + JavaFramework, + SourceFramework, + TargetFramework, + TranslationType, +) +from react_agent.context import Context +from react_agent.custom_tools.dotnet_validator import ( + compile_and_run_dotnet, + validate_dotnet_code, +) +from react_agent.custom_tools.java_validator import ( + compile_and_run_java, + validate_java_code, +) +from react_agent.state import State +from react_agent.utils.types import QueryEquivalenceDeepDiff + +logger = logging.getLogger(__name__) + + +class SourceQueryInput(BaseModel): + """Input payload for validating source-side schema/queries.""" + + validation_schema_code: str = Field( + min_length=1, + description="Source schema validation code. This should include imports, serialization, runtime config, context/session/config/bootstrap setup, and any other code needed to run the query, but should keep " + "the Schema and Related Settings logic equivalent to the original source_schema_code (without JSON serialization related annotations). Should be fully valid and runnable code with entrypoint. Include simple one-entity fetch queries to validate each entity (see examples). Do not include source query related code here." + ) + validation_harness_code: str = Field( + min_length=1, + description="Source query validation harness code. Must include the query method(s) and a main entrypoint method that executes the queries, extracts `count`, `firstSample`, `lastSample`, potentially additional query information or errors, and writes the output as JSON to the path defined in the environment variable.", + ) + source_framework: SourceFramework = Field( + description="Source framework for translation" + ) + entry_type_name: str = Field( + min_length=1, + description="Entrypoint type name declared in validation_schema_code/validation_harness_code", + ) + entry_method_name: str = Field( + min_length=1, + description="Entrypoint method name declared in validation_schema_code/validation_harness_code", + ) + + +class TargetQueryInput(BaseModel): + """Input payload for validating target-side schema/queries.""" + + validation_schema_code: str = Field( + min_length=1, + description="Target schema validation code. This should include imports, serialization, runtime config, context/session/config/bootstrap setup, and any other code needed to run the query, but should keep " + "the Schema and Related Settings logic equivalent to the original translated_schema_code (without JSON serialization related annotations). Should be fully valid and runnable code with entrypoint. Include simple one-entity fetch queries to validate each entity (see examples). Do not include target query related code here.", + ) + validation_harness_code: str = Field( + min_length=1, + description="Target query validation harness code. Must include the query method(s) and a main entrypoint method that executes the queries, extracts `count`, `firstSample`, `lastSample`, potentially additional query information or errors, and writes the output as JSON to the path defined in the environment variable.", + ) + target_framework: TargetFramework = Field(description="Target framework for translation") + entry_type_name: str = Field( + min_length=1, + description="Entrypoint type name declared in validation_schema_code/validation_harness_code", + ) + entry_method_name: str = Field( + min_length=1, + description="Entrypoint method name declared in validation_schema_code/validation_harness_code", + ) + + +class QueryEquivalenceInput(BaseModel): + """Input payload for source/target query equivalence checks.""" + + source_validation_output: str = Field( + min_length=1, + description=( + "Output of validate_source_query. Preferred format: full tool output string that starts with " + "[Source Query Validation Passed] followed by JSON summary." + ), + ) + target_validation_output: str = Field( + min_length=1, + description=( + "Output of validate_target_query. Preferred format: full tool output string that starts with " + "[Target Query Validation Passed] followed by JSON summary." + ), + ) + + +# @tool("validate_source_query", args_schema=SourceQueryInput) +@tool +async def validate_source_query( + validation_harness_code: str, + runtime: ToolRuntime, +) -> list[Any] | str: + """Validate source query execution metadata.""" + runtime: ToolRuntime[Context, State] = runtime # ty:ignore[invalid-assignment] + src_framework = cast(FrameworkEnum, runtime.state.source_target) + + output, entrypoint_json = "Compilation Failed", "" + translation_type = cast(TranslationType, runtime.state.translation_type) + if src_framework.value in DotnetFramework: + output, entrypoint_json = await compile_and_run_dotnet( + validation_harness_code.strip(), + DotnetFramework(src_framework.value), + translation_type + ) + elif src_framework.value in JavaFramework: + output, entrypoint_json = await compile_and_run_java( + validation_harness_code.strip(), + JavaFramework(src_framework.value), + runtime.state.source_validation_entry_type_name or "ValidationEntryPoint", + translation_type + ) + + if "Compilation Failed" in output: + return f"[Source Query Validation Failed]\\n{output}" + else: + if entrypoint_json and "===JSON ERROR===" not in entrypoint_json: + try: + parsed: dict[str, Any] = orjson.loads(entrypoint_json) + return Command( + update={ + "source_query_validation_results": parsed, + "translation_messages": [ + ToolMessage( + content=f"[Source Query Validation Passed]\\n{output}", + tool_call_id=runtime.tool_call_id + ) + ] + } + ) + except orjson.JSONDecodeError: + return f"[Source Query Validation Failed] Could not parse JSON output.\\n{output}" + return f"[Source Query Validation Failed] No JSON output.\\n{output}" + + + +# @tool("validate_target_query", args_schema=TargetQueryInput) +@tool +async def validate_target_query( + validation_harness_code: str, + runtime: ToolRuntime, +) -> Command | str: + """Validate target query execution metadata via java_validator.""" + runtime: ToolRuntime[Context, State] = runtime # ty:ignore[invalid-assignment] + tgt_framework = cast(FrameworkEnum, runtime.state.destination_target) + + output, entrypoint_json = "Compilation Failed", "" + translation_type = cast(TranslationType, runtime.state.translation_type) + if tgt_framework.value in DotnetFramework: + output, entrypoint_json = await compile_and_run_dotnet( + validation_harness_code.strip(), + DotnetFramework(tgt_framework.value), + translation_type + ) + elif tgt_framework.value in JavaFramework: + output, entrypoint_json = await compile_and_run_java( + validation_harness_code.strip(), + JavaFramework(tgt_framework.value), + runtime.state.target_validation_entry_type_name or "ValidationEntryPoint", + translation_type + ) + + if "Compilation Failed" in output: + return f"[Target Query Validation Failed]\\n{output}" + else: + if entrypoint_json and "===JSON ERROR===" not in entrypoint_json: + try: + parsed: dict[str, Any] = orjson.loads(entrypoint_json) + return Command( + update={ + "target_query_validation_results": parsed, + "translation_messages": [ + ToolMessage( + content=f"[Target Query Validation Passed]\\n{output}", + tool_call_id=runtime.tool_call_id + ) + ] + } + ) + except orjson.JSONDecodeError: + return f"[Target Query Validation Failed] Could not parse JSON output.\\n{output}" + return f"[Target Query Validation Failed] No JSON output.\\n{output}" + + +def _check_validation_markers( + validation_output: str, + passed_marker: str, + failed_marker: str, +) -> str | None: + text = validation_output.strip() + if not text: + return "validation output is empty" + if failed_marker in text: + return f"found failure marker {failed_marker}" + if passed_marker not in text: + logger.warning("Validation output does not contain expected markers. Output: %s", validation_output) + + +# @tool("check_query_equivalence", args_schema=QueryEquivalenceInput) +@tool +async def check_query_equivalence( + source_validation_output: str, + target_validation_output: str, + runtime: ToolRuntime, +) -> str: + """Compare source and target query metadata for logical equivalence.""" + runtime: ToolRuntime[Context, State] = runtime # ty:ignore[invalid-assignment] + output = _check_validation_markers( + source_validation_output, + "[Source Query Validation Passed]", + "[Source Query Validation Failed]", + ) + if output is not None: + return f"[Query Equivalence Failed] Invalid source validation payload: {output}" + + output = _check_validation_markers( + target_validation_output, + "[Target Query Validation Passed]", + "[Target Query Validation Failed]", + ) + if output is not None: + return f"[Query Equivalence Failed] Invalid target validation payload: {output}" + + source_query_validation_results = runtime.state.source_query_validation_results or {} + target_query_validation_results = runtime.state.target_query_validation_results or {} + common_keys = list(set(source_query_validation_results.keys()).intersection(set(target_query_validation_results.keys()))) + common_keys.sort() + + if not common_keys: + return f"[Query Equivalence Results]\\n{json.dumps({'error': 'No matching query keys found between source and target.'})}" + + diff_results = {} + diff_tasks: dict[str, Awaitable[QueryEquivalenceDeepDiff]] = OrderedDict() + for key in common_keys: + source_q = source_query_validation_results[key] + target_q = target_query_validation_results[key] + + if not isinstance(source_q, dict) or not isinstance(target_q, dict): + diff_results[key] = {"error": "Query payload is not an object."} + continue + + src_count = source_q.get("count") + tgt_count = target_q.get("count") + + src_first = source_q.get("firstSample") + tgt_first = target_q.get("firstSample") + src_last = source_q.get("lastSample") + tgt_last = target_q.get("lastSample") + + def compute_diffs(): + count_diff = DeepDiff(src_count, tgt_count) + + diff_first = DeepDiff(src_first, tgt_first, ignore_order=True, report_repetition=True, significant_digits=3, cutoff_intersection_for_pairs=1, cutoff_distance_for_pairs=1, get_deep_distance=True) + diff_last = DeepDiff(src_last, tgt_last, ignore_order=True, report_repetition=True, significant_digits=3, cutoff_intersection_for_pairs=1, cutoff_distance_for_pairs=1, get_deep_distance=True) + + if not count_diff and diff_first.get("deep_distance") == 0 and diff_last.get("deep_distance") == 0: + return {} + + diff_swapped_first = DeepDiff(src_first, tgt_last, ignore_order=True, report_repetition=True, significant_digits=3, cutoff_intersection_for_pairs=1, cutoff_distance_for_pairs=1, get_deep_distance=True) + diff_swapped_last = DeepDiff(src_last, tgt_first, ignore_order=True, report_repetition=True, significant_digits=3, cutoff_intersection_for_pairs=1, cutoff_distance_for_pairs=1, get_deep_distance=True) + + if not count_diff and diff_swapped_first.get("deep_distance") == 0 and diff_swapped_last.get("deep_distance") == 0: + return {} + + sample_diffs = OrderedDict(( + ("deepdiff_mapping", { "old": runtime.state.source_target or "source", "new": runtime.state.destination_target or "target" }), + ("countDiff", count_diff.to_json()), + ("firstSampleDiff", diff_first.to_json()), + ("lastSampleDiff", diff_last.to_json()) + )) + + return sample_diffs + + diff_tasks[key] = asyncio.to_thread(compute_diffs) + + awaited_tasks = await asyncio.gather(*diff_tasks.values(), return_exceptions=True) + for i, key in enumerate(common_keys): + if isinstance(awaited_tasks[i], Exception): + diff_results[key] = {"error": f"Error computing diffs: {awaited_tasks[i]}"} + elif cast(QueryEquivalenceDeepDiff, awaited_tasks[i]).get("error") is not None: + diff_results[key] = {"error": cast(QueryEquivalenceDeepDiff, awaited_tasks[i])["error"]} + elif not awaited_tasks[i]: + diff_results[key] = {"status": "Equivalent"} + else: + diff_results[key] = OrderedDict((("status", "Differences Found"), ("diffs", awaited_tasks[i]))) + + return f"[Query Equivalence Results]\\n{orjson.dumps(diff_results).decode("utf-8")}" diff --git a/services/orchestrator/src/react_agent/custom_tools/sandbox_tools.py b/services/orchestrator/src/react_agent/custom_tools/sandbox_tools.py new file mode 100644 index 0000000..a0f314a --- /dev/null +++ b/services/orchestrator/src/react_agent/custom_tools/sandbox_tools.py @@ -0,0 +1,89 @@ +"""Provides tools for connecting to sandbox Docker containers.""" + +import logging + +from daytona import AsyncDaytona, DaytonaNotFoundError +from langchain_core.tools import tool + +from react_agent.constants import SandboxType +from react_agent.context import ValidationSandbox + +logger = logging.getLogger(__name__) + + +@tool +async def execute_in_sandbox( + sandbox_type: SandboxType, command: str +) -> tuple[str, int]: + """Executes a shell command directly inside the target Daytona sandbox container (e.g. 'dotnet-service' or 'java-service'). + + This is used as a fallback to interact with the environment or compile code manually when the HTTP API is insufficient. + + Args: + sandbox_type: The target service name (e.g., 'dotnet-service' or 'java-service'). + command: The shell command to run. + + Returns: + A tuple containing: + - The standard output and standard error from the executed command, or an error message if it failed. + - The exit code from the command. + """ + try: + async with AsyncDaytona() as daytona: + sandbox = await ValidationSandbox.get_sandbox(daytona, sandbox_type) + logger.info("Executing command in service: %s", sandbox_type) + result = await sandbox.process.exec(command, timeout=300) + output = "" + stdout = getattr(result, "result", None) + if stdout: + output += f"STDOUT:\n{stdout}\n" + logger.info("STDOUT: %s", stdout) + + stderr = getattr(result, "error", getattr(result, "stderr", None)) + if stderr: + output += f"STDERR:\n{stderr}\n" + logger.info("STDERR: %s", stderr) + + exit_code = getattr(result, "exit_code", getattr(result, "exitCode", 0)) + output += f"Process exited with status: {exit_code}" + logger.info("Process exited with status: %s", exit_code) + + if not output: + return "Command executed successfully with no output.", exit_code + return output, exit_code + except DaytonaNotFoundError as e: + logger.exception("Daytona error") + return f"[Daytona Error] {e}", -1 + except Exception as e: + logger.exception("Unexpected error during sandbox command execution") + return f"Error executing sandbox command: {e}", -1 + + +@tool +async def download_file_from_sandbox( + sandbox_type: SandboxType, remote_path: str +) -> str: + """Retrieve the content of a file from a specified service container using Daytona FS. + + Args: + sandbox_type: The target service name (e.g., 'dotnet-service' or 'java-service'). + remote_path: The absolute path to the remote file to read. + + Returns: + The content of the file as a string. + """ + try: + async with AsyncDaytona() as daytona: + sandbox = await ValidationSandbox.get_sandbox(daytona, sandbox_type) + logger.info("Daytona retrieving file: %s from service: %s", remote_path, sandbox_type) + content = await sandbox.fs.download_file(remote_path) + + if isinstance(content, bytes): + return content.decode("utf-8") + + except DaytonaNotFoundError as e: + logger.exception("Daytona fs download error: file not found") + return f"[Daytona Error] File not found: {e}" + except Exception as e: + logger.exception("Unexpected error during file download execution") + return f"Unexpected error: {e}" diff --git a/services/orchestrator/src/react_agent/graph.py b/services/orchestrator/src/react_agent/graph.py new file mode 100644 index 0000000..6c3e549 --- /dev/null +++ b/services/orchestrator/src/react_agent/graph.py @@ -0,0 +1,954 @@ +# pyright: ignore[reportArgumentType] +# ty:ignore[invalid-argument-type] +# ty:ignore[invalid-type-form] + +"""Define the Universal Object Mapping orchestrator graph.""" +import json +import logging +import os +from datetime import UTC, datetime +from typing import Any, Literal, Union, cast + +import logfire +from langchain.agents import create_agent +from langchain.agents.middleware import ( + ClearToolUsesEdit, + ContextEditingMiddleware, + ModelFallbackMiddleware, + ModelRetryMiddleware, + ToolRetryMiddleware, +) +from langchain.agents.structured_output import ProviderStrategy +from langchain.messages import AIMessage +from langchain_core.callbacks import CallbackManager +from langchain_core.globals import set_llm_cache +from langchain_core.messages import HumanMessage +from langchain_core.runnables import RunnableConfig +from langfuse import get_client +from langfuse.langchain import CallbackHandler +from langgraph.cache.memory import InMemoryCache +from langgraph.graph import START, StateGraph +from langgraph.prebuilt import ToolNode +from langgraph.runtime import Runtime +from langgraph.types import CachePolicy, RetryPolicy +from pydantic import BaseModel, Field, model_validator +from pydantic.experimental.missing_sentinel import MISSING + +from react_agent.constants import ( + AvailableModel, + DotnetFramework, + FrameworkEnum, + JavaFramework, + SourceFramework, + TargetFramework, + TranslationType, +) +from react_agent.context import Context +from react_agent.custom_tools.dotnet_validator import validate_dotnet_code +from react_agent.custom_tools.java_validator import validate_java_code +from react_agent.custom_tools.mcp_database import load_database_tools +from react_agent.custom_tools.query_validator import ( + check_query_equivalence, + validate_source_query, + validate_target_query, +) +from react_agent.prompts import ( + SYSTEM_PROMPT_EXTRACTION, + SYSTEM_PROMPT_SCHEMA_INSPECTOR, + build_system_prompt, +) +from react_agent.state import ( + InputState, + OutputState, + State, +) +from react_agent.tools import TOOLS +from react_agent.utils import ( + create_example_for_prompt, + get_database_mapping_json, + get_model, +) +from react_agent.utils.deterministic_checks import ( + _latest_validation_outcome, +) +from react_agent.utils.request_logging import LoggingCallbackHandler +from react_agent.utils.utils import override_pydantic_model_schema + +logger = logging.getLogger(__name__) + + +class ExtractionOutput(BaseModel): + """Structured output for identifying user intent from messages.""" + + source_schema_code: Union[list[str], str, None] = Field( + description="The source schema code. Return as a list of strings (one string per line) to preserve all original newlines and indentation.", + min_length=1, + ) + source_query_code: Union[list[str], str, None] = Field( + description="The source query code. Return as a list of strings (one string per line) to preserve all original newlines and indentation.", + min_length=1, + ) + translation_type: TranslationType = Field( + description="The type of translation to perform.", + ) + source_target: FrameworkEnum = Field( + description="The identified origin framework." + ) + source_target_version: Union[str, None] = Field( + description="The identified origin framework version." + ) + destination_target: FrameworkEnum = Field( + description="The identified target framework." + ) + destination_target_version: Union[str, None] = Field( + description="The identified target framework version." + ) + + @model_validator(mode='after') + def join_lists(self): + if isinstance(self.source_schema_code, list): + self.source_schema_code = "\n".join(self.source_schema_code) + if isinstance(self.source_query_code, list): + self.source_query_code = "\n".join(self.source_query_code) + return self + + +class BaseTranslationOutput(BaseModel): + """Structured output for the translated schema and/or queries.""" + + translated_schema_code: Union[list[str], str] = Field( + min_length=1, + description="The precise translated schema definitions (entities/models) and context/session/config/bootstrap setup with runtime configs. Plain code only. Do not include usage queries. This corresponds to the example code below `--- Schema and Related Settings ---` comment. See examples in target_validation_schema_code description. Return as a list of strings (one string per line)." + ) + translated_query_code: Union[list[str], str] = Field( + min_length=1, + description=( + "The precise translated production queries only. Keep query semantics and method shape equivalent to source query " + "code. Plain code only. Do not include schema definitions, validation harness helpers, or synthetic validator-only " + "parameters unless they already exist in source query code. This corresponds to the example code methods named `QueryX` or `queryX` inside main entry point class or in own classes. See examples in target_validation_harness_code description. Return as a list of strings (one string per line)." + ) + ) + source_validation_schema_code: Union[list[str], str] = Field( + min_length=1, + description="Source schema validation code. This should include imports, serialization, runtime config, context/session/config/bootstrap setup, and any other code needed to run the query, but should keep " + "the Schema and Related Settings logic equivalent to the original source_schema_code (without JSON serialization related annotations). Should be fully valid and runnable code with entrypoint. Include simple one-entity fetch queries to validate each entity (see examples). Do not include source query related code here. See examples. Return as a list of strings (one string per line)." + ) + source_validation_harness_code: Union[list[str], str] = Field( + min_length=1, + description=( + "The full execution harness code for the SOURCE queries, including the source schema, query methods, any necessary helper classes/records, and the main entry point " + "that executes the source queries and writes the resulting JSON to the environment path. See examples. Return as a list of strings (one string per line)." + ) + ) + source_validation_entry_type_name: str = Field( + min_length=1, + description="The name of the main entry point type (e.g., class) in the source validation code. This is needed to run the code and should be declared in the source_validation_schema_code or source_validation_harness_code. Type name should be just the name of the class/type, without namespace or module prefix. Examples: `EFCoreQueryEntrypoint`, `NHibernateQueryEntrypoint`, `DapperQueryEntrypoint`" + ) + target_validation_schema_code: Union[list[str], str] = Field( + min_length=1, + description="Target schema validation code. This should include imports, serialization, runtime config, context/session/config/bootstrap setup, and any other code needed to run the query, but should keep " + "the Schema and Related Settings logic equivalent to the original translated_schema_code (without JSON serialization related annotations). Should be fully valid and runnable code with entrypoint. Include simple one-entity fetch queries to validate each entity (see examples). Do not include target query related code here. Return as a list of strings (one string per line)." + ) + target_validation_harness_code: Union[list[str], str] = Field( + min_length=1, + description=( + "The full execution harness code for the translated TARGET queries, including the translated query methods, any necessary helper classes/records, and the main entry point " + "that executes the target queries and writes the resulting JSON to the environment path. See examples. Return as a list of strings (one string per line)." + ) + ) + target_validation_entry_type_name: str = Field( + min_length=1, + description="The name of the main entry point type (e.g., class) in the target validation code. This is needed to run the code and should be declared in the source_validation_schema_code or target_validation_harness_code. Type name should be just the name of the class/type, without namespace or module prefix. Examples: `MongoQueryEntrypoint`, `Neo4jQueryEntrypoint`" + ) + + @model_validator(mode='after') + def check_entrypoint_names(self): + # First, join any list fields into strings + if isinstance(self.translated_schema_code, list): + self.translated_schema_code = "\n".join(self.translated_schema_code) + if isinstance(self.translated_query_code, list): + self.translated_query_code = "\n".join(self.translated_query_code) + if isinstance(self.source_validation_schema_code, list): + self.source_validation_schema_code = "\n".join(self.source_validation_schema_code) + if isinstance(self.source_validation_harness_code, list): + self.source_validation_harness_code = "\n".join(self.source_validation_harness_code) + if isinstance(self.target_validation_schema_code, list): + self.target_validation_schema_code = "\n".join(self.target_validation_schema_code) + if isinstance(self.target_validation_harness_code, list): + self.target_validation_harness_code = "\n".join(self.target_validation_harness_code) + + errors = [] + if "source_validation_harness_code" in self.__dict__: + if self.source_validation_harness_code is not None and self.source_validation_entry_type_name not in self.source_validation_harness_code: + errors.append(ValueError("source_validation_entry_type_name must be declared in source_validation_harness_code.")) + if "source_validation_schema_code" in self.__dict__: + if self.source_validation_schema_code is not None and self.source_validation_entry_type_name not in self.source_validation_schema_code: + errors.append(ValueError("source_validation_entry_type_name must be declared in source_validation_schema_code.")) + if "target_validation_harness_code" in self.__dict__: + if self.target_validation_harness_code is not None and self.target_validation_entry_type_name not in self.target_validation_harness_code: + errors.append(ValueError("target_validation_entry_type_name must be declared in target_validation_harness_code.")) + if "target_validation_schema_code" in self.__dict__: + if self.target_validation_schema_code is not None and self.target_validation_entry_type_name not in self.target_validation_schema_code: + errors.append(ValueError("target_validation_entry_type_name must be declared in target_validation_schema_code.")) + if errors: + raise ExceptionGroup("Validation entry type name checks failed", errors) + return self + + +async def _create_translation_output_model(state: State) -> type[BaseModel]: + """Dynamically create a Pydantic model for the translation output based on the input model.""" + assert state.source_target is not None and state.destination_target is not None + base_model_fields = BaseTranslationOutput.model_fields + output_schema_overrides = {} + if state.translation_type == TranslationType.SCHEMA: + output_schema_overrides = { + "translated_query_code": { + "annotation": None | MISSING, + "attributes": { + "default": MISSING, + "exclude": True, + } + }, + "source_validation_schema_code": { + "annotation": Union[list[str], str], + "attributes": { + "description": base_model_fields["source_validation_schema_code"].description + + await create_example_for_prompt(state.source_target, True) + if base_model_fields["source_validation_schema_code"].description else None, + } + }, + "source_validation_harness_code": { + "annotation": None | MISSING, + "attributes": { + "default": MISSING, + "exclude": True, + } + }, + "target_validation_schema_code": { + "annotation": Union[list[str], str], + "attributes": { + "description": base_model_fields["target_validation_schema_code"].description + + await create_example_for_prompt(state.destination_target, True) + if base_model_fields["target_validation_schema_code"].description else None, + } + }, + "target_validation_harness_code": { + "annotation": None | MISSING, + "attributes": { + "default": MISSING, + "exclude": True, + } + } + } + elif state.translation_type == TranslationType.QUERY or state.translation_type == TranslationType.BOTH: + output_schema_overrides = { + "source_validation_schema_code": { + "annotation": None | MISSING, + "attributes": { + "default": MISSING, + "exclude": True, + } + }, + "source_validation_harness_code": { + "annotation": Union[list[str], str], + "attributes": { + "description": base_model_fields["source_validation_harness_code"].description + + await create_example_for_prompt(state.source_target, False) + if base_model_fields["source_validation_harness_code"].description else None, + } + }, + "target_validation_schema_code": { + "annotation": None | MISSING, + "attributes": { + "default": MISSING, + "exclude": True, + } + }, + "target_validation_harness_code": { + "annotation": Union[list[str], str], + "attributes": { + "description": base_model_fields["target_validation_harness_code"].description + + await create_example_for_prompt(state.destination_target, False) + if base_model_fields["target_validation_harness_code"].description else None, + } + } + } + return override_pydantic_model_schema(BaseTranslationOutput, output_schema_overrides, model_name="TranslationOutput") + + +def is_input_extracted(state: State) -> bool: + """Check if the input has been extracted.""" + if state.translation_type == TranslationType.SCHEMA: + is_code_extracted = state.source_schema_code is not None + elif state.translation_type == TranslationType.QUERY: + is_code_extracted = state.source_query_code is not None + elif state.translation_type == TranslationType.BOTH: + is_code_extracted = ( + state.source_schema_code is not None and state.source_query_code is not None + ) + else: + return False + return ( + is_code_extracted + and state.source_target is not None + and state.destination_target is not None + ) + + +async def extract_input( + state: State, config: RunnableConfig, runtime: Runtime[Context] +): + """Extract raw source code and targets from recent messages if missing from structured input.""" + if is_input_extracted(state): + return {} + + # model = await get_model(config, runtime, AvailableModel.OLLAMA_QWEN3_CODER_30B) + # structured_llm = model.with_structured_output(ExtractionOutput) + + system_prompt = SYSTEM_PROMPT_EXTRACTION.format( + origin_frameworks=[f.value for f in SourceFramework], + destination_frameworks=[f.value for f in TargetFramework], + ) + + extraction_agent = create_agent( + await get_model(config, runtime, AvailableModel.EINFRA_QWEN3_CODER_30B, temperature=0), + system_prompt=system_prompt, + response_format=ProviderStrategy(ExtractionOutput, strict=True), + middleware=[ + ModelRetryMiddleware(), + ModelFallbackMiddleware( + await get_model(config, runtime, AvailableModel.EINFRA_MINI, temperature=0), + await get_model(config, runtime, AvailableModel.OLLAMA_QWEN3_CODER_30B, temperature=0), + ), + ], + # debug=True if os.getenv("DEVELOPMENT") else False, + ) + already_extracted = { + "source_schema_code": state.source_schema_code, + "source_query_code": state.source_query_code, + "translation_type": state.translation_type, + "source_target": state.source_target, + "source_target_version": state.source_target_version, + "destination_target": state.destination_target, + "destination_target_version": state.destination_target_version, + } + prompt = f"""Analyze the following conversation and extract the source schema/query code, the origin framework, the origin framework version (if available), the destination framework, the destination framework version (if available), and decide if the translation type is schema, query or both: + +Already extracted: +{json.dumps(already_extracted)} + +Conversation: +{state.messages[-1].content}""" + + response = await extraction_agent.ainvoke( + {"messages": [HumanMessage(content=prompt)]} + ) + + if "structured_response" not in response: + logger.warning("Extraction agent did not return structured response.") + return {} + + extraction: ExtractionOutput = response["structured_response"] + return extraction.model_dump(warnings="error", exclude_unset=True) + + +async def schema_inspection( + state: State, config: RunnableConfig, runtime: Runtime[Context] +) -> dict[str, Any]: + """Inspect source and target database schemas using database tools. + + Runs a lightweight ReAct agent with only database tools to examine + the relevant database schemas before translation begins. + """ + # Load database tools asynchronously + async with load_database_tools() as db_tools: + if not db_tools: + logger.warning("No database tools available for schema inspection.") + return { + "schema_context": "No database tools available. Schema inspection skipped." + } + + model = await get_model(config, runtime, AvailableModel.EINFRA_KIMI_K2_6, temperature=0) + + system_prompt = SYSTEM_PROMPT_SCHEMA_INSPECTOR.format( + system_time=datetime.now(tz=UTC).isoformat(), + ) + + database_mapping = await get_database_mapping_json(cast(FrameworkEnum, state.destination_target)) + + agent = create_agent( + model, + tools=db_tools, + system_prompt=system_prompt, + middleware=[ + ModelRetryMiddleware(), + ModelFallbackMiddleware( + await get_model(config, runtime, AvailableModel.EINFRA_DEEPSEEK_V4_PRO_THINKING, temperature=0), + await get_model(config, runtime, AvailableModel.EINFRA_AGENTIC, temperature=0), + await get_model( + config, runtime, AvailableModel.OLLAMA_QWEN3_CODER_30B, temperature=0 + ), + ), + ToolRetryMiddleware(), + # LLMToolSelectorMiddleware( + # model=await get_model( + # config, runtime, AvailableModel.EINFRA_QWEN3_5 + # ), + # ), + ContextEditingMiddleware( + edits=[ + ClearToolUsesEdit( + trigger=100000, + keep=3, + ) + ] + ), + # SummarizationMiddleware(model, trigger=("fraction", 0.8)), + ], + # debug=True if os.getenv("DEVELOPMENT") else False, + ) + + message = f"""Inspect the database schemas relevant to translating code from {cast(FrameworkEnum, state.source_target).value}{f" {state.source_target_version}" if state.source_target_version else ""} to {cast(FrameworkEnum, state.destination_target).value}{f" {state.destination_target_version}" if state.destination_target_version else ""}. + +{f"Mapping from {database_mapping['source']} to {database_mapping['destination']}:\n\n{json.dumps(database_mapping['mapping'])}\n\n" if database_mapping else ""} + +Source code being translated: +{f"\n{state.source_schema_code}\n\n" if state.source_schema_code else ""} +{f"\n{state.source_query_code}\n\n" if state.source_query_code else ""}""" + + try: + response = await agent.ainvoke( + {"messages": [HumanMessage(content=message)]} + ) + # Extract the final assistant response as schema context + schema_summary = ( + response["messages"][-1].content if response["messages"] else "" + ) + return {"schema_context": str(schema_summary)} + except Exception: + logger.warning("Schema inspection failed.", exc_info=True) + return { + "schema_context": "Schema inspection encountered an error. Proceeding without schema context." + } + + +async def translation_agent( + state: State, config: RunnableConfig, runtime: Runtime[Context] +) -> dict[str, Any]: + """Use a ReAct agent to perform translation and validation loops natively. + + Combines static tools (validators, fallback docs) with dynamically loaded + database and documentation MCP tools. + + !! DEPRECATED + """ + model = await get_model(config, runtime, temperature=0) + + all_tools = TOOLS + + system_prompt = await build_system_prompt(state) + + # Create the ReAct agent + agent = create_agent( + model, + tools=all_tools, + response_format=ProviderStrategy(BaseTranslationOutput, strict=True), + system_prompt=system_prompt, + middleware=[ + ModelRetryMiddleware(), + ModelFallbackMiddleware( + await get_model(config, runtime, AvailableModel.EINFRA_AGENTIC, temperature=0), + await get_model(config, runtime, AvailableModel.OLLAMA_QWEN3_CODER_30B, temperature=0), + ), + ToolRetryMiddleware(), + # LLMToolSelectorMiddleware( + # model=await get_model( + # config, runtime, AvailableModel.EINFRA_QWEN3_5 + # ), + # ), + ContextEditingMiddleware( + edits=[ + ClearToolUsesEdit( + trigger=100000, + keep=3, + ) + ] + ), + # SummarizationMiddleware(model, trigger=("fraction", 0.8)), + # LLMToolEmulator( + # tools=["validate_java_code", "dotnet_validator", "validate_source_query", "validate_target_query", "check_query_equivalence"], + # model=await get_model(config, runtime, AvailableModel.EINFRA_MINI), + # ), + ], + # debug=True if os.getenv("DEVELOPMENT") else False, + ) + + strategies = "\n".join([r.get("strategy", "") for r in state.council_responses]) + + message = f"""Translate the following Source Code ({"schema/query" if cast(TranslationType, state.translation_type).value == TranslationType.BOTH else cast(TranslationType, state.translation_type).value}) from {cast(FrameworkEnum, state.source_target).value}{f" {state.source_target_version}" if state.source_target_version else ""} to {cast(FrameworkEnum, state.destination_target).value}{f" {state.destination_target_version}" if state.destination_target_version else ""}. +{f"\nStrategies to consider:\n{strategies}\n" if strategies else ""}{f"\nDatabase Schema Context:\n{state.schema_context}\n" if state.schema_context else ""}--- +Source Code: +{f"\n{state.source_schema_code.strip()}\n" if state.source_schema_code else ""}{f"\n\n{state.source_query_code.strip()}\n" if state.source_query_code else ""} +""" + + # Invoke the agent. It manages its own messages and tool calls loops. + response = await agent.ainvoke( + { + "messages": [*state.translation_messages] + if len(state.translation_messages) > 0 + else [HumanMessage(content=message)] + } + ) + logger.debug("Translation response: %s", response) + + if "structured_response" not in response: + source_validation = _latest_validation_outcome( + list(response["messages"]), + "[Source Query Validation Passed]", + "[Source Query Validation Failed]", + ) + if ( + state.translation_type in {TranslationType.QUERY, TranslationType.BOTH} + and source_validation == "failed" + ): + return { + "messages": response["messages"], + "translation_messages": response["messages"], + "translated_schema_code": None, + "translated_query_code": None, + "validation_harness_code": None, + } + + logger.warning("No structured response available.") + feedback = HumanMessage( + content=( + "Return a structured_response with translated_schema_code and/or translated_query_code. " + "If translation_type includes query, run validate_source_query, validate_target_query, " + "and check_query_equivalence before finalizing." + ) + ) + updated_messages = [*response["messages"], feedback] + return { + "messages": updated_messages, + "translation_messages": updated_messages, + "translated_schema_code": None, + "translated_query_code": None, + } + + # Extract structured output if available + output = cast(BaseTranslationOutput, response["structured_response"]) + updates: dict[str, Any] = { + "messages": response["messages"], + "translation_messages": response["messages"], + } + updates.update(output.model_dump(warnings="error", exclude_unset=True)) + + return updates + + +async def generate_translation_node( + state: State, config: RunnableConfig, runtime: Runtime[Context] +) -> dict[str, Any]: + """Deterministically generate the translation using structured LLM output via a React Agent without tools.""" + TranslationOutput = await _create_translation_output_model(state) + + model = await get_model(config, runtime, temperature=0) + + system_prompt = await build_system_prompt(state) + + message = f"""Translate the following Source Code ({"schema/query" if state.translation_type and state.translation_type.value == TranslationType.BOTH else (state.translation_type.value if state.translation_type else "schema")}) from {state.source_target.value if state.source_target else "Unknown"}{f" {state.source_target_version}" if state.source_target_version else ""} to {state.destination_target.value if state.destination_target else "Unknown"}{f" {state.destination_target_version}" if state.destination_target_version else ""}. +{f"\nDatabase Schema Context:\n{state.schema_context}\n" if state.schema_context else ""}--- +Source Code: +{f"\n{state.source_schema_code}\n" if state.source_schema_code else ""}{f"\n\n{state.source_query_code}\n" if state.source_query_code else ""} +""" + + agent = create_agent( + model, + response_format=ProviderStrategy(TranslationOutput, strict=True), + system_prompt=system_prompt, + middleware=[ + ModelRetryMiddleware(), + ModelFallbackMiddleware( + await get_model(config, runtime, AvailableModel.EINFRA_KIMI_K2_6, temperature=0), + await get_model(config, runtime, AvailableModel.EINFRA_AGENTIC, temperature=0), + await get_model(config, runtime, AvailableModel.OLLAMA_QWEN3_CODER_30B, temperature=0), + ), + ToolRetryMiddleware(), + ], + ) + + # Invoke the agent + response = await agent.ainvoke( + { + "messages": [*state.translation_messages] + if len(state.translation_messages) > 0 + else [HumanMessage(content=message)] + } + ) + + updates: dict[str, Any] = { + "translation_loop_count": state.translation_loop_count + 1, + } + + if "structured_response" not in response: + logger.warning("LLM did not return TranslationOutput properly.") + return updates + + output = response["structured_response"] + updates.update(output.model_dump(warnings="error", exclude_unset=True)) + + updates["translation_messages"] = AIMessage( + content="Generated translation. Commencing deterministic validation..." + ) + + return updates + + +async def human_intervention_node( + state: State, config: RunnableConfig, runtime: Runtime[Context] +) -> dict[str, Any]: + """A dummy node that acts as a pausing point when loop threshold is reached.""" + return {} + + +def prep_schema_validation(state: State) -> dict[str, Any]: + """Inject ToolCalls into state for schema validation.""" + tool_calls = [] + target = state.destination_target + if target and target.value in DotnetFramework: + tool_calls.append({ + "name": "validate_dotnet_code", + "args": { + "source_code": state.target_validation_harness_code.strip() if state.target_validation_harness_code else "", + "framework": target.value, + }, + "id": "schema_val_1", + "type": "tool_call" + }) + elif target and target.value in JavaFramework: + tool_calls.append({ + "name": "validate_java_code", + "args": { + "source_code": state.target_validation_harness_code.strip() if state.target_validation_harness_code else "", + "framework": target.value, + "entry_type_name": state.target_validation_entry_type_name or "ValidationEntryPoint", + }, + "id": "schema_val_1", + "type": "tool_call" + }) + return {"translation_messages": [AIMessage(content="", tool_calls=tool_calls)]} + + +def prep_query_validation(state: State) -> dict[str, Any]: + """Inject ToolCalls into state for parallel query validation.""" + tool_calls = [ + { + "name": "validate_source_query", + "args": {"validation_harness_code": state.source_validation_harness_code or ""}, + "id": "query_val_1", + "type": "tool_call" + }, + { + "name": "validate_target_query", + "args": {"validation_harness_code": state.target_validation_harness_code or ""}, + "id": "query_val_2", + "type": "tool_call" + } + ] + return {"translation_messages": [AIMessage(content="", tool_calls=tool_calls)]} + + +def prep_query_equivalence(state: State) -> dict[str, Any]: + """Inject ToolCall for check_query_equivalence if both validations passed.""" + last_msgs = state.translation_messages[-2:] if len(state.translation_messages) >= 2 else [] + src_str = str(last_msgs[0].content) if len(last_msgs) >= 2 else "" + tgt_str = str(last_msgs[1].content) if len(last_msgs) >= 2 else "" + + tool_calls = [ + { + "name": "check_query_equivalence", + "args": { + "source_validation_output": src_str, + "target_validation_output": tgt_str, + }, + "id": "query_equiv_1", + "type": "tool_call" + } + ] + return {"translation_messages": [AIMessage(content="", tool_calls=tool_calls)]} + + +validate_schema_node = ToolNode( + [validate_dotnet_code, validate_java_code], name="validate_schema_node", messages_key="translation_messages" +) + +validate_query_node = ToolNode( + [validate_source_query, validate_target_query], name="validate_query_node", messages_key="translation_messages" +) + +check_query_equivalence_node = ToolNode( + [check_query_equivalence], name="check_query_equivalence_node", messages_key="translation_messages" +) + + +def route_post_query_validation(state: State) -> Literal["prep_query_equivalence", "evaluation_node"]: + """Route to equivalence check if validations passed, else skip to evaluation.""" + last_msgs = state.translation_messages[-2:] if len(state.translation_messages) >= 2 else [] + if len(last_msgs) >= 2 and state.translation_type in [TranslationType.QUERY, TranslationType.BOTH]: + src_str = str(last_msgs[0].content) + tgt_str = str(last_msgs[1].content) + if "[Source Query Validation Passed]" in src_str and "[Target Query Validation Passed]" in tgt_str: + return "prep_query_equivalence" + return "evaluation_node" + + +class EvaluationOutput(BaseModel): + decision: Literal["ACCEPT", "REJECT"] = Field( + description="Decision whether to accept or reject the translation." + ) + explanation: str = Field(description="Explanation for the decision.", min_length=1) + + +async def evaluation_node( + state: State, config: RunnableConfig, runtime: Runtime[Context] +) -> dict[str, Any]: + """Evaluate validation outputs and deepdiff results to decide on translation acceptance.""" + model = await get_model(config, runtime, AvailableModel.EINFRA_KIMI_K2_6) + + last_msgs_str = [ + msg.content if isinstance(msg.content, str) else str(msg.content) + for msg in state.translation_messages[-4:] + ] + + prompt = f"""Evaluate the following validation results for a schema/query translation. +Based on the validation output and DeepDiff equivalence results, decide if the translation is ACCEPTABLE or if it should be REJECTED and retried. + +Validation Results: +{chr(10).join(last_msgs_str)} +Is the translation logically equivalent and syntactically valid? Provide your reasoning and output ACCEPT or REJECT. +""" + agent = create_agent( + model, + response_format=ProviderStrategy(EvaluationOutput, strict=True), + middleware=[ + ModelRetryMiddleware(), + ModelFallbackMiddleware( + await get_model(config, runtime, AvailableModel.EINFRA_THINKER), + await get_model(config, runtime, AvailableModel.OLLAMA_QWEN3_CODER_30B), + ), + ToolRetryMiddleware(), + ], + ) + + try: + response = await agent.ainvoke({"messages": [HumanMessage(content=prompt)]}) + if "structured_response" not in response: + return { + "translation_messages": [ + HumanMessage( + content="[Evaluation Failed] Could not parse LLM evaluation decision." + ) + ] + } + + output = response["structured_response"] + + return { + "explanation_message": output.explanation, + "translation_messages": [HumanMessage(content=f"[{output.decision}] {output.explanation}")] + } + except Exception as e: + logger.error(f"Evaluation node failed: {e}") + return { + "translation_messages": [HumanMessage(content=f"[Evaluation Error] {e}")] + } + + +def route_post_evaluation( + state: State, +) -> Literal["generate_translation_node", "human_intervention_node", "__end__"]: + """Route from evaluation to the next node.""" + last_msg = ( + state.translation_messages[-1].content if state.translation_messages else "" + ) + if ( + "[REJECT]" in last_msg + or "[Evaluation Failed]" in last_msg + or "[Evaluation Error]" in last_msg + ): + if state.translation_loop_count >= 3: + return "human_intervention_node" + return "generate_translation_node" + return "__end__" + + +def should_extract_input(state: State) -> Literal["schema_inspection", "extract_input"]: + """Route execution to extraction until mandatory input fields are present.""" + if is_input_extracted(state): + return "schema_inspection" + else: + return "extract_input" + + +def route_post_translation( + state: State, +) -> Literal["prep_schema_validation", "prep_query_validation"]: + """Route from translation generator to the first applicable validation prep node.""" + if state.translation_type == TranslationType.SCHEMA: + return "prep_schema_validation" + return "prep_query_validation" + + +def route_post_schema_validation( + state: State, +) -> Literal[ + "prep_query_validation", + "generate_translation_node", + "human_intervention_node", + "__end__", +]: + """Route from validate_schema to validate_query prep or handle validation failure.""" + last_msg = ( + state.translation_messages[-1].content if state.translation_messages else "" + ) + if "Failed]" in last_msg: + if state.translation_loop_count >= 3: + return "human_intervention_node" + return "generate_translation_node" + + if state.translation_type == TranslationType.BOTH: + return "prep_query_validation" + return "__end__" + + +# Observability +langfuse = get_client() + +# Verify connection +if langfuse.auth_check(): + logger.info("Langfuse client is authenticated and ready!") +else: + logger.error( + "Langfuse authentication failed. Please check your credentials and host." + ) + +# Initialize Langfuse CallbackHandler for Langchain (tracing) +langfuse_handler = CallbackHandler() + +logfire.configure( + # sampling=logfire.SamplingOptions.level_or_duration(background_rate=0.3), + console=False, + scrubbing=False, +) +# logfire.install_auto_tracing(modules=['react_agent'], min_duration=0.01, check_imported_modules='ignore') +logfire.instrument_openai(suppress_other_instrumentation=False) +logfire.instrument_requests(capture_all=True) +logfire.instrument_httpx(capture_all=True) +logfire.instrument_aiohttp_client(capture_all=True) +logging.basicConfig(handlers=[logfire.LogfireLoggingHandler()]) + +# Build the graph + +# checkpointer = InMemorySaver() +# store = InMemoryStore() +cache = InMemoryCache() +if not os.getenv("DEVELOPMENT"): + from langchain_redis import RedisCache + # from langchain_community.cache import AsyncRedisCache as NodeRedisCache + # from redis.asyncio import Redis + + # redis_client = Redis.from_url(os.getenv("REDIS_URI", "redis://localhost:6379/1")) + + # # Node Cache for caching graph states (not LLM calls) + # cache = NodeRedisCache(redis_=redis_client) + + # Global LLM Cache for caching LLM calls + redis_cache = RedisCache( + redis_url=os.getenv("REDIS_URI", "redis://localhost:6379").rstrip("/") + "/1", + prefix="llm_cache", + # redis_client=redis_client + ) + set_llm_cache(redis_cache) + +builder = StateGraph( + State, + input_schema=InputState, + output_schema=OutputState, + context_schema=Context, +) + +builder.add_node( + extract_input, + cache_policy=CachePolicy(), + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + schema_inspection, + cache_policy=CachePolicy(ttl=900), + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + generate_translation_node, + cache_policy=CachePolicy(), + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + human_intervention_node, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) + +builder.add_node( + prep_schema_validation, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + prep_query_validation, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + validate_schema_node, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + validate_query_node, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + prep_query_equivalence, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + check_query_equivalence_node, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) +builder.add_node( + evaluation_node, + retry_policy=RetryPolicy(max_attempts=3), # pyright: ignore[reportArgumentType] +) + +builder.add_conditional_edges(START, should_extract_input) +builder.add_conditional_edges("extract_input", should_extract_input) +builder.add_edge("schema_inspection", "generate_translation_node") + +builder.add_conditional_edges("generate_translation_node", route_post_translation) +builder.add_edge("prep_schema_validation", "validate_schema_node") +builder.add_conditional_edges("validate_schema_node", route_post_schema_validation) + +builder.add_edge("prep_query_validation", "validate_query_node") +builder.add_conditional_edges("validate_query_node", route_post_query_validation) +builder.add_edge("prep_query_equivalence", "check_query_equivalence_node") +builder.add_edge("check_query_equivalence_node", "evaluation_node") + +builder.add_conditional_edges("evaluation_node", route_post_evaluation) +builder.add_edge("human_intervention_node", "generate_translation_node") + +graph = builder.compile( + name="UOM Orchestrator Workflow", + interrupt_before=["human_intervention_node"], + # checkpointer=checkpointer, + # store=store, + cache=cache, + # debug=True if os.getenv("DEVELOPMENT") else False, +).with_config({"callbacks": CallbackManager([langfuse_handler, LoggingCallbackHandler()])}) + +# logger.info(graph.get_graph().draw_mermaid()) diff --git a/services/orchestrator/src/react_agent/prompts.py b/services/orchestrator/src/react_agent/prompts.py new file mode 100644 index 0000000..7ab885c --- /dev/null +++ b/services/orchestrator/src/react_agent/prompts.py @@ -0,0 +1,1019 @@ +"""Default prompts used by the agent.""" + +from react_agent.state import State +from react_agent.utils.utils import get_framework_config_content, get_snippet_content + +SYSTEM_PROMPT_TRANSLATOR = """You are a Universal Object Mapping architect. Your goal is to aid in translating database schema structures and query logic between diverse languages and frameworks. + +Allowed origin frameworks: {origin_frameworks} +Allowed destination frameworks: {destination_frameworks} + +Core translation contract: +1. Identify whether the user input contains schema code, query code, or both. +2. Translate only what is requested by translation type. +3. Preserve behavior, field intent, and query semantics. +4. Keep translated query methods semantically equivalent to the source query method. Do not introduce synthetic validator parameters (for example sortByField/ascending) unless they already exist in source query code. +5. Keep schema code and query code separated. +6. For QUERY or BOTH translations, produce two separate code artifacts: + - translated_query_code: production query implementation only (see ). + - validation_harness_code: validator-facing harness only. +7. Never embed validation harness helpers inside translated_query_code. +10. Put schema/query validator-only setup in validation_harness_code (e.g. DbContext/session/template/bootstrap config, deterministic ordering inputs by unique id or relevant property, count query/statement wiring). +11. Structured output fields already separate content. Do NOT wrap field values with XML tags. +12. All code should be properly indented, including line breaks, with properly formatted blocks of code without any additional markdown formatting. + +Mandatory validation workflow: +1. Translate schema first. +2. Validate schema using validate_java_code or validate_dotnet_code. +3. For query translations, run tools in this strict order: + [validate_source_query, validate_target_query] in parallel -> check_query_equivalence. +4. If any validation fails, fix code and rerun until all required validations pass. +5. Do not finalize query translations unless all three query validation steps pass. +6. When preparing source-side validation harness input, keep the original source query logic unchanged and place only setup/bootstrap code around it. + +Framework rules: +1. For Java schema classes, avoid public access modifier unless explicitly required. +2. For Spring Data MongoDB queries, use MongoTemplate with Query/Criteria API. +3. For Spring Data Neo4j queries, use Neo4jTemplate and Cypher-DSL (Statement-based), not raw string concatenation. +4. Keep translated query method shape close to source query method shape. Avoid adding extra method parameters unless required by source query. +5. If deterministic ordering/count metadata is needed for validation, place it in validation_harness_code. +6. For Spring Data Neo4j metadata extraction, return Cypher-DSL objects (for example statement, countStatement). + +Source framework harness rules: +1. For EF Core: validation harness returns IQueryable. Entrypoint signature: Build(DbContext context, bool ascending). +2. For Dapper: validation harness returns (string Sql, object? Parameters). Entrypoint signature: Build(bool ascending). The SQL should include ORDER BY with ASC/DESC based on the ascending parameter. +3. For NHibernate: validation harness returns IQuery. Entrypoint signature: Build(ISession session, bool ascending). Use HQL with ORDER BY asc/desc based on the ascending parameter. + +Target framework harness rules: +1. For Spring Data MongoDB: validation harness returns Map containing "query" (Query), "countQuery" (Query), and "collection" (String). Entrypoint signature: build(MongoTemplate mongoTemplate). +2. For Spring Data Neo4j: validation harness returns Map containing "statement" (Statement), "countStatement" (Statement), and "params" (Map). Entrypoint signature: build(Neo4jTemplate neo4jTemplate, String sortByField, boolean ascending). + +Structured input/output examples: + + +source_schema_code: +```csharp +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +[Table(\"Customers\", Schema = \"Sales\")] +public class Customer +{{ + [Key] + public int CustomerID {{ get; set; }} + + public required string CustomerName {{ get; set; }} +}} +``` +source_query_code: null + + + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document(collection = \"customers\") +class Customer {{ + @Id + private String id; + + @Field(\"customerId\") + private Integer customerId; + + @Field(\"customerName\") + private String customerName; +}} + +translated_query_code: null +validation_harness_code: null + + + + + +source_schema_code: +```csharp +[Table(\"OrderLines\", Schema = \"Sales\")] +public class OrderLine +{{ + [Key] + public int OrderLineID {{ get; set; }} + + [ForeignKey(nameof(Order))] + public int OrderID {{ get; set; }} + + public int StockItemID {{ get; set; }} + + public required string Description {{ get; set; }} + + public int PackageTypeID {{ get; set; }} + + public int Quantity {{ get; set; }} + + public decimal? UnitPrice {{ get; set; }} + + public decimal TaxRate {{ get; set; }} + + public int PickedQuantity {{ get; set; }} + + public DateTime? PickingCompletedWhen {{ get; set; }} + + public int LastEditedBy {{ get; set; }} + + public DateTime LastEditedWhen {{ get; set; }} +}} +``` +source_query_code: +```csharp +public List Query1() +{{ + using var context = contextFactory.CreateDbContext(); + + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + + var orderLines = context.OrderLines + .Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to) + .ToList(); + + return orderLines; +}} +``` + + +translated_schema_code: +```java +@Document(collection = \"orderLines\") +class OrderLine {{ + @Id + private String id; + + @Field(\"orderLineId\") + private Integer orderLineId; + + @Field(\"orderId\") + private Integer orderId; + + @Field(\"stockItemId\") + private Integer stockItemId; + + @Field(\"description\") + private String description; + + @Field(\"packageTypeId\") + private Integer packageTypeId; + + @Field(\"quantity\") + private Integer quantity; + + @Field(\"unitPrice\") + private BigDecimal unitPrice; + + @Field(\"taxRate\") + private BigDecimal taxRate; + + @Field(\"pickedQuantity\") + private Integer pickedQuantity; + + @Field(\"pickingCompletedWhen\") + private Date pickingCompletedWhen; + + @Field(\"lastEditedBy\") + private Integer lastEditedBy; + + @Field(\"lastEditedWhen\") + private Date lastEditedWhen; +}} +``` +translated_query_code: +```java +class OrderLineQuery {{ + private final MongoTemplate mongoTemplate; + + OrderLineQuery(MongoTemplate mongoTemplate) {{ + this.mongoTemplate = mongoTemplate; + }} + + List query1() {{ + Date from = new Date(2014, 12, 20); + Date to = new Date(2014, 12, 31); + Query query = Query.query(Criteria.where(\"pickingCompletedWhen\").gte(from).lte(to)); + return mongoTemplate.find(query, OrderLine.class); + }} +}} +``` +validation_harness_code: +```java +class QueryValidationHarness {{ + static Map build(MongoTemplate mongoTemplate) {{ + Date from = new Date(2014, 12, 20); + Date to = new Date(2014, 12, 31); + Query query = Query.query(Criteria.where(\"pickingCompletedWhen\").gte(from).lte(to)); + Query countQuery = Query.of(query).limit(-1).skip(-1); + return Map.of( + \"query\", query, + \"countQuery\", countQuery, + \"collection\", \"orderLines\" + ); + }} +}} +``` + + + +translated_schema_code: +```java +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; + +@Node(\"Person\") +class Person {{ + @Id + private String name; +}} +``` +translated_query_code: +```java +class NeoPersonQuery {{ + private final Neo4jTemplate neo4jTemplate; + + NeoPersonQuery(Neo4jTemplate neo4jTemplate) {{ + this.neo4jTemplate = neo4jTemplate; + }} + + List query1() {{ + var p = Cypher.node(\"Person\").named(\"p\"); + Statement statement = Cypher.match(p) + .returning(p) + .build(); + return neo4jTemplate.findAll(statement, Person.class).toList(); + }} +}} +``` +validation_harness_code: +```java +import java.util.Map; +import org.neo4j.cypherdsl.core.Cypher; +import org.neo4j.cypherdsl.core.Statement; +import org.springframework.data.neo4j.core.Neo4jTemplate; + +class NeoQueryEntrypoint {{ + static Map build(Neo4jTemplate neo4jTemplate, String sortByField, boolean ascending) {{ + var person = Cypher.node(\"Person\").named(\"p\"); + var sortProperty = person.property(sortByField); + Statement statement = Cypher.match(person) + .returning(person) + .orderBy(ascending ? sortProperty.ascending() : sortProperty.descending()) + .limit(Cypher.literalOf(1)) + .build(); + + Statement countStatement = Cypher.match(person) + .returning(Cypher.count(person).as(\"cnt\")) + .build(); + + return Map.of( + \"statement\", statement, + \"countStatement\", countStatement, + \"params\", Map.of() + ); + }} +}} +``` +""" + +SYSTEM_PROMPT_TRANSLATION_NODE = """You are a Universal Object Mapping translator. You generate structured schema and query translations between .NET and Java/Spring Data frameworks. + +Source frameworks: {origin_frameworks} +Target frameworks: {destination_frameworks} + +You produce a structured TranslationOutput with these fields: +- translated_schema_code: target entity/model definitions. +- translated_query_code: target production query implementation only. +- validation_schema_code: target schema code for the validation sandbox (may include bootstrap, DbContext, or similar setup). +- source_validation_schema_code: source schema code for the validation sandbox (may include DbContext/session setup). +- validation_harness_code: target query validation harness (returns metadata map for automated equivalence checking). +- source_validation_harness_code: source query validation harness (wraps original query in a static entrypoint). +- validation_sort_by_field: deterministic sort field for samples (e.g. "OrderLineID" / "orderLineId"). +- validation_entry_type_name: entrypoint class name in the harness (e.g. "QueryValidationHarness" / "QueryEntrypoint"). +- validation_entry_method_name: entrypoint method name (e.g. "build" / "Build"). + +Core rules: +1. Translate only what is requested by translation type (schema, query, or both). +2. Preserve behavior, field intent, and query semantics. +3. Keep translated query method shape close to source query method shape. +4. Keep schema code and query code strictly separated across fields. +5. Never embed validation harness helpers inside translated_query_code. +6. Never embed schema classes inside validation_harness_code. +7. All code should be properly indented with proper line breaks and no markdown formatting. +8. Do NOT wrap field values with XML tags. + +Framework rules: +1. For Java schema classes, avoid public access modifier unless explicitly required. +2. For Spring Data MongoDB queries, use MongoTemplate with Query/Criteria API. +3. For Spring Data Neo4j queries, use Neo4jTemplate and Cypher-DSL (Statement-based), not raw string queries. +4. For NHibernate queries, use HQL via ISession.CreateQuery. +5. For Dapper queries, use raw SQL strings with parameterized queries. + +Source framework harness rules: +1. EF Core: returns IQueryable. Signature: Build(DbContext context, bool ascending). Apply OrderBy/OrderByDescending on the sort field based on ascending. +2. Dapper: returns (string Sql, object? Parameters). Signature: Build(bool ascending). SQL includes ORDER BY with ASC/DESC based on ascending. +3. NHibernate: returns IQuery. Signature: Build(ISession session, bool ascending). HQL includes ORDER BY asc/desc based on ascending. + +Target framework harness rules: +1. Spring Data MongoDB: returns Map with keys "query" (Query), "countQuery" (Query), "collection" (String). Signature: build(MongoTemplate mongoTemplate). +2. Spring Data Neo4j: returns Map with keys "statement" (Statement), "countStatement" (Statement), "params" (Map). Signature: build(Neo4jTemplate neo4jTemplate, String sortByField, boolean ascending). + +--- EXAMPLES --- + + + +source_schema_code: +[Table("Customers", Schema = "Sales")] +public class Customer +{{ + [Key] + public int CustomerID {{ get; set; }} + public required string CustomerName {{ get; set; }} +}} + + +translated_schema_code: +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@Document(collection = "customers") +class Customer {{ + @Id + private String id; + + @Field("customerId") + private Integer customerId; + + @Field("customerName") + private String customerName; +}} + +validation_schema_code: (same as translated_schema_code for schema-only) +source_validation_schema_code: (same as source_schema_code for schema-only) + + + + + +source_schema_code: +[Table("People", Schema = "Application")] +public class Person +{{ + [Key] + public int PersonID {{ get; set; }} + public required string FullName {{ get; set; }} +}} + + +translated_schema_code: +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; + +@Node("Person") +class Person {{ + @Id + private Long id; + + @Property("personId") + private Integer personId; + + @Property("fullName") + private String fullName; +}} + + + + + +source_schema_code: +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{{ + [Key] + public int OrderLineID {{ get; set; }} + public int OrderID {{ get; set; }} + public int StockItemID {{ get; set; }} + public required string Description {{ get; set; }} + public int Quantity {{ get; set; }} + public decimal? UnitPrice {{ get; set; }} + public decimal TaxRate {{ get; set; }} + public DateTime? PickingCompletedWhen {{ get; set; }} + public int LastEditedBy {{ get; set; }} + public DateTime LastEditedWhen {{ get; set; }} +}} + +source_query_code: +public List Query1() +{{ + using var context = contextFactory.CreateDbContext(); + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + var orderLines = context.OrderLines + .Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to) + .ToList(); + return orderLines; +}} + + +translated_schema_code: +import java.math.BigDecimal; +import java.util.Date; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@Document(collection = "orderLines") +class OrderLine {{ + @Id + private String id; + @Field("orderLineId") + private Integer orderLineId; + @Field("orderId") + private Integer orderId; + @Field("stockItemId") + private Integer stockItemId; + @Field("description") + private String description; + @Field("quantity") + private Integer quantity; + @Field("unitPrice") + private BigDecimal unitPrice; + @Field("taxRate") + private BigDecimal taxRate; + @Field("pickingCompletedWhen") + private Date pickingCompletedWhen; + @Field("lastEditedBy") + private Integer lastEditedBy; + @Field("lastEditedWhen") + private Date lastEditedWhen; +}} + +translated_query_code: +import java.util.Date; +import java.util.List; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +class OrderLineQuery {{ + private final MongoTemplate mongoTemplate; + OrderLineQuery(MongoTemplate mongoTemplate) {{ this.mongoTemplate = mongoTemplate; }} + + List query1() {{ + Date from = new Date(2014, 12, 20); + Date to = new Date(2014, 12, 31); + Query query = Query.query(Criteria.where("pickingCompletedWhen").gte(from).lte(to)); + return mongoTemplate.find(query, OrderLine.class); + }} +}} + +source_validation_schema_code: +using System; +using Microsoft.EntityFrameworkCore; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Sandbox; + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{{ + [Key] + public int OrderLineID {{ get; set; }} + public int OrderID {{ get; set; }} + public int StockItemID {{ get; set; }} + public string Description {{ get; set; }} = string.Empty; + public int Quantity {{ get; set; }} + public decimal? UnitPrice {{ get; set; }} + public decimal TaxRate {{ get; set; }} + public DateTime? PickingCompletedWhen {{ get; set; }} + public int LastEditedBy {{ get; set; }} + public DateTime LastEditedWhen {{ get; set; }} +}} + +public class WideWorldImportersContext : DbContext +{{ + public WideWorldImportersContext(DbContextOptions options) : base(options) {{ }} + public DbSet OrderLines => Set(); +}} + +source_validation_harness_code: +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace Sandbox; + +public static class QueryEntrypoint +{{ + public static IQueryable Build(WideWorldImportersContext context, bool ascending) + {{ + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + var query = context.OrderLines + .Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to); + return ascending ? query.OrderBy(ol => ol.OrderLineID) : query.OrderByDescending(ol => ol.OrderLineID); + }} +}} + +validation_schema_code: (same as translated_schema_code) + +validation_harness_code: +import java.util.Date; +import java.util.Map; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +class QueryValidationHarness {{ + static Map build(MongoTemplate mongoTemplate) {{ + Date from = new Date(2014, 12, 20); + Date to = new Date(2014, 12, 31); + Query query = Query.query(Criteria.where("pickingCompletedWhen").gte(from).lte(to)); + Query countQuery = Query.of(query).limit(-1).skip(-1); + return Map.of("query", query, "countQuery", countQuery, "collection", "orderLines"); + }} +}} + +validation_sort_by_field: "OrderLineID" +validation_entry_type_name: "QueryValidationHarness" (target) / "QueryEntrypoint" (source) +validation_entry_method_name: "build" (target) / "Build" (source) + + + + + +source_validation_harness_code: +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace Sandbox; + +public static class QueryEntrypoint +{{ + public static IQueryable Build(AppDbContext context, bool ascending) + {{ + var query = context.People.Where(p => p.FullName != null); + return ascending ? query.OrderBy(p => p.PersonID) : query.OrderByDescending(p => p.PersonID); + }} +}} + +validation_harness_code: +import java.util.Map; +import org.neo4j.cypherdsl.core.Cypher; +import org.neo4j.cypherdsl.core.Statement; +import org.springframework.data.neo4j.core.Neo4jTemplate; + +class QueryValidationHarness {{ + static Map build(Neo4jTemplate neo4jTemplate, String sortByField, boolean ascending) {{ + var person = Cypher.node("Person").named("p"); + var sortProp = person.property(sortByField); + Statement statement = Cypher.match(person) + .where(person.property("fullName").isNotNull()) + .returning(person) + .orderBy(ascending ? sortProp.ascending() : sortProp.descending()) + .limit(Cypher.literalOf(1)) + .build(); + Statement countStatement = Cypher.match(person) + .where(person.property("fullName").isNotNull()) + .returning(Cypher.count(person).as("cnt")) + .build(); + return Map.of("statement", statement, "countStatement", countStatement, "params", Map.of()); + }} +}} + + + + + +source_validation_schema_code: +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Sandbox; + +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{{ + [Key] + public int OrderLineID {{ get; set; }} + public DateTime? PickingCompletedWhen {{ get; set; }} + public string Description {{ get; set; }} = string.Empty; +}} + +source_validation_harness_code: +using System; + +namespace Sandbox; + +public static class QueryEntrypoint +{{ + public static (string Sql, object? Parameters) Build(bool ascending) + {{ + var sql = @"SELECT OrderLineID, PickingCompletedWhen, Description + FROM Sales.OrderLines + WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To + ORDER BY OrderLineID " + (ascending ? "ASC" : "DESC"); + var parameters = new {{ From = new DateTime(2014, 12, 20), To = new DateTime(2014, 12, 31) }}; + return (sql, parameters); + }} +}} + + + + + +source_validation_schema_code: +using System; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace Sandbox; + +public class OrderLine +{{ + public virtual int OrderLineID {{ get; set; }} + public virtual DateTime? PickingCompletedWhen {{ get; set; }} + public virtual string Description {{ get; set; }} = string.Empty; +}} + +public class OrderLineMap : ClassMapping +{{ + public OrderLineMap() + {{ + Schema("Sales"); + Table("OrderLines"); + Id(x => x.OrderLineID, m => m.Column("OrderLineID")); + Property(x => x.PickingCompletedWhen); + Property(x => x.Description); + }} +}} + +source_validation_harness_code: +using System; +using NHibernate; + +namespace Sandbox; + +public static class QueryEntrypoint +{{ + public static IQuery Build(ISession session, bool ascending) + {{ + var hql = "FROM OrderLine ol WHERE ol.PickingCompletedWhen >= :from AND ol.PickingCompletedWhen <= :to ORDER BY ol.OrderLineID " + (ascending ? "asc" : "desc"); + return session.CreateQuery(hql) + .SetParameter("from", new DateTime(2014, 12, 20)) + .SetParameter("to", new DateTime(2014, 12, 31)); + }} +}} + +""" + +SYSTEM_PROMPT_EXTRACTION = """You are an information extractor. Your goal is to extract source schema code, source query code, origin framework/version, destination framework/version, and translation type from the user's messages. + +Allowed origin frameworks: {origin_frameworks} +Allowed destination frameworks: {destination_frameworks} + +Extraction rules: +1. You must identify the origin framework and the destination framework from the user's messages. +2. You must identify IF the code is a schema (entities/models+context/sessions/configs, etc.) or a query for the given origin framework, or both. +3. If some data has already been extracted, you must use it as is and only extract the missing data. +4. Output specific structured outputs exactly as requested. Do not provide markdown wrapping if native tools capture the output natively. +5. Keep source_schema_code and source_query_code as raw code snippets when available. +6. Preserve the original formatting (including indentation and line breaks) of the extracted code snippets.""" + +SYSTEM_PROMPT_SCHEMA_INSPECTOR = """You are a database schema inspector. Your goal is to examine source and target database schemas to provide context for code translation. + +You have access to database tools that can: +- List collections/tables/node labels/relationship types in databases +- Inspect schema structures (columns, fields, nodes, relationships/edges) +- Sample documents/rows/nodes/edges to understand data shapes + +Your task: +1. Inspect the SOURCE database schema relevant to the code being translated. + - For MS SQL: use the prebuilt mssql tools to list tables, describe columns, and sample rows. + - For MongoDB: use mongodb tools to list collections, inspect document schemas, and sample documents. + - For Neo4j: use the prebuilt neo4j tools to list node labels, relationship types, and sample nodes/edges. +2. Inspect the TARGET database schema if applicable (e.g., if translating from SQL to MongoDB, inspect what MongoDB collections exist). +3. Return a concise but complete summary of the relevant source and target schemas. + +System time: {system_time}""" + + +async def build_system_prompt(state: State) -> str: + """Dynamically build the system prompt based on the specific translation pair.""" + assert state.source_target is not None and state.destination_target is not None + base_prompt = f"""You are a Universal Object Mapping architect. Your goal is to aid in translating database schema structures and query logic between diverse languages and frameworks. + +Source Framework: {state.source_target.value} +Destination Framework: {state.destination_target.value} + +Core translation contract: +1. Identify whether the user input contains schema code, query code, or both. +2. Translate only what is requested by translation type. +3. Preserve behavior, field intent, and query semantics. +4. Keep translated query methods semantically equivalent to the source query method. Do not introduce synthetic validator parameters (for example sortByField/ascending) unless they already exist in source query code. +5. Keep schema code and query code separated. +6. For QUERY or BOTH translations, produce: + - translated_query_code: production query implementation only. +7. Structured output fields already separate content. Do NOT wrap field values with XML tags. +8. All code should be properly indented, including line breaks, with properly formatted blocks of code without any additional markdown formatting. +9. DO NOT USE COMMENTS OR PLACEHOLDERS IN TRANSLATED CODE. THIS CODE WILL BE EXECUTED. + +Framework rules: +1. For Java schema classes, avoid public access modifier unless explicitly required. +2. For Spring Data MongoDB queries, use MongoTemplate with Query/Criteria API. +3. For Spring Data Neo4j queries, use Neo4jTemplate and Cypher-DSL (Statement-based), not raw string concatenation. +4. Keep translated query method shape close to source query method shape. Avoid adding extra method parameters unless required by source query. + +--- Validation setup configuration --- +Source ({state.source_target.value}) +{await get_framework_config_content(state.source_target)} +Target ({state.destination_target.value}) +{await get_framework_config_content(state.destination_target)} + +--- EXAMPLES (for source_validation_schema_code, source_validation_harness_code, target_validation_schema_code, target_validation_harness_code see below) --- + + + +source_schema_code: +[Table("Customers", Schema = "Sales")] +public class Customer +{{ + [Key] + public int CustomerID {{ get; set; }} + public required string CustomerName {{ get; set; }} +}} + + +translated_schema_code: +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@Document(collection = "customers") +class Customer {{ + @Id + private String id; + + @Field("customerId") + private Integer customerId; + + @Field("customerName") + private String customerName; +}} + +translated_query_code: null + + + + + +source_schema_code: +[Table("People", Schema = "Application")] +public class Person +{{ + [Key] + public int PersonID {{ get; set; }} + public required string FullName {{ get; set; }} +}} + + +translated_schema_code: +import org.springframework.data.neo4j.core.schema.Id; +import org.springframework.data.neo4j.core.schema.Node; +import org.springframework.data.neo4j.core.schema.Property; + +@Node("Person") +class Person {{ + @Id @GeneratedValue + private Long id; + + @Property("personId") + private Integer personId; + + @Property("fullName") + private String fullName; +}} + + + + + +source_schema_code: +[Table("OrderLines", Schema = "Sales")] +public class OrderLine +{{ + [Key] + public int OrderLineID {{ get; set; }} + public int OrderID {{ get; set; }} + public int StockItemID {{ get; set; }} + public required string Description {{ get; set; }} + public int Quantity {{ get; set; }} + public decimal? UnitPrice {{ get; set; }} + public decimal TaxRate {{ get; set; }} + public DateTime? PickingCompletedWhen {{ get; set; }} + public int LastEditedBy {{ get; set; }} + public DateTime LastEditedWhen {{ get; set; }} +}} + +public class WideWorldImportersContext : DbContext +{{ + public WideWorldImportersContext(DbContextOptions options) : base(options) {{ }} + public DbSet OrderLines => Set(); +}} + +source_query_code: +public List Query1() +{{ + using var context = contextFactory.CreateDbContext(); + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + var orderLines = context.OrderLines + .Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to) + .ToList(); + return orderLines; +}} + + +translated_schema_code: +import java.math.BigDecimal; +import java.time.LocalDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@Document(collection = "orderLines") +class OrderLine {{ + @Id + private String id; + @Field("orderLineId") + private Integer orderLineId; + @Field("orderId") + private Integer orderId; + @Field("stockItemId") + private Integer stockItemId; + @Field("description") + private String description; + @Field("quantity") + private Integer quantity; + @Field("unitPrice") + private BigDecimal unitPrice; + @Field("taxRate") + private BigDecimal taxRate; + @Field("pickingCompletedWhen") + private LocalDateTime pickingCompletedWhen; + @Field("lastEditedBy") + private Integer lastEditedBy; + @Field("lastEditedWhen") + private LocalDateTime lastEditedWhen; +}} + +translated_query_code: +import java.time.*; +import java.util.*; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; + +class MongoQueryEntrypoint {{ + private final MongoTemplate mongoTemplate; + MongoQueryEntrypoint(MongoTemplate mongoTemplate) {{ this.mongoTemplate = mongoTemplate; }} + + List query1() {{ + LocalDate from = LocalDate.of(2014, 12, 20); + LocalDate to = LocalDate.of(2014, 12, 31); + Query query = Query.query(Criteria.where("pickingCompletedWhen").gte(from).lte(to)); + return mongoTemplate.find(query, OrderLine.class); + }} +}} + + + + + +source_schema_code: +using System; + +namespace Sandbox; + +public class OrderLine +{{ + public int OrderLineID {{ get; set; }} + public DateTime? PickingCompletedWhen {{ get; set; }} + public string Description {{ get; set; }} = string.Empty; +}} + +source_query_code: +using System; + +namespace Sandbox; + +public static class DapperQueryEntrypoint +{{ + public static IEnumerable Query(SqlConnection conn) + {{ + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + string sql = @"SELECT * FROM Sales.OrderLines WHERE PickingCompletedWhen >= @From AND PickingCompletedWhen <= @To"; + return conn.Query(sql, new {{ From = from, To = to }}); + }} +}} + + + + + +source_schema_code: +using System; +using NHibernate.Mapping.ByCode; +using NHibernate.Mapping.ByCode.Conformist; + +namespace Sandbox; + +public class OrderLine +{{ + public virtual int OrderLineID {{ get; set; }} + public virtual DateTime? PickingCompletedWhen {{ get; set; }} + public virtual string Description {{ get; set; }} = string.Empty; +}} + +public class OrderLineMap : ClassMapping +{{ + public OrderLineMap() + {{ + Table("OrderLines"); Schema("Sales"); + Id(x => x.OrderLineID, m => m.Generator(Generators.Identity)); + Property(x => x.PickingCompletedWhen); + Property(x => x.Description); + }} +}} + +source_query_code: +using System; +using NHibernate; + +namespace Sandbox; + +public static class NHibernateQueryEntrypoint +{{ + public static IQueryable Query1(NHibernate.ISession session) + {{ + var from = new DateTime(2014, 12, 20); + var to = new DateTime(2014, 12, 31); + return session.Query().Where(ol => ol.PickingCompletedWhen >= from && ol.PickingCompletedWhen <= to); + }} +}} + + +""" + +# Mandatory validation workflow: +# 1. Translate schema first. +# 2. Validate schema using validate_java_code or validate_dotnet_code. +# 3. For query translations, run tools in this strict order: +# [validate_source_query, validate_target_query] in parallel -> check_query_equivalence. +# 4. If any validation fails, fix code and rerun until all required validations pass. +# 5. Do not finalize query translations unless all three query validation steps pass. + + snippets = "--- VALIDATION ENTRYPOINT EXAMPLES ---\n" + src_schema = await get_snippet_content(state.source_target, is_schema=True) + src_query = await get_snippet_content(state.source_target, is_schema=False) + snippets += f""" + + +source_validation_schema_code: +```csharp +{src_schema} +``` +source_validation_harness_code: +```csharp +{src_query} +``` + +""" + + tgt_schema = await get_snippet_content(state.destination_target, is_schema=True) + tgt_query = await get_snippet_content(state.destination_target, is_schema=False) + snippets += f""" +target_validation_schema_code: +```java +{tgt_schema} +``` +target_validation_harness_code: +```java +{tgt_query} +``` + + +""" + + return base_prompt + snippets diff --git a/services/orchestrator/src/react_agent/state.py b/services/orchestrator/src/react_agent/state.py new file mode 100644 index 0000000..a12185a --- /dev/null +++ b/services/orchestrator/src/react_agent/state.py @@ -0,0 +1,95 @@ +"""Define the state structures for the agent.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Sequence + +from langchain_core.messages import AnyMessage +from langgraph.graph import add_messages +from langgraph.managed import IsLastStep +from typing_extensions import Annotated + +from react_agent.constants import FrameworkEnum, TranslationType +from react_agent.utils.types import QueryEquivalenceDeepDiff, QueryValidationResults + + +@dataclass +class InputState: + """Defines the input state for the agent, representing a narrower interface to the outside world. + + This class is used to define the initial state and structure of incoming data. + """ + + messages: Annotated[Sequence[AnyMessage], add_messages] = field( + default_factory=list + ) + """ + Messages tracking the primary execution state of the agent. + + Typically accumulates a pattern of: + 1. HumanMessage - user input + 2. AIMessage with .tool_calls - agent picking tool(s) to use to collect information + 3. ToolMessage(s) - the responses (or errors) from the executed tools + 4. AIMessage without .tool_calls - agent responding in unstructured format to the user + 5. HumanMessage - user responds with the next conversational turn + + Steps 2-5 may repeat as needed. + + The `add_messages` annotation ensures that new messages are merged with existing ones, + updating by ID to maintain an "append-only" state unless a message with the same ID is provided. + """ + + # The original source code snippet the user wants translated + source_schema_code: str | None = field(default=None) + source_query_code: str | None = field(default=None) + translation_type: TranslationType | None = field(default=None) + source_target: FrameworkEnum | None = field(default=None) + source_target_version: str | None = field(default=None) + destination_target: FrameworkEnum | None = field(default=None) + destination_target_version: str | None = field(default=None) + + +@dataclass +class OutputState: + """Defines the output state for the graph, representing a narrower interface to the outside world. + + This class is used to define the final state and structure of outgoing data. + """ + + translated_schema_code: str | None = field(default=None) + translated_query_code: str | None = field(default=None) + source_validation_schema_code: str | None = field(default=None) + source_validation_harness_code: str | None = field(default=None) + target_validation_schema_code: str | None = field(default=None) + target_validation_harness_code: str | None = field(default=None) + explanation_message: str | None = field(default=None) + + +@dataclass +class State(InputState, OutputState): + """Represents the complete state of the graph, extending InputState with additional attributes. + + This class can be used to store any information needed throughout the agent's lifecycle. + """ + + is_last_step: IsLastStep = field(default=False) + """ + Indicates whether the current step is the last one before the graph raises an error. + + This is a 'managed' variable, controlled by the state machine rather than user code. + It is set to 'True' when the step count reaches recursion_limit - 1. + """ + + # Core variables + source_validation_entry_type_name: str | None = field(default=None) + target_validation_entry_type_name: str | None = field(default=None) + source_query_validation_results: QueryValidationResults | None = field(default=None) + target_query_validation_results: QueryValidationResults | None = field(default=None) + query_equivalence_deep_diffs: dict[str, QueryEquivalenceDeepDiff] | None = field(default=None) + schema_context: str = field(default="") + council_responses: list[dict] = field(default_factory=list) + translation_messages: Annotated[Sequence[AnyMessage], add_messages] = field( + default_factory=list + ) + translation_loop_count: int = field(default=0) diff --git a/services/orchestrator/src/react_agent/tools.py b/services/orchestrator/src/react_agent/tools.py new file mode 100644 index 0000000..8a2aa83 --- /dev/null +++ b/services/orchestrator/src/react_agent/tools.py @@ -0,0 +1,55 @@ +"""This module provides the tools for the orchestrator agent. + +It defines static tools (validators) and re-exports the async loaders +for database and documentation tools which must be loaded at runtime +in graph nodes. +""" + +from typing import Any, List, Optional, cast + +from langchain_tavily import TavilySearch +from langgraph.runtime import get_runtime + +from react_agent.context import Context +from react_agent.custom_tools.docs_search import load_docs_mcp_tools +from react_agent.custom_tools.dotnet_validator import validate_dotnet_code +from react_agent.custom_tools.java_validator import validate_java_code +from react_agent.custom_tools.mcp_database import load_database_tools +from react_agent.custom_tools.query_validator import ( + check_query_equivalence, + validate_source_query, + validate_target_query, +) + + +async def search(query: str) -> Optional[dict[str, Any]]: + """Search for general web results. + + This function performs a search using the Tavily search engine, which is designed + to provide comprehensive, accurate, and trusted results. It's particularly useful + for answering questions about current events. + """ + runtime = get_runtime(Context) + wrapped = TavilySearch(max_results=runtime.context.max_search_results) + return cast(dict[str, Any], await wrapped.ainvoke({"query": query})) + + +# Static tools available without async initialization. +# Database and documentation tools are loaded dynamically in graph nodes. +TOOLS: List[Any] = [ + # search, + validate_java_code, + validate_dotnet_code, + validate_source_query, + validate_target_query, + check_query_equivalence, + # fetch_web_docs, + # execute_in_sandbox, +] + +__all__ = [ + "TOOLS", + "load_database_tools", + "load_docs_mcp_tools", + "search", +] diff --git a/services/orchestrator/src/react_agent/utils/__init__.py b/services/orchestrator/src/react_agent/utils/__init__.py new file mode 100644 index 0000000..ec0282d --- /dev/null +++ b/services/orchestrator/src/react_agent/utils/__init__.py @@ -0,0 +1,27 @@ +"""Utility helpers and preprocessing exports for react_agent.""" + +from react_agent.utils.preprocessing import ( + get_mongodb_standalone_mapping, + get_neo4j_standalone_mapping, +) +from react_agent.utils.utils import ( + create_example_for_prompt, + get_database_mapping_json, + get_message_text, + get_model, + get_normalized_framework_name, + get_snippet_content, + load_chat_model, +) + +__all__ = [ + "get_database_mapping_json", + "get_message_text", + "get_model", + "load_chat_model", + "get_mongodb_standalone_mapping", + "get_neo4j_standalone_mapping", + "get_normalized_framework_name", + "get_snippet_content", + "create_example_for_prompt", +] diff --git a/services/orchestrator/src/react_agent/utils/deterministic_checks.py b/services/orchestrator/src/react_agent/utils/deterministic_checks.py new file mode 100644 index 0000000..c4f8745 --- /dev/null +++ b/services/orchestrator/src/react_agent/utils/deterministic_checks.py @@ -0,0 +1,229 @@ +"""Utilities for deterministic checks on translation outputs and validation states in React Agent.""" + +import logging +from typing import Any, Literal + +from langchain_core.messages import HumanMessage + +from react_agent.constants import FrameworkEnum, TranslationType +from react_agent.state import State +from react_agent.utils import get_message_text + +logger = logging.getLogger(__name__) + + +def _contains_structured_code(code: str | None) -> bool: + """Return True when a generated code payload looks like code.""" + if code is None: + return False + return bool(code.strip()) and "class " in code + + +def _contains_validation_harness_payload(code: str | None) -> bool: + """Detect whether query output still contains validation harness artifacts.""" + if code is None: + return False + + lower_code = code.lower() + tokens = ( + "validation_harness", + "queryvalidationharness", + "validationharness", + ) + return any(token in lower_code for token in tokens) + + +def _message_contains_marker(messages: list[Any], marker: str) -> bool: + """Return whether any message content contains a marker.""" + for message in reversed(messages): + content = "" + try: + content = get_message_text(message) + except Exception: + raw_content = getattr(message, "content", "") + content = str(raw_content) + if marker in content: + return True + return False + + +def _find_unexpected_tokens( + source_code: str | None, + translated_code: str | None, + tokens: tuple[str, ...], +) -> list[str]: + """Return tokens introduced by translated code that are not present in source code.""" + if not (source_code or "").strip(): + return [] + + source = (source_code or "").lower() + translated = (translated_code or "").lower() + return [token for token in tokens if token in translated and token not in source] + + +def _latest_validation_outcome( + messages: list[Any], + passed_marker: str, + failed_marker: str, +) -> Literal["passed", "failed", "missing"]: + """Return latest known validation outcome from message history.""" + for message in reversed(messages): + content = "" + try: + content = get_message_text(message) + except Exception: + raw_content = getattr(message, "content", "") + content = str(raw_content) + + if passed_marker in content: + return "passed" + if failed_marker in content: + return "failed" + + return "missing" + + +def _validate_query_code_structure( + destination_target: FrameworkEnum | None, + translated_query_code: str | None, + source_query_code: str | None, +) -> list[str]: + """Apply deterministic structural checks to translated query code.""" + errors: list[str] = [] + + if not _contains_structured_code(translated_query_code): + errors.append( + "translated_query_code must contain concrete Java/C# class-based code, not prose." + ) + return errors + + assert translated_query_code is not None + lower_code = translated_query_code.lower() + + unexpected_sort_tokens = _find_unexpected_tokens( + source_query_code, + translated_query_code, + ("sortbyfield", "sortdirection", "ascending", "descending"), + ) + if unexpected_sort_tokens: + errors.append( + "translated_query_code introduced synthetic ordering/config inputs not present in source query " + f"({', '.join(unexpected_sort_tokens)}). Keep translated_query_code equivalent to the source query " + "and place deterministic validation setup in validation_harness_code." + ) + + if destination_target == FrameworkEnum.JAVA_SPRING_DATA_MONGODB: + for token in ("mongotemplate", "criteria", "query"): + if token not in lower_code: + errors.append( + "Spring Data MongoDB query must use MongoTemplate with Query/Criteria API." + ) + break + + if destination_target == FrameworkEnum.JAVA_SPRING_DATA_NEO4J: + if "neo4jtemplate" not in lower_code: + errors.append("Spring Data Neo4j query must use Neo4jTemplate.") + + uses_cypher_dsl = any( + token in translated_query_code + for token in ( + "org.neo4j.cypherdsl.core", + "Cypher.", + "Statement", + "Renderer", + ) + ) + if not uses_cypher_dsl: + errors.append( + "Spring Data Neo4j query must use Cypher-DSL syntax (not raw Cypher string composition)." + ) + + return errors + + +def _deterministic_translation_issues( + state: State, + translated_schema_code: str | None, + translated_query_code: str | None, + validation_harness_code: str | None, + messages: list[Any], +) -> tuple[list[str], bool, bool]: + """Validate whether current translation state can safely terminate.""" + issues: list[str] = [] + schema_ok = True + query_ok = True + + if state.translation_type in {TranslationType.SCHEMA, TranslationType.BOTH}: + if not _contains_structured_code(translated_schema_code): + schema_ok = False + issues.append( + "translated_schema_code is missing or not valid class-based code." + ) + + if state.translation_type in {TranslationType.QUERY, TranslationType.BOTH}: + if not _contains_structured_code(validation_harness_code): + query_ok = False + issues.append( + "validation_harness_code must contain class-based validation helper code for query metadata checks." + ) + + if _contains_validation_harness_payload(translated_query_code): + query_ok = False + issues.append( + "translated_query_code must exclude validation harness content. Keep harness in validation_harness_code only." + ) + + query_structure_issues = _validate_query_code_structure( + state.destination_target, + translated_query_code, + state.source_query_code, + ) + if query_structure_issues: + query_ok = False + issues.extend(query_structure_issues) + + source_validation = _latest_validation_outcome( + messages, + "[Source Query Validation Passed]", + "[Source Query Validation Failed]", + ) + if source_validation == "failed": + query_ok = False + issues.append( + "validate_source_query failed. Source query/schema must be fixed before continuing." + ) + elif source_validation != "passed": + query_ok = False + issues.append( + "validate_source_query must pass before query translation can finish." + ) + + if not _message_contains_marker(messages, "[Target Query Validation Passed]"): + query_ok = False + issues.append( + "validate_target_query must pass before query translation can finish." + ) + + if not _message_contains_marker(messages, "[Query Equivalence Passed]"): + query_ok = False + issues.append( + "check_query_equivalence must pass before query translation can finish." + ) + + return issues, schema_ok, query_ok + + +def _deterministic_feedback_message(issues: list[str]) -> HumanMessage: + """Build a deterministic repair instruction for the translation agent.""" + numbered_issues = "\n".join( + f"{idx + 1}. {issue}" for idx, issue in enumerate(issues) + ) + feedback = ( + "Your previous translation attempt cannot be accepted yet.\n" + "Fix all deterministic issues below, then return structured output again:\n" + f"{numbered_issues}\n" + "For query translations, keep translated_query_code and validation_harness_code separate.\n" + "Required tool sequence for query translations:" + " validate_source_query -> validate_target_query -> check_query_equivalence." + ) + return HumanMessage(content=feedback) diff --git a/services/orchestrator/src/react_agent/utils/preprocessing.py b/services/orchestrator/src/react_agent/utils/preprocessing.py new file mode 100644 index 0000000..aa5b71c --- /dev/null +++ b/services/orchestrator/src/react_agent/utils/preprocessing.py @@ -0,0 +1,695 @@ +"""Mapping preprocessing helpers for MongoDB and Neo4j.""" + +from typing import Any, Literal, cast + +from react_agent.constants import FrameworkEnum +from react_agent.utils.utils import get_database_mapping_json + + +def _normalize_text(value: Any) -> str | None: + """Normalize a text value by stripping surrounding whitespace.""" + if not isinstance(value, str): + return None + + normalized = value.strip() + if not normalized: + return None + return normalized + + +def _singularize_name(name: str) -> str: + """Convert a plural identifier to a simple singular form.""" + if name.endswith("ies") and len(name) > 3: + return f"{name[:-3]}y" + if name.endswith("s") and not name.endswith("ss") and len(name) > 1: + return name[:-1] + return name + + +def _format_mongodb_source_schema(table_path_payload: Any) -> str | None: + """Build a source schema string from MongoDB table path metadata.""" + if not isinstance(table_path_payload, dict): + return None + + source_database = _normalize_text(table_path_payload.get("database")) + source_schema = _normalize_text(table_path_payload.get("schema")) + + if source_database and source_schema: + return f"{source_database}.{source_schema}" + return source_schema or source_database + + +def _extract_mongodb_property_mappings(fields_payload: Any) -> list[dict[str, Any]]: + """Extract MongoDB property mappings in standalone format.""" + if not isinstance(fields_payload, dict): + return [] + + fields_payload_dict = cast(dict[str, Any], fields_payload) + + property_mappings: list[dict[str, Any]] = [] + seen_mappings: set[tuple[str, str]] = set() + + for source_field_key, field_payload in sorted( + fields_payload_dict.items(), + key=lambda item: str(item[0]).lower(), + ): + if not isinstance(field_payload, dict): + continue + + field_payload_dict = cast(dict[str, Any], field_payload) + + source = field_payload_dict.get("source") + target = field_payload_dict.get("target") + if not isinstance(source, dict) or not isinstance(target, dict): + continue + if target.get("included") is False: + continue + + source_column = _normalize_text(source.get("name")) or _normalize_text( + source_field_key + ) + target_property = _normalize_text(target.get("name")) + sql_data_type = _normalize_text(source.get("databaseSpecificType")) + mongo_data_type = _normalize_text(target.get("type")) + + if ( + not source_column + or not target_property + or not sql_data_type + or not mongo_data_type + ): + continue + if source_column == "_id" or sql_data_type.upper() == "NONE": + continue + + mapping_key = (source_column.lower(), target_property.lower()) + if mapping_key in seen_mappings: + continue + seen_mappings.add(mapping_key) + + property_mappings.append( + { + "sourceColumn": source_column, + "sqlDataType": sql_data_type.lower(), + "isPrimaryKey": bool(source.get("isPrimaryKey") is True), + "targetProperty": target_property, + "mongoDataType": mongo_data_type.upper(), + } + ) + + property_mappings.sort( + key=lambda item: ( + str(item.get("targetProperty", "")).lower(), + str(item.get("sourceColumn", "")).lower(), + ) + ) + return property_mappings + + +def _extract_mongodb_standalone_mapping( + mapping_payload: Any, +) -> dict[Literal["collections"], dict[str, Any]]: + """Extract MongoDB mapping into standalone collections/mappings structure.""" + empty_mapping: dict[Literal["collections"], dict[str, Any]] = { + "collections": {}, + } + if not isinstance(mapping_payload, dict): + return empty_mapping + + project_payload = mapping_payload.get("project") + if not isinstance(project_payload, dict): + return empty_mapping + + content_payload = project_payload.get("content") + if not isinstance(content_payload, dict): + return empty_mapping + + collections_payload = content_payload.get("collections") + mappings_payload = content_payload.get("mappings") + tables_payload = content_payload.get("tables") + + if ( + not isinstance(collections_payload, dict) + or not isinstance(mappings_payload, dict) + or not isinstance(tables_payload, dict) + ): + return empty_mapping + + collections_mapping: dict[str, list[dict[str, Any]]] = {} + + for mapping_entry in mappings_payload.values(): + if not isinstance(mapping_entry, dict): + continue + + collection_id = _normalize_text(mapping_entry.get("collectionId")) + table_id = _normalize_text(mapping_entry.get("table")) + if not collection_id or not table_id: + continue + + collection_entry = collections_payload.get(collection_id) + table_entry = tables_payload.get(table_id) + if not isinstance(collection_entry, dict) or not isinstance(table_entry, dict): + continue + + collection_name = _normalize_text(collection_entry.get("name")) + table_path = table_entry.get("path") + source_schema = _format_mongodb_source_schema(table_path) + source_table = ( + _normalize_text(table_path.get("table")) + if isinstance(table_path, dict) + else None + ) + + if not collection_name or not source_schema or not source_table: + continue + + settings = mapping_entry.get("settings") + mapping_type = "NEW_DOCUMENT" + embedded_path: str | None = None + + if isinstance(settings, dict): + source_mapping_type = _normalize_text(settings.get("type")) + if source_mapping_type: + mapping_type = source_mapping_type.upper() + embedded_path = _normalize_text(settings.get("embeddedPath")) + + standalone_mapping: dict[str, Any] = { + "mappingType": mapping_type, + "sourceSchema": source_schema, + "sourceTable": source_table, + "propertyMappings": _extract_mongodb_property_mappings( + mapping_entry.get("fields") + ), + } + if embedded_path and mapping_type != "NEW_DOCUMENT": + standalone_mapping["embeddedPath"] = embedded_path + + collections_mapping.setdefault(collection_name, []).append(standalone_mapping) + + sorted_collections: dict[str, dict[str, Any]] = {} + for collection_name in sorted(collections_mapping): + sorted_collections[collection_name] = { + "mappings": sorted( + collections_mapping[collection_name], + key=lambda item: ( + str(item.get("mappingType", "")), + str(item.get("sourceSchema", "")), + str(item.get("sourceTable", "")), + str(item.get("embeddedPath", "")), + ), + ) + } + + return {"collections": sorted_collections} + + +def _extract_column_names_in_order(column_payload: Any) -> list[str]: + """Extract SQL column names while preserving nested traversal order.""" + if not isinstance(column_payload, dict): + return [] + + extracted_names: list[str] = [] + seen_names: set[str] = set() + + def _walk_column(column: Any) -> None: + if not isinstance(column, dict): + return + + column_name = _normalize_text(column.get("name")) + if column_name and column_name not in seen_names: + seen_names.add(column_name) + extracted_names.append(column_name) + + nested_columns = column.get("columns") + if isinstance(nested_columns, list): + for nested_column in nested_columns: + _walk_column(nested_column) + + referenced_column = column.get("referenced-column") + if isinstance(referenced_column, dict): + _walk_column(referenced_column) + + _walk_column(column_payload) + return extracted_names + + +def _extract_table_name(column_payload: Any) -> str | None: + """Extract the first available source table name from a column payload.""" + if not isinstance(column_payload, dict): + return None + + direct_table_name = _normalize_text(column_payload.get("table")) + if direct_table_name: + return direct_table_name + + nested_columns = column_payload.get("columns") + if isinstance(nested_columns, list): + for nested_column in nested_columns: + nested_table_name = _extract_table_name(nested_column) + if nested_table_name: + return nested_table_name + + referenced_column = column_payload.get("referenced-column") + if isinstance(referenced_column, dict): + return _extract_table_name(referenced_column) + + return None + + +def _extract_source_table(mapping_entries: list[Any]) -> str | None: + """Extract the source table name from graph object mapping entries.""" + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + source_table = _extract_table_name(mapping_entry.get("column")) + if source_table: + return source_table + + return None + + +def _extract_primary_key_columns(mapping_entries: list[Any]) -> set[str]: + """Extract normalized primary key source columns for a Neo4j node.""" + primary_key_columns: set[str] = set() + + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != "Id": + continue + + for column_name in _extract_column_names_in_order(mapping_entry.get("column")): + primary_key_columns.add(column_name.lower()) + + return primary_key_columns + + +def _extract_node_id_space(mapping_entries: list[Any]) -> str | None: + """Extract the id-space identifier from a Neo4j node mapping.""" + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != "Id": + continue + + node_id_space = _normalize_text(field.get("id-space")) + if node_id_space: + return node_id_space + + return None + + +def _extract_node_label( + mapping_entries: list[Any], + source_table: str | None, +) -> str | None: + """Extract the target node label from Neo4j node mapping entries.""" + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != "Label": + continue + + label_candidates = _extract_column_names_in_order(mapping_entry.get("column")) + if label_candidates: + return _singularize_name(label_candidates[0]) + + if source_table: + return _singularize_name(source_table) + return None + + +def _collect_node_property_mappings( + mapping_entries: list[Any], + primary_key_columns: set[str], +) -> list[dict[str, Any]]: + """Collect node property mappings in the standalone Neo4j format.""" + property_mappings: list[dict[str, Any]] = [] + seen_mappings: set[tuple[str, str]] = set() + + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != "Data": + continue + + source_columns = _extract_column_names_in_order(mapping_entry.get("column")) + if not source_columns: + continue + + source_column = source_columns[0] + target_property = _normalize_text(field.get("name")) + if not target_property: + continue + + column = mapping_entry.get("column") + if not isinstance(column, dict): + continue + + sql_data_type = _normalize_text(column.get("sql-data-type")) + neo4j_data_type = _normalize_text(field.get("neo4j-data-type")) + if not sql_data_type or not neo4j_data_type: + continue + + mapping_key = (source_column.lower(), target_property.lower()) + if mapping_key in seen_mappings: + continue + seen_mappings.add(mapping_key) + + property_mappings.append( + { + "sourceColumn": source_column, + "sqlDataType": sql_data_type, + "isPrimaryKey": source_column.lower() in primary_key_columns, + "targetProperty": target_property, + "neo4jDataType": neo4j_data_type, + } + ) + + property_mappings.sort( + key=lambda item: ( + str(item.get("targetProperty", "")).lower(), + str(item.get("sourceColumn", "")).lower(), + ) + ) + return property_mappings + + +def _resolve_entity_from_id_space( + id_space: Any, + id_space_to_entity: dict[str, str], +) -> str | None: + """Resolve id-space to a node label for relationship endpoint mappings.""" + normalized_id_space = _normalize_text(id_space) + if not normalized_id_space: + return None + + resolved_entity = id_space_to_entity.get(normalized_id_space.lower()) + if resolved_entity: + return resolved_entity + + fallback_segment = normalized_id_space.rsplit(".", 1)[-1] + fallback_entity = _normalize_text(fallback_segment) + if fallback_entity: + return _singularize_name(fallback_entity) + + return None + + +def _extract_endpoint_mapping( + mapping_entries: list[Any], + endpoint_type: str, + id_space_to_entity: dict[str, str], +) -> dict[Literal["sourceColumn", "targetEntity"], str] | None: + """Extract relationship endpoint mapping for StartId/EndId fields.""" + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != endpoint_type: + continue + + source_columns = _extract_column_names_in_order(mapping_entry.get("column")) + target_entity = _resolve_entity_from_id_space( + field.get("id-space"), + id_space_to_entity, + ) + + if source_columns and target_entity: + return { + "sourceColumn": source_columns[0], + "targetEntity": target_entity, + } + + return None + + +def _extract_relationship_type(mapping_entries: list[Any]) -> str | None: + """Extract Neo4j relationship type from mapping entries.""" + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != "RelationshipType": + continue + + relationship_columns = _extract_column_names_in_order( + mapping_entry.get("column") + ) + if relationship_columns: + return relationship_columns[0].upper() + + return None + + +def _collect_relationship_property_mappings( + mapping_entries: list[Any], +) -> list[dict[str, str]]: + """Collect relationship properties in the standalone Neo4j format.""" + property_mappings: list[dict[str, str]] = [] + seen_mappings: set[tuple[str, str]] = set() + + for mapping_entry in mapping_entries: + if not isinstance(mapping_entry, dict): + continue + + field = mapping_entry.get("field") + if not isinstance(field, dict) or field.get("type") != "Data": + continue + + source_columns = _extract_column_names_in_order(mapping_entry.get("column")) + if not source_columns: + continue + + source_column = source_columns[0] + target_property = _normalize_text(field.get("name")) + if not target_property: + continue + + column = mapping_entry.get("column") + if not isinstance(column, dict): + continue + + sql_data_type = _normalize_text(column.get("sql-data-type")) + neo4j_data_type = _normalize_text(field.get("neo4j-data-type")) + if not sql_data_type or not neo4j_data_type: + continue + + mapping_key = (source_column.lower(), target_property.lower()) + if mapping_key in seen_mappings: + continue + seen_mappings.add(mapping_key) + + property_mappings.append( + { + "sourceColumn": source_column, + "sqlDataType": sql_data_type, + "targetProperty": target_property, + "neo4jDataType": neo4j_data_type, + } + ) + + property_mappings.sort( + key=lambda item: ( + str(item.get("targetProperty", "")).lower(), + str(item.get("sourceColumn", "")).lower(), + ) + ) + return property_mappings + + +def _extract_neo4j_standalone_mapping( + mapping_payload: Any, +) -> dict[Literal["nodes", "relationships"], dict[str, Any]]: + """Extract Neo4j mapping into standalone nodes/relationships structure.""" + empty_mapping: dict[Literal["nodes", "relationships"], dict[str, Any]] = { + "nodes": {}, + "relationships": {}, + } + if not isinstance(mapping_payload, list): + return empty_mapping + + nodes: dict[str, dict[str, Any]] = {} + id_space_to_entity: dict[str, str] = {} + + for graph_object in mapping_payload: + if not isinstance(graph_object, dict): + continue + if graph_object.get("graph-object-type") != "Node": + continue + + mapping_entries = graph_object.get("mappings") + if not isinstance(mapping_entries, list): + continue + + source_schema = _normalize_text(graph_object.get("schema")) + source_table = _extract_source_table(mapping_entries) + node_label = _extract_node_label(mapping_entries, source_table) + + if not source_schema or not source_table or not node_label: + continue + + primary_key_columns = _extract_primary_key_columns(mapping_entries) + property_mappings = _collect_node_property_mappings( + mapping_entries, + primary_key_columns, + ) + + existing_node_mapping = nodes.get(node_label) + if existing_node_mapping is None: + nodes[node_label] = { + "sourceSchema": source_schema, + "sourceTable": source_table, + "propertyMappings": property_mappings, + } + else: + merged_property_mappings = [ + *existing_node_mapping.get("propertyMappings", []), + *property_mappings, + ] + deduped: dict[tuple[str, str], dict[str, Any]] = {} + for property_mapping in merged_property_mappings: + source_column = _normalize_text(property_mapping.get("sourceColumn")) + target_property = _normalize_text( + property_mapping.get("targetProperty") + ) + if not source_column or not target_property: + continue + deduped[(source_column.lower(), target_property.lower())] = ( + property_mapping + ) + + existing_node_mapping["propertyMappings"] = sorted( + deduped.values(), + key=lambda item: ( + str(item.get("targetProperty", "")).lower(), + str(item.get("sourceColumn", "")).lower(), + ), + ) + + node_id_space = _extract_node_id_space(mapping_entries) + if node_id_space: + id_space_to_entity[node_id_space.lower()] = node_label + + relationships: dict[str, list[dict[str, Any]]] = {} + + for graph_object in mapping_payload: + if not isinstance(graph_object, dict): + continue + if graph_object.get("graph-object-type") != "Relationship": + continue + + mapping_entries = graph_object.get("mappings") + if not isinstance(mapping_entries, list): + continue + + relationship_type = _extract_relationship_type(mapping_entries) + source_schema = _normalize_text(graph_object.get("schema")) + source_table = _extract_source_table(mapping_entries) + start_node_mapping = _extract_endpoint_mapping( + mapping_entries, + "StartId", + id_space_to_entity, + ) + end_node_mapping = _extract_endpoint_mapping( + mapping_entries, + "EndId", + id_space_to_entity, + ) + + if ( + not relationship_type + or not source_schema + or not source_table + or not start_node_mapping + or not end_node_mapping + ): + continue + + relationships.setdefault(relationship_type, []).append( + { + "sourceSchema": source_schema, + "sourceTable": source_table, + "startNodeMapping": start_node_mapping, + "endNodeMapping": end_node_mapping, + "propertyMappings": _collect_relationship_property_mappings( + mapping_entries + ), + } + ) + + sorted_nodes = {node_name: nodes[node_name] for node_name in sorted(nodes)} + + sorted_relationships: dict[str, list[dict[str, Any]]] = {} + for relationship_type in sorted(relationships): + sorted_relationships[relationship_type] = sorted( + relationships[relationship_type], + key=lambda item: ( + str(item.get("sourceSchema", "")), + str(item.get("sourceTable", "")), + str(item.get("startNodeMapping", {}).get("sourceColumn", "")), + str(item.get("endNodeMapping", {}).get("sourceColumn", "")), + ), + ) + + return { + "nodes": sorted_nodes, + "relationships": sorted_relationships, + } + + +async def get_neo4j_standalone_mapping() -> ( + dict[Literal["nodes", "relationships"], dict[str, Any]] | None +): + """Load standalone SQL-to-Neo4j mapping grouped into nodes and relationships.""" + raw_database_mapping = await get_database_mapping_json( + FrameworkEnum.JAVA_SPRING_DATA_NEO4J + ) + database_mapping = _extract_neo4j_standalone_mapping( + raw_database_mapping.get("mapping") if raw_database_mapping else None + ) + if not database_mapping: + return None + + nodes = database_mapping.get("nodes") + relationships = database_mapping.get("relationships") + if not isinstance(nodes, dict) or not isinstance(relationships, dict): + return None + + return { + "nodes": nodes, + "relationships": relationships, + } + + +async def get_mongodb_standalone_mapping() -> ( + dict[Literal["collections"], dict[str, Any]] | None +): + """Load standalone SQL-to-MongoDB mapping grouped into collections.""" + raw_database_mapping = await get_database_mapping_json( + FrameworkEnum.JAVA_SPRING_DATA_MONGODB + ) + database_mapping = _extract_mongodb_standalone_mapping( + raw_database_mapping.get("mapping") if raw_database_mapping else None + ) + if not database_mapping: + return None + + collections = database_mapping.get("collections") + if not isinstance(collections, dict): + return None + + return { + "collections": collections, + } diff --git a/services/orchestrator/src/react_agent/utils/request_logging.py b/services/orchestrator/src/react_agent/utils/request_logging.py new file mode 100644 index 0000000..b8cf2bf --- /dev/null +++ b/services/orchestrator/src/react_agent/utils/request_logging.py @@ -0,0 +1,347 @@ +"""Callback Handler streams to stdout on new llm token.""" + +from __future__ import annotations + +import logging +import os +from datetime import datetime +from io import TextIOWrapper +from typing import Any + +import httpx +from langchain_core.agents import AgentAction, AgentFinish +from langchain_core.callbacks.base import BaseCallbackHandler +from langchain_core.messages import BaseMessage +from langchain_core.outputs import LLMResult +from pythonjsonlogger.orjson import OrjsonFormatter as JsonFormatter + +TIMESTAMP = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") +LOGS_DIR = "logs" + + +class LoggingCallbackHandler(BaseCallbackHandler): + """Callback Handler that logs all events to a file in JSON format.""" + + out: dict[str, TextIOWrapper] = {} + timestamp = TIMESTAMP + + def __init__( + self, logs_dir: str = LOGS_DIR, log_string_max_length: int = 1000 + ) -> None: + """Initialize the callback handler.""" + super().__init__() + self.log_string_max_length = log_string_max_length + self.logger = logging.getLogger(self.__class__.__name__) + os.makedirs(f"{logs_dir}/{self.timestamp}", exist_ok=True) + handler = logging.FileHandler( + f"{logs_dir}/{self.timestamp}/langgraph_logging_{self.timestamp}.log" + ) + # handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s] %(message)s :: Exception: %(exc_info)s")) + handler.setFormatter(JsonFormatter()) + handler.setLevel(logging.DEBUG) + self.logger.addHandler(handler) + self.logger.setLevel(logging.DEBUG) + logging.debug(f"Initialized {self.logger.name}.") + + def on_llm_start( + self, serialized: dict[str, Any], prompts: list[str], **kwargs: Any + ) -> None: + """Run when LLM starts running. + + Args: + serialized: The serialized LLM. + prompts: The prompts to run. + **kwargs: Additional keyword arguments. + """ + self.logger.debug( + "LLM start", + extra={"payload": {"serialized": serialized, "prompts": prompts}, "kwargs": kwargs}, + ) + + def on_chat_model_start( + self, + serialized: dict[str, Any], + messages: list[list[BaseMessage]], + **kwargs: Any, + ) -> None: + """Run when LLM starts running. + + Args: + serialized: The serialized LLM. + messages: The messages to run. + **kwargs: Additional keyword arguments. + """ + self.logger.debug( + "Chat model start", + extra={"payload": {"serialized": serialized, "messages": messages}, "kwargs": kwargs}, + ) + + # @override + # def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + # """Run on new LLM token. Only available when streaming is enabled. + + # Args: + # token: The new token. + # **kwargs: Additional keyword arguments. + # """ + # # self.logger.debug("LLM new token", extra={"kwargs": kwargs}) + + # # out = self.out.get(kwargs.get("run_id", "default"), sys.stdout) + # # out.write(token) + # # out.flush() + # # os.fsync(out.fileno()) + + def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: + """Run when LLM ends running. + + Args: + response: The response from the LLM. + **kwargs: Additional keyword arguments. + """ + response_dump = response.model_dump(exclude={"generations"}) + self.logger.debug( + "LLM end", extra={"payload": {"response": response_dump}, "kwargs": kwargs} + ) + # out = self.out.get(kwargs.get("run_id", "default")) + # if out and not out.closed: + # out.close() + + def on_llm_error(self, error: BaseException, **kwargs: Any) -> None: + """Run when LLM errors. + + Args: + error: The error that occurred. + **kwargs: Additional keyword arguments. + """ + self.logger.debug("LLM error.", exc_info=error, extra={"payload": {"error": error}, "kwargs": kwargs}) + # out = self.out.get(kwargs.get("run_id", "default")) + # if out and not out.closed: + # out.close() + + def on_chain_start( + self, serialized: dict[str, Any], inputs: dict[str, Any], **kwargs: Any + ) -> None: + """Run when a chain starts running. + + Args: + serialized: The serialized chain. + inputs: The inputs to the chain. + **kwargs: Additional keyword arguments. + """ + self.logger.debug( + "Chain start", + extra={"payload": {"serialized": serialized, "inputs": inputs}, "kwargs": kwargs}, + ) + # if self.out.get(kwargs.get("run_id", "default")) is None: + # self.out[kwargs.get("run_id", "default")] = open(f"logs/{self.timestamp}/llm_out_{kwargs.get('run_id', 'default')}.log", "a") + + def on_chain_end(self, outputs: dict[str, Any], **kwargs: Any) -> None: + """Run when a chain ends running. + + Args: + outputs: The outputs of the chain. + **kwargs: Additional keyword arguments. + """ + # outputs_copy = deepcopy(outputs) + # for key, value in outputs_copy.items(): + # if isinstance(value, str): + # value = ( + # value + # if len(value) <= self.log_string_max_length + # else f"{value[: self.log_string_max_length]}... [truncated]" + # ) + # outputs_copy[key] = value + self.logger.debug("Chain end", extra={"payload": {"outputs": outputs}, "kwargs": kwargs}) + # for out in self.out.values(): + # if not out.closed: + # out.close() + + def on_chain_error(self, error: BaseException, **kwargs: Any) -> None: + """Run when chain errors. + + Args: + error: The error that occurred. + **kwargs: Additional keyword arguments. + """ + self.logger.debug("Chain error.", extra={"payload": {"error": error}, "kwargs": kwargs}) + # for out in self.out.values(): + # if not out.closed: + # out.close() + + def on_tool_start( + self, serialized: dict[str, Any], input_str: str, **kwargs: Any + ) -> None: + """Run when the tool starts running. + + Args: + serialized: The serialized tool. + input_str: The input string. + **kwargs: Additional keyword arguments. + """ + self.logger.debug( + "Tool start", + extra={"payload": {"serialized": serialized, "input_str": input_str}, "kwargs": kwargs}, + ) + + def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any: + """Run on agent action. + + Args: + action: The agent action. + **kwargs: Additional keyword arguments. + """ + self.logger.debug("Agent action", extra={"payload": {"action": action}, "kwargs": kwargs}) + + def on_tool_end(self, output: Any, **kwargs: Any) -> None: + """Run when tool ends running. + + Args: + output: The output of the tool. + **kwargs: Additional keyword arguments. + """ + # try: + # if isinstance(output, str): + # output_copy = deepcopy(output) + # output_copy = ( + # output_copy + # if len(output_copy) <= self.log_string_max_length + # else f"{output_copy[: self.log_string_max_length]}... [truncated]" + # ) + # elif isinstance(output, dict): + # output_copy = deepcopy(output) + # for key, value in output_copy.items(): + # if isinstance(value, str): + # value = ( + # value + # if len(value) <= self.log_string_max_length + # else f"{value[: self.log_string_max_length]}... [truncated]" + # ) + # output_copy[key] = value + # else: + # output_copy = f"Output of type {type(output)} is not logged in detail." + # except Exception: + # output_copy = f"Output of type {type(output)} could not be logged." + self.logger.debug("Tool end", extra={"payload": {"output": output}, "kwargs": kwargs}) + + def on_tool_error(self, error: BaseException, **kwargs: Any) -> None: + """Run when tool errors. + + Args: + error: The error that occurred. + **kwargs: Additional keyword arguments. + """ + self.logger.debug("Tool error.", exc_info=error, extra={"payload": {"error": error}, "kwargs": kwargs}) + + def on_text(self, text: str, **kwargs: Any) -> None: + """Run on an arbitrary text. + + Args: + text: The text to print. + **kwargs: Additional keyword arguments. + """ + self.logger.debug("Text", extra={"payload": {"text": text}, "kwargs": kwargs}) + + def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None: + """Run on the agent end. + + Args: + finish: The agent finish. + **kwargs: Additional keyword arguments. + """ + self.logger.debug("Agent finish", extra={"payload": {"finish": finish}, "kwargs": kwargs}) + + +class LLMRequestLogger: + timestamp = TIMESTAMP + logger = logging.getLogger("LLMRequestLogger") + + def __init__(self): + os.makedirs(f"logs/{self.timestamp}", exist_ok=True) + handler = logging.FileHandler( + f"logs/{self.timestamp}/request_logging_{self.timestamp}.log" + ) + # handler.setFormatter(logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s] %(message)s")) + handler.setFormatter(JsonFormatter()) + handler.setLevel(logging.DEBUG) + self.logger.addHandler(handler) + self.logger.setLevel(logging.DEBUG) + self.logger.debug(f"Initialized {self.logger.name}.") + + def log_request(self, request: httpx.Request): + self.logger.debug( + f"Request: {request.method} {request.url}", + extra={ + "request": { + "method": request.method, + "url": request.url, + "headers": request.headers, + "content": request.content.decode(), + } + }, + ) + + def log_response(self, response: httpx.Response): + request = response.request + self.logger.debug( + f"Response: {request.method} {request.url} - {response.status_code}", + extra={ + "request": { + "method": request.method, + "url": request.url, + "headers": request.headers, + "content": request.content.decode(), + }, + "response": { + "status_code": response.status_code, + "headers": response.headers, + "extensions": response.extensions, + "cookies": response.cookies, + }, + }, + ) + + class LogRequest(httpx.Request): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + LLMRequestLogger.logger.debug( + f"Request: {self.method} {self.url} - Headers: {self.headers} - Body: {self.content}" + ) + + class LogResponse(httpx.Response): + def iter_bytes(self, *args, **kwargs): + for chunk in super().iter_bytes(*args, **kwargs): # noqa: UP028 + # LLMRequestLogger.logger.debug(chunk) + yield chunk + + async def aiter_bytes(self, *args, **kwargs): + async for chunk in super().aiter_bytes(*args, **kwargs): + # LLMRequestLogger.logger.debug(chunk) + yield chunk + + class LogTransport(httpx.BaseTransport): + def __init__(self, transport: httpx.BaseTransport): + self.transport = transport + + def handle_request(self, request: httpx.Request) -> httpx.Response: + response = self.transport.handle_request(request) + + return LLMRequestLogger.LogResponse( + status_code=response.status_code, + headers=response.headers, + stream=response.stream, + extensions=response.extensions, + ) + + class AsyncLogTransport(httpx.AsyncBaseTransport): + def __init__(self, transport: httpx.AsyncBaseTransport): + self.transport = transport + + async def handle_async_request(self, request: httpx.Request): + response = await self.transport.handle_async_request(request) + + return LLMRequestLogger.LogResponse( + status_code=response.status_code, + headers=response.headers, + stream=response.stream, + extensions=response.extensions, + ) diff --git a/services/orchestrator/src/react_agent/utils/types.py b/services/orchestrator/src/react_agent/utils/types.py new file mode 100644 index 0000000..b59443b --- /dev/null +++ b/services/orchestrator/src/react_agent/utils/types.py @@ -0,0 +1,21 @@ +from typing import Any, Literal + +from react_agent.constants import ( + DotnetFramework, + FrameworkEnum, + JavaFramework, + SourceFramework, + TargetFramework, +) + +FrameworkType = DotnetFramework | JavaFramework | SourceFramework | TargetFramework | FrameworkEnum + +QueryValidationResults = dict[ + Literal["count", "firstSample", "lastSample", "error", "sqlString", "cypher", "mongoQuery", "mongoAggregation"], + Any +] + +QueryEquivalenceDeepDiff = dict[ + Literal["deepdiff_mapping", "count_diff", "first_sample_diff", "last_sample_diff", "error"], + Any +] diff --git a/services/orchestrator/src/react_agent/utils/utils.py b/services/orchestrator/src/react_agent/utils/utils.py new file mode 100644 index 0000000..99b89a4 --- /dev/null +++ b/services/orchestrator/src/react_agent/utils/utils.py @@ -0,0 +1,511 @@ +"""Core utility & helper functions.""" +import logging +import os +import re +from typing import Annotated, Any, Literal, cast + +import aiofiles +import httpx +import orjson +from langchain.agents.middleware import Runtime +from langchain.chat_models import init_chat_model +from langchain_core.language_models import BaseChatModel, ModelProfile +from langchain_core.messages import BaseMessage +from langchain_core.runnables import RunnableConfig +from langchain_ollama import ChatOllama +from langchain_openai import ChatOpenAI +from openai import DefaultAsyncHttpxClient, DefaultHttpxClient +from pydantic import BaseModel, Field, create_model + +from react_agent.constants import ( + FRAMEWORK_TO_NORMALIZED_NAME, + AvailableModel, + FrameworkEnum, +) +from react_agent.context import Context +from react_agent.utils.request_logging import LLMRequestLogger +from react_agent.utils.types import FrameworkType + +logger = logging.getLogger(__name__) +llm_request_logger = LLMRequestLogger() + + +async def get_snippet_content(framework: FrameworkEnum, is_schema: bool = False) -> str: + """Read a snippet file's content based on framework and type.""" + base_dir = os.path.join( + os.path.dirname(__file__), "..", "..", "context", "snippets" + ) + file_map = { + FrameworkEnum.DOTNET_EFCORE: ( + "EFCoreSchemaValidationEntrypoint.cs", + "EFCoreQueryEntrypoint.cs", + ), + FrameworkEnum.DOTNET_DAPPER: ( + "DapperSchemaValidationEntrypoint.cs", + "DapperQueryEntrypoint.cs", + ), + FrameworkEnum.DOTNET_NHIBERNATE: ( + "NHibernateSchemaValidationEntrypoint.cs", + "NHibernateQueryEntrypoint.cs", + ), + FrameworkEnum.JAVA_SPRING_DATA_MONGODB: ( + "MongoSchemaValidationEntrypoint.java", + "MongoQueryEntrypoint.java", + ), + FrameworkEnum.JAVA_SPRING_DATA_NEO4J: ( + "Neo4jSchemaValidationEntrypoint.java", + "Neo4jQueryEntrypoint.java", + ), + } + + if framework not in file_map: + logger.warning(f"No snippet file mapping found for framework {framework.value}") + return "" + + file_name = file_map[framework][0] if is_schema else file_map[framework][1] + path = os.path.join(base_dir, file_name) + try: + async with aiofiles.open(path) as f: + return await f.read() + except Exception as e: + logger.warning(f"Failed to read snippet file {path}: {e}") + return "" + + +async def get_framework_config_content(framework: FrameworkEnum) -> str: + base_dir = os.path.join( + os.path.dirname(__file__), "..", "..", "context", "snippets" + ) + file_map = { + FrameworkEnum.DOTNET_EFCORE: "efcore-sandbox.csproj", + FrameworkEnum.DOTNET_DAPPER: "dapper-sandbox.csproj", + FrameworkEnum.DOTNET_NHIBERNATE: "nhibernate-sandbox.csproj", + FrameworkEnum.JAVA_SPRING_DATA_MONGODB: "mongo-pom.xml", + FrameworkEnum.JAVA_SPRING_DATA_NEO4J: "neo4j-pom.xml", + } + + if framework not in file_map: + logger.error(f"No config file mapping found for framework {framework.value}") + raise ValueError(f"Unsupported framework: {framework.value}") + + path = os.path.join(base_dir, file_map[framework]) + try: + async with aiofiles.open(path) as f: + return await f.read() + except Exception as e: + logger.error(f"Failed to read framework config file {path}: {e}") + raise e + + +def get_model_creator_map() -> dict[AvailableModel, str]: + """Return a mapping of available models to their LangChain creator class names.""" + creator_map = {} + for provider_model in AvailableModel: + _, model = provider_model.value.split("/", maxsplit=1) + if re.search("kimi", model, re.IGNORECASE): + creator_map[model] = "moonshotai" + elif re.search("qwen", model, re.IGNORECASE): + creator_map[model] = "alibaba" + elif re.search("glm", model, re.IGNORECASE): + creator_map[model] = "zai" + elif re.search("deepseek", model, re.IGNORECASE): + creator_map[model] = "deepseek" + elif re.search("mistral", model, re.IGNORECASE): + creator_map[model] = "mistral" + elif re.search("llama", model, re.IGNORECASE): + creator_map[model] = "meta" + return creator_map + + +def get_normalized_framework_name( + framework: FrameworkType, +) -> str: + """Normalize framework names into a standardized string format for LLM prompting.""" + return FRAMEWORK_TO_NORMALIZED_NAME[cast(FrameworkEnum, framework)] + + +def get_message_text(msg: BaseMessage) -> str: + """Get the text content of a message.""" + content = msg.content + if isinstance(content, str): + return content + elif isinstance(content, dict): + return content.get("text", "") + else: + txts = [c if isinstance(c, str) else (c.get("text") or "") for c in content] + return "".join(txts).strip() + + +async def load_chat_model( + fully_specified_name: str, config: dict[str, Any] = {}, +) -> BaseChatModel: + """Load a chat model from a fully specified name. + + Args: + fully_specified_name (str): String in the format 'provider/model'. + config (dict): Optional configuration passed from the context to initialize remote parameters like API keys. + """ + provider, model = fully_specified_name.split("/", maxsplit=1) + debugging = True if os.getenv("DEVELOPMENT") else False + # log_http_transport = llm_request_logger.LogTransport(httpx.HTTPTransport()) + # async_log_http_transport = llm_request_logger.AsyncLogTransport(httpx.AsyncHTTPTransport()) + + if provider == "openai" or provider == "einfra": + debug_kwargs = {} + if debugging: + debug_kwargs = { + "http_client": DefaultHttpxClient( + timeout=120, + event_hooks={ + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + ), + "http_async_client": DefaultAsyncHttpxClient( + timeout=120, + event_hooks={ + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + ), + } + model_client = ChatOpenAI( + model=model, # type: ignore + base_url=config.get("openai_api_url"), # type: ignore + api_key=config.get("openai_api_key"), # type: ignore + max_retries=10, + request_timeout=120, # type: ignore + stream_usage=True, + **({"temperature": config.get("temperature", 0)} if config.get("temperature") is not None else {}), + # **debug_kwargs, # type: ignore + ) + elif provider == "ollama": + debug_kwargs = {} + if debugging: + debug_kwargs = { + "sync_client_kwargs": { + "event_hooks": { + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + # "transport": log_http_transport + }, + "async_client_kwargs": { + "event_hooks": { + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + # "transport": async_log_http_transport + }, + } + model_client = ChatOllama( + model=model, + base_url=config.get("ollama_api_url", "http://localhost:11434"), + **({"temperature": config.get("temperature", 0)} if config.get("temperature") is not None else {}), + # reasoning=True, + # **debug_kwargs, # type: ignore + ) + else: + if debugging: + # TODO: make init_chat_model the only branch, do not use provider-specific clients here, so that we can use configurable model params + model_client = init_chat_model( + model, + model_provider=provider, + base_url=config.get("openai_api_url"), + api_key=config.get("openai_api_key"), + reasoning=True, + stream_usage=True, + max_retries=10, + timeout=120, + configurable_fields="any", + **config, + http_client=DefaultHttpxClient( + timeout=120, + event_hooks={ + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + ), + http_async_client=DefaultAsyncHttpxClient( + timeout=120, + event_hooks={ + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + ), + sync_client_kwargs={ + "event_hooks": { + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + }, + async_client_kwargs={ + "event_hooks": { + "request": [llm_request_logger.log_request], + "response": [llm_request_logger.log_response], + }, + }, + ) + else: + model_client = init_chat_model( + model, + model_provider=provider, + reasoning=True, + stream_usage=True, + max_retries=10, + timeout=120, + configurable_fields="any", + **config, + ) + + if getattr(model_client, "profile", None) is None: + profile: ModelProfile | None = None + try: + p_kwargs = {} + if provider in ("einfra", "litellm"): + config = config or {} + base_url = config.get("openai_api_url") + if base_url: + base_url = base_url.rstrip("/") + url = f"{base_url}/model/info" + headers = {} + api_key = config.get("openai_api_key") + if api_key: + headers["Authorization"] = f"Bearer {api_key}" + async with httpx.AsyncClient() as client: + response = await client.get( + url, + params={"model_name": model}, + headers=headers, + timeout=10.0, + ) + if response.status_code == 200: + data = response.json() + profile_raw = None + if "data" in data and isinstance(data["data"], list): + for model_entry in data["data"]: + if model_entry.get("model_name") == model: + profile_raw = model_entry + break + elif "model_info" in data and isinstance( + data["model_info"], list + ): + for model_entry in data["model_info"]: + if model_entry.get("model_name") == model: + profile_raw = model_entry + break + + if profile_raw: + minfo = profile_raw.get("model_info", {}) + lparams = profile_raw.get("litellm_params", {}) + supported = minfo.get("supported_openai_params", []) + ebody = lparams.get("extra_body", {}) + + max_input = minfo.get("max_input_tokens") or minfo.get( + "context_length" + ) + # max_output = minfo.get("max_output_tokens") or ebody.get( + # "max_tokens" + # ) + + p_kwargs = { + "name": profile_raw.get("model_name", model), + "text_inputs": True, + "text_outputs": True, + } + if max_input is not None: + p_kwargs["max_input_tokens"] = int(max_input) + # if max_output is not None: + # p_kwargs["max_output_tokens"] = int(max_output) + + for key, check in [ + ( + "tool_calling", + "tools" in supported or "functions" in supported, + ), + ("tool_choice", "tool_choice" in supported), + ("structured_output", "response_format" in supported), + ( + "reasoning_output", + "reasoning_effort" in supported + or "thinking" + in ebody.get("chat_template_kwargs", {}), + ), + ("temperature", "temperature" in supported), + ]: + if check: + p_kwargs[key] = True + + elif provider == "ollama": + config = config or {} + base_url = config.get( + "ollama_api_url", "http://localhost:11434" + ).rstrip("/") + async with httpx.AsyncClient() as client: + response = await client.post( + f"{base_url}/api/show", json={"model": model}, timeout=10.0 + ) + if response.status_code == 200: + profile_raw = response.json() + details = profile_raw.get("details", {}) + family = details.get("family", "llama") + model_info = profile_raw.get("model_info", {}) + max_input = model_info.get(f"{family}.context_length") + capabilities = profile_raw.get("capabilities", []) + + p_kwargs = { + "name": profile_raw.get("model", model), + "text_inputs": True, + "text_outputs": True, + } + if max_input is not None: + p_kwargs["max_input_tokens"] = int(max_input) + + if "vision" in capabilities: + p_kwargs["image_inputs"] = True + if "tools" in capabilities or "tool_calling" in capabilities: + p_kwargs["tool_calling"] = True + + # Try to get missing info from AI Gateway + if ( + not p_kwargs.get("max_input_tokens") + or not p_kwargs.get("max_output_tokens") + or "reasoning_output" not in p_kwargs + ): + creator_map = get_model_creator_map() + creator = creator_map.get(AvailableModel(fully_specified_name)) + if creator: + gateway_url = ( + f"https://ai-gateway.vercel.sh/v1/models/{creator}/{model}" + ) + try: + async with httpx.AsyncClient() as client: + gw_resp = await client.get(gateway_url, timeout=5.0) + if gw_resp.status_code == 200: + gw_data = gw_resp.json() + + if ( + not p_kwargs.get("max_input_tokens") + and "context_window" in gw_data + ): + p_kwargs["max_input_tokens"] = gw_data["context_window"] + + if ( + not p_kwargs.get("max_output_tokens") + and "max_tokens" in gw_data + ): + p_kwargs["max_output_tokens"] = gw_data["max_tokens"] + + tags = gw_data.get("tags", []) + if ( + "reasoning_output" not in p_kwargs + and "reasoning" in tags + ): + p_kwargs["reasoning_output"] = True + if "image_inputs" not in p_kwargs and "vision" in tags: + p_kwargs["image_inputs"] = True + if "tool_calling" not in p_kwargs and "tool-use" in tags: + p_kwargs["tool_calling"] = True + except Exception as e: + logger.debug( + f"Could not fetch AI Gateway profile for {creator}/{model}: {e}" + ) + profile = ModelProfile(**p_kwargs) + + except Exception as e: + logger.warning(f"Failed to fetch model profile for {provider}/{model}: {e}") + + if profile is not None: + model_client.profile = profile # type: ignore + + return model_client # type: ignore + + +# TODO: remove this, and only use load_chat_model; if "configurable" is set (non-empty), use "init_chat_model" path to initialize (with custom system prompt, model params, etc.) +async def get_model( + config: RunnableConfig, + runtime: Runtime[Context], + model_name_override: str | None = None, + temperature: float | None = None, + **chat_model_kwargs, +) -> BaseChatModel: + """Factory to initialize the model using configuration or context.""" + configurable = config.get("configurable", {}) + model_choice = model_name_override or configurable.get( + "model", runtime.context.model + ) + model_name = getattr(model_choice, "value", str(model_choice)) + + openai_url = configurable.get( + "openai_api_url", getattr(runtime.context, "openai_api_url", None) + ) + openai_key = configurable.get( + "openai_api_key", getattr(runtime.context, "openai_api_key", None) + ) + + return await load_chat_model( + model_name, config={"openai_api_url": openai_url, "openai_api_key": openai_key, "temperature": temperature, **chat_model_kwargs} + ) + + +async def get_database_mapping_json( + target_framework: FrameworkEnum, +) -> dict[Literal["source", "destination", "mapping"], str | object] | None: + """Load the database mapping JSON for the given target framework.""" + try: + if target_framework == FrameworkEnum.JAVA_SPRING_DATA_MONGODB: + async with aiofiles.open("src/context/mappings/mssql_mongodb.json") as f: + return { + "source": "Microsoft SQL Server", + "destination": "MongoDB", + "mapping": orjson.loads(await f.read()), + } + elif target_framework == FrameworkEnum.JAVA_SPRING_DATA_NEO4J: + async with aiofiles.open("src/context/mappings/mssql_neo4j.json") as f: + return { + "source": "Microsoft SQL Server", + "destination": "Neo4j", + "mapping": orjson.loads(await f.read()), + } + except Exception: + logger.warning( + f"Failed to load database mapping for {target_framework.value}", + exc_info=True, + ) + return None + + +async def create_example_for_prompt( + framework: FrameworkEnum, + return_schema: bool +) -> str: + """Create example code snippets for prompts based on the framework.""" + example = f""" + +{await get_snippet_content(framework, is_schema=True) if return_schema else await get_snippet_content(framework, is_schema=False)} +""" + return example + + +def override_pydantic_model_schema(model_cls: type[BaseModel], overrides: dict[str, dict[str, Any]], model_name: str | None = None) -> type[BaseModel]: + """Override the schema of a Pydantic model's fields.""" + new_fields = {} + for f_name, f_info in model_cls.model_fields.items(): + f_dct = f_info.asdict() + if f_name in overrides: + override = overrides[f_name] + f_dct["annotation"] = override.get("annotation", f_dct["annotation"]) + f_dct["metadata"] = override.get("metadata", f_dct["metadata"]) + f_dct["attributes"] = {**f_dct["attributes"], **override.get("attributes", {})} + new_fields[f_name] = ( + Annotated[f_dct["annotation"], *f_dct['metadata'], Field(**f_dct['attributes'])], # noqa: F821 + None, + ) + return create_model( + model_name or model_cls.__name__, + __base__=model_cls, + **new_fields, + ) + +def identity(x: Any) -> Any: + return x diff --git a/services/orchestrator/static/studio_ui.png b/services/orchestrator/static/studio_ui.png new file mode 100644 index 0000000..808d3c8 Binary files /dev/null and b/services/orchestrator/static/studio_ui.png differ diff --git a/services/orchestrator/tests/cassettes/103fe67e-a040-4e4e-aadb-b20a7057f904.yaml b/services/orchestrator/tests/cassettes/103fe67e-a040-4e4e-aadb-b20a7057f904.yaml new file mode 100644 index 0000000..1d80331 --- /dev/null +++ b/services/orchestrator/tests/cassettes/103fe67e-a040-4e4e-aadb-b20a7057f904.yaml @@ -0,0 +1,2053 @@ +interactions: +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:50:53.832822+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA2yRS0vEQAzHv0rIxctUuqurOEdXXRQPooIvpIzb9KFtsjuTUevS7y6tiAqeQh6/ + /PPYYJ2jxTaUWTrZf32Wp9Jf3h2+Fa/z+4/F1dFiskCD2q1oqKIQXElo0EszBFwIdVDHigZbyalB + i8vGxZySnWSWBGEmTabpdDfdm6ZocCmsxIr2YfPdVOl9wEdj8VrAcXgjD51ED+tIQWthcE8SFbQi + KCRyTh6kgHPH5bxyNRs43WoaYKIcVCCQ88sKCvEj0UpQiKtEJcmdEtRciG/d0HcbzkmhJcgFtHI6 + Mp3EbezNz4QiTRbDsPd4rMGPWTo5XB/NbiYXz2fkZrfHxye+q9pFiQbZtQP3NcZA8Soq2g2uI/kO + Lf63A/b9o8Ggsso8uSD8V3lMBFpH4iWh5dg0BuP4Drv5UshUXogD2t2dPYMS9XfsYNr3nwAAAP// + AwCzkxon7QEAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22ab88f99c4cb1-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:51:31 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:51:29Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:51:31Z' + request-id: + - req_019JrRXeYjgQXtSjyp85fdHe + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: null + headers: {} + method: POST + uri: https://api.tavily.com/search + response: + body: + string: '{"query":"founder of LangChain","follow_up_questions":null,"answer":null,"images":[],"results":[{"title":"Speaker + Harrison Chase - ELC","url":"https://sfelc.com/speaker/harrison-chase","content":"Harrison + Chase is the CEO and co-founder of LangChain, a company formed around the + open source Python/Typescript packages that aim to make it easy to develop + Language Model applications. Prior to starting LangChain, he led the ML team + at Robust Intelligence (an MLOps company focused on testing and validation + of machine learning models), led the","score":0.99967754,"raw_content":null},{"title":"Harrison + Chase - The AI Conference","url":"https://aiconference.com/speakers/harrison-chase/","content":"Harrison + Chase is the co-founder and CEO of LangChain, a company formed around the + open-source Python/Typescript packages that aim to make it easy to develop + Language Model applications. Prior to starting LangChain, he led the ML team + at Robust Intelligence (an MLOps company focused on testing and validation + of machine learning models), led the","score":0.9995908,"raw_content":null},{"title":"Harrison + Chase | TEDAI San Francisco","url":"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/","content":"Harrison + Chase, a Harvard graduate in statistics and computer science, co-founded LangChain + to streamline the development of Language Model applications with open-source + Python/Typescript packages. Chase''s experience includes heading the Machine + Learning team at Robust Intelligence, focusing on the testing and validation + of machine learning models, and leading the entity linking team at Kensho","score":0.9994746,"raw_content":null},{"title":"Harrison + Chase, Author at TechCrunch","url":"https://techcrunch.com/author/harrison-chase/","content":"Harrison + Chase is the CEO and co-founder of LangChain, a company formed around the + open source Python/Typescript packages that aim to make it easy to develop + Language Model applications","score":0.9994185,"raw_content":null},{"title":"LangChain''s + Harrison Chase on Building the Orchestration Layer for AI ...","url":"https://www.sequoiacap.com/podcast/training-data-harrison-chase/","content":"Sonya + Huang: Hi, and welcome to training data. We have with us today Harrison Chase, + founder and CEO of LangChain. Harrison is a legend in the agent ecosystem, + as the product visionary who first connected LLMs with tools and actions. + And LangChain is the most popular agent building framework in the AI space.","score":0.99876,"raw_content":null},{"title":"Harrison + Chase, LangChain CEO - Interview - YouTube","url":"https://www.youtube.com/watch?v=7D8bw_4hTdo","content":"Join + us for an insightful interview with Harrison Chase, the CEO and co-founder + of Langchain, as he provides a comprehensive overview of Langchain''s innovati","score":0.99854493,"raw_content":null},{"title":"Harrison + Chase - Forbes","url":"https://www.forbes.com/profile/harrison-chase/","content":"Harrison + Chase only cofounded LangChain in late 2022, but the company caught instant + attention for enabling anyone to build apps powered by large language models + like GPT-4 in as little as two","score":0.9977743,"raw_content":null},{"title":"LangChain + - Wikipedia","url":"https://en.wikipedia.org/wiki/LangChain","content":"In + October 2023 LangChain introduced LangServe, a deployment tool designed to + facilitate the transition from LCEL (LangChain Expression Language) prototypes + to production-ready applications.[5]\nIntegrations[edit]\nAs of March 2023, + LangChain included integrations with systems including Amazon, Google, and + Microsoft Azure cloud storage; API wrappers for news, movie information, and + weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot + learning prompt generation support; finding and summarizing \"todo\" tasks + in code; Google Drive documents, spreadsheets, and presentations summarization, + extraction, and creation; Google Search and Microsoft Bing web search; OpenAI, + Anthropic, and Hugging Face language models; iFixit repair guides and wikis + search and summarization; MapReduce for question answering, combining documents, + and question generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and + pymupdf for PDF file text extraction and manipulation; Python and JavaScript + code generation, analysis, and debugging; Milvus vector database[6] to store + and retrieve vector embeddings; Weaviate vector database[7] to cache embedding + and data objects; Redis cache database storage; Python RequestsWrapper and + other methods for API requests; SQL and NoSQL databases including JSON support; + Streamlit, including for logging; text mapping for k-nearest neighbors search; + time zone conversion and calendar operations; tracing and recording stack + symbols in threaded and asynchronous subprocess runs; and the Wolfram Alpha + website and SDK.[8] As a language model integration framework, LangChain''s + use-cases largely overlap with those of language models in general, including + document analysis and summarization, chatbots, and code analysis.[2]\nHistory[edit]\nLangChain + was launched in October 2022 as an open source project by Harrison Chase, + while working at machine learning startup Robust Intelligence. In April 2023, + LangChain had incorporated and the new startup raised over $20 million in + funding at a valuation of at least $200 million from venture firm Sequoia + Capital, a week after announcing a $10 million seed investment from Benchmark.[3][4]\n + The project quickly garnered popularity, with improvements from hundreds of + contributors on GitHub, trending discussions on Twitter, lively activity on + the project''s Discord server, many YouTube tutorials, and meetups in San + Francisco and London. As of April 2023, it can read from more than 50 document + types and data sources.[9]\nReferences[edit]\nExternal links[edit]","score":0.99694854,"raw_content":null},{"title":"Harrison + Chase - CEO of LangChain - Analytics India Magazine","url":"https://analyticsindiamag.com/people/harrison-chase/","content":"By + AIM The dynamic co-founder and CEO of LangChain, Harrison Chase is simplifying + the creation of applications powered by LLMs. With a background in statistics + and computer science from Harvard University, Chase has carved a niche in + the AI landscape. AIM Brand Solutions, a marketing division within AIM, specializes + in creating diverse content such as documentaries, public artworks, podcasts, + videos, articles, and more to effectively tell compelling stories. AIM Research + produces a series of annual reports on AI & Data Science covering every aspect + of the industry. Discover how Cypher 2024 expands to the USA, bridging AI + innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms","score":0.99491996,"raw_content":null},{"title":"Key + Insights from Harrison Chase''s Talk on Building Next-Level AI Agents","url":"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents","content":"Harrison + Chase, founder of LangChain, shared insights on the evolution of AI agents + and their applications during Sequoia Capital''s AI Ascent. ... Saves you + a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises","score":0.9941801,"raw_content":null}],"response_time":2.54}' + headers: + Connection: + - keep-alive + Content-Length: + - '7474' + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:51:34 GMT + Server: + - nginx + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + your question about the founder of LangChain, I''ll need to search for the most + up-to-date information. Let me do that for you.", "type": "text"}, {"type": + "tool_use", "name": "search", "input": {"query": "founder of LangChain"}, "id": + "toolu_01BqD5W1PjJea5XEEFryhmGg"}]}, {"role": "user", "content": [{"type": "tool_result", + "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", \"content\": + \"Harrison Chase is the CEO and co-founder of LangChain, a company formed around + the open source Python/Typescript packages that aim to make it easy to develop + Language Model applications. Prior to starting LangChain, he led the ML team + at Robust Intelligence (an MLOps company focused on testing and validation of + machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_01BqD5W1PjJea5XEEFryhmGg", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:50:59.620569+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RUXW/cRgz8K8QiQF90wln+SHtvjlvERh2kqG2gaFMYvBUlbW7FlXe55wiG/3vB + 9Z0/EvRJgEQOZ4ZDPRjXmpUZU3+7PLg8/P1Kmq83H9PPza9Xf/2d+vPh5hdTGZkn0ipKCXsylYnB + 6wtMySVBFlOZMbTkzcpYj7mlxeHieJECM8miWTZHy5NmaSpjAwuxmNU/D3tQoW/aXh4rcz0gb2AO + GboQ9RnhLlMSF7iGC7DIwOEephi2rqVSeO9kAMddiCNqHeA6ZAEZCLqQuaUIoYNL5P5sQMf1F/7C + 1//zEVyCc4zRpcBwNmCiGj6FSJAmsq5zFr2fV4pwUH9XqK0604bFHhm5hbPfPv84vqnhfE+gfT2d + waMQNMumqeG6oI0T8gz3mCB0nbNOGYDHzHagVjs+WwlriqUJMAEyhIl4kUKOltSqr2SlzD2sd1wH + rYM12k0flYXiJEFxSZxNhbhOzkIRknXElqCLYVTNW4wt3LDbUkxO5gJ8VMMH6opRglEc9y+qKhgI + PLXFnU9oB8cEl4SRtUwIR0CBP8M6J4ELFvLe9TqxAnzW3wWbE7UQGETjwH0huUXv2qethw7GHbjf + g5dMpsLwuH7l8112duNn6NExtTCFKXuMTmb1QWmeXujkMbOTuSqT1jOcTtF5tfmw2m36iduALUR0 + Si+5nktMWKDL3Druyx5nSGRzVAFbivCuWcLovFfejveVagOqpPysCEXFJNGOl5ayiSu6y8EhnOHk + BH0FaQhR/AzYSckeh8y2wMK7g5fmRCU2W0oyktJUsA/EdhgxbopXJzXclACXsPyUlENLMQ1uql65 + qCFakw0jAe49hC7iSPchbsoBt7QlH6bCYpq8s0VZginck7qx1ijHnsAj9xl72q0MvNsQfPzjenFU + GP14ajZwcm0Bwbe2uz5HerXINKFmadJcWuXon9e8o1d8CB1ICP4p/M8q9KZRYMQNgRMgTI4iSIAi + sfx6vqdejieS3vHpxRvZtXn8tzJJwnQbCVNgszLE7a3kyGb3IdFd1vCbFWfvK5PLH3f1YBxPWW4l + bIiTWTXL98eVCVnevHx/8vj4HwAAAP//AwBvXpD30gUAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22abad2aef4caf-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:51:40 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:51:35Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:51:40Z' + request-id: + - req_012BB17DG6rpcEU1xAbh9Zao + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:51:11.433533+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SRTWvcQAyG/4rQpZdxcHY3XuJLCYX0a3tJCz2EYiYeeT2tLXktTRuz+L8Xe1vS + noQ+3lcP0hljwBJ7PVb59ef44Xtzc/+UBp8Oxce3t9vdcf+ADm0aaJkiVX8kdDhKtxS8alTzbOiw + l0Adllh3PgXKttlNpsJMlm3yzS4vNjk6rIWN2LB8PP81NXpe5Gso8YuAZ/1FI1gbFU6J1KIw+LpO + ozfqJgfvX3UdMFEAE1DyY91CI4uCoBc1SENmkgVvBJEbGXt/8XiSZHDwfHzT+sjgOUA0hUYSBxqv + 4EAGPUEQsNbb6jlJusLZvdCKdFXS5Qbr4ZY8Vfn13VTc3/XF/vT88PPdp9ooNBoEHbLvF90Fc1Hx + kAzLM54SjROW+LUViLrC/wEBaV4oX+M8f3OoJkM1klfh/ynWhtIpEdeEJaeuc5jWN5Xny7bK5Aex + YrnbFg4l2b+12/08/wYAAP//AwBjKTr0BQIAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22abf709c34cb4-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:51:49 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:51:47Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:51:49Z' + request-id: + - req_01QtVfENAkfspCf9KVLM7Emk + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "Who is the founder + of LangChain?"}, "id": "toolu_01Ay6FAm67qxRvHMctedfsdo"}]}, {"role": "user", + "content": [{"type": "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_01Ay6FAm67qxRvHMctedfsdo", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:51:14.324904+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RVbW/bNhD+KwdiQDZA9lynaQd9a4IWyZBi6dq9AMsQnKmzxJoiFd7RrhD0vw9H + 2Z6XoZ8MUHfH5+3oJ+MaU5ue24fFi9uP169//u2PO/rzarndvXt9+/nt9t3GVEbGgbSKmLElU5kU + vR4gs2PBIKYyfWzIm9pYj7mh2fnsYsYxBJLZcrF8uXi1XJjK2BiEgpj6r6fDUKEv2l5+anOJTA3E + ANIRMGGyHSTi7IUruAGLAWwMa9dQED8CBt5RgjHmBI+ZWFwMgKuYpQxYxxwaShDXcIuhverQhfl9 + uA/XmJLjGOCqQyZw/O1y+BRhRdDHRDAkso6pgu7YY+Ps0Iahgau3vzxrv6ZEgImAY0+woRGG6ILw + HuYzJDrj2Fwr1BdzeKcXoHKrn9cfrz9pAxfAoxAsF8vlHD4VlP2AYYQdMqxj6qkBTNoHcaAw45iT + JbgbpYvhx0/jQB9tcoPAgHaDLTGgKz0CPW5caMEJELKjBBKhoS35OBQEGVuC95oFwGHwzhbcXFRf + zuES7aYtN9f/ao/KaoupgTZhkxX6zkkHCKtjuZJiQXEsznLRSUlloQRsHQVL5Y7zOdwl2rqYGejL + QKl8quGS1uogCyZRAke1ipmemuLme7SdCwS3hClomRD2SvvXuMoscBOEvHetzqwAj7quo82H4GoK + Q1sQbtG7yTgNRb8f7g/Dy8pM0rycw4fs7AbaFHfS1SduPuq5H6FFF6iBIQ7ZY3IyqiQK+s2N4uhz + cDLO4XKEN0NyXt0/r+Czwu5jkI4B16qWEwaPOdiu2id44tBhAwmd0mDXBrd2FoPAOofmwEfjs0Wf + pyjELSX4brlYQO+8d3FarYs53PQDWvlfVh1DQ2yTW2m/2n5vPLU0matQsKUgQDbyyEL9vdGwQucY + 9MHRKqtPit07mFp6ljmG729v3/MPU34kRj9FBe2UwhNZO2RYkdWljIHUH0XQR5aDxBOc2So7XxRY + J+xpF9OGT5TnAffJezWH3x2XJS18zxjaiH6CcrKcrCvDkgh7r3HQQfsN6pX+/vn4xiJVJxuI1hKz + W3kqOiHsnL5DCUNbCO2nUppEWGV2gZipILCJdNHe3MyGuKOkljzf1yPoM4aEg2sgOaYT8i40mSWN + wF3cWdTJ//X8jGFbNCkAtEvzXeD3Q0yCwU7SF6ekmx4YAlVZq4p2vhjtD5pMWzM9yCcK6AW0Xjt9 + C2Ruvv5dGZY4PCRCjsHUhkLzIDkFs//A9Jh1j00dsveVyeW/rX4yLgxZHiRuKLCpl4ufFpWJWU4P + zy8WX7/+AwAA//8DAHBCV3w8BwAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22ac0918734cb2-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:51:56 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:51:49Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:51:56Z' + request-id: + - req_01BLNX4GjFRn5bwxxBo3hd3r + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:51:55.561440+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1RRS2vbQBD+K8NcclkFxY6dZG+NIaTgXNxgU0oRa2lkCa9mJO1sY2P034vklrSn + YR7fg28uWBdosQmHLL3b0fHwbb85Plb1utsudt8X2+enFRrUc0vjFYXgDoQGe/HjwIVQB3WsaLCR + gjxazL2LBSXzZJEEYSZNZunsPl3OUjSYCyuxov1x+UuqdBrhU7H4LuA4fFAPWtUBukhBa2FweR57 + p+TPBr7eeA9MVIAKBHJ9XkEpI4KgkaAQ20QlKZwS1FxK37grx16iwtrxYVW5msFxAbUGKCVyQf0t + rEmhISgEtHI6cZ4l3uJgPt2K+CyGMYMpuLGPWXr3Rt1m177+enj5svp4OZVv283+tESD7JoRd7U5 + oriNivaCXaT+jBb/qIOUn9ZwGH4aDCpt1pMLwv8rT4tAXSTOCS1H7w3G6TX2clXIVI7EAe39fGlQ + ov47e5oPw28AAAD//wMAEu0ZMPkBAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22ad0acd1032b3-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:52:33 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:52:31Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:52:33Z' + request-id: + - req_01GSwnbYyFo11udBwAygsuuc + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "founder of LangChain"}, + "id": "toolu_01MeqRWpHv7FACwFxfMVRbx6"}]}, {"role": "user", "content": [{"type": + "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_01MeqRWpHv7FACwFxfMVRbx6", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:51:58.213993+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RU0W7cNhD8lQWfWkCnXs6Jg96bY7SIURtxE/epVxh71EraHLWUyeW5guF/L1Z3 + bmynfRJEcmdmZ4d8cNy4tRtyd7t8c/3b5erns7yKfHp18iXSr1+73zO7yuk0kp2inLEjV7kUgy1g + zpwVRV3lhthQcGvnA5aGFieLd4scRUgXq+Xq7fJ0tXSV81GURN36z4cnUKW/rXz+rN0HzNRAFNCe + IBMm30OiXILmCi7Ao8CY4p4bgikWuGftAb0vCZWApY1pQOUogNtYdEZpY5GGEsQWLlG68x5Z1hvZ + yM3/bAJn+IgpcY4C5z1mquEqJoI8kueWPYYwzQhv6lcHrdQ4fVw8IaM0cP7LpxcM9UZWNXx8om+e + cwsE62W1XK2qufiAN4woE9wV9rswQYcs1ACquWnttjEBawYWiXtU3hPgOKaIvgeNcHYBDe0pxHEg + 0XojJ/Uzznu0Qla2xiBgEd8begYUiCPJIseSPJn1X8mrifzkNW4pzTrhvudAr60w1PuYdiwdoALC + gL5nIQiESWw1KyYtI5ih1MDnuC1Z4UKUQuCOxFO9kbf1Ea83PbBFv+uS2WYqsqJyVvZ5tspsKkoJ + smcrhzbFwXTtMTXwh/CeUmad6o28q+E6cUzmzqzDBP1rSQW96TyYf3XUffmkWwkHa+k/BFfmhe+h + jb48JZnyDG4C9xi4OQQ0tt8bMt+hXFu0vk0HecimcsAdASsQ5sn+jwOdRRfsCK6s2sYe2M8clsUU + S9e/GOL1pH2Un26mkbJPPCqM6HfYUa7h5lnWzO9jzjJ3MgdfFDShnxvgwyU9u7CKoQjrNPdodYk8 + 8d4qy3Z+IBgDtEUa63Ieyp5ESyLwOLLaJqchQ+AdwRe6K5ERzo9bBvqBxPcDpt1szveXzkfJ3FCy + 2IJExW0gaLkzim9Cycc8ZaWhgtFG7kvAFKb59vR8iKsd9/Zw+WMkUkevPM7ww+XlVf7x8PxojOEQ + v4MxubJRvYgTIIxxNDJoEw408xjptnCYPTm7AOxINNfu8a/KZY3jbSLMUdzakTS3WpK440amu2Jh + c2spIVSuzK/y+sGxjEVvNe5Isluvlu9PKxeLvlh8f/L4+A8AAAD//wMAo1MaSPYFAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22ad1b7c4d4cb1-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:52:39 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:52:33Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:52:39Z' + request-id: + - req_01E8yQY8oXhidTwTxeWKdjiW + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:52:42.318107+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SRXWvbUAyG/4rQzW6Oi5O2SeObQgspgzBW2tLBGObUVuJDbSk50mkSgv/7sLPR + 7Uro4331IJ0w1Fhgp5syn9w8r252+7iY3YfV9+nbkibd+8ccHdpxS8MUqfoNocMo7VDwqkHNs6HD + TmpqscCq9amm7DK7zlSYybJpPr3KZ9McHVbCRmxY/Dz9NTU6DPIxFPgs4Fn3FMGaoLBLpBaEwVdV + it6oPTr4+qVtgYlqMAElH6sG1jIoCDpRg7TNTLLaG0HgtcTOnz3eJBmsPG/uGx8YPNcQTGEtiWuK + F7Aig46gFrDG2+h5lHSBvfukFWnLpMMNxsMNeSrzyeQ1/rh7OsQFx2931/v54+HhZblEh+y7QXfG + HFS8TYbFCXeJ4hELfG0Ego7wf0BA1p+Ut9j3vxyqybaM5FX4f4qxobRLxBVhwaltHabxTcXpvK00 + eSdWLK4uZw4l2b+1xbzvfwMAAP//AwB9NM36BQIAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22ae2f5dec4cb6-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:53:20 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:53:18Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:53:20Z' + request-id: + - req_01DsVqmPigsQsHqctV3yYFQ5 + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "Who is the founder + of LangChain?"}, "id": "toolu_011WrXBSxr9nrNB5w7QxGUFF"}]}, {"role": "user", + "content": [{"type": "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_011WrXBSxr9nrNB5w7QxGUFF", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:52:45.186170+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA2RV0W4bRwz8FWIRoC1wUhXZSdp7i402NuCgbpunNoVB7VF3jPZ210uuHNXIvxc8 + SbGcPgnYI4fDIYd6dNy51o3S3y1e3sa/7h/ecL6X128+XXU3//rxLJ27xukuk0WRCPbkGldSsAcU + YVGM6ho3po6Ca50PWDuanc1ezSTFSDpbLpbni9fLhWucT1Epqmv/fjyCKn229OmndRco1EGKoAOB + EBY/QCGpQaWBa/AYIZe05Y5glyo8sA6A3teCSsBxncqIyikCrlLVCWWdauyoQFrDDcb+ckCO7cf4 + MV5hKSwpwuWAQsDyLBxjB5e//PYsbQ5XVAiwEEgaCTa0g5w4qhzqfQNpGM9rvpzDZZodarTfxvvj + p5M04AjBulsulsu5YSzncIF+0xcLbZ/Yo8FtsXTQF+yq5ez1gdXXcEMTRWVR9jIR9GnMVamAeKbo + aapxZjzHjHEH6+SrtKeErJQ/fF1VDgq4B0+Z4kxSLZ7gdqdDij9+2GUSXzgrZPQb7MlkRgXkETTB + iBsCViAUpmIvHW0ppDwVrNgTvLfNAsw5sJ+GKxPF8zncFtpyqgL0OVOZ2LdwQetkA1IsyrF/It48 + yR2om4b9Hv3AkeCGsEQLVsIRUOGPtKqicB2VQuDekJuTridNDotKMpUxKbcYuNvvX1rDeAAPR/DJ + Invyr+bwe2W/gb6kBx1O5b2397CDHjlSBznlGrCw7mx2RvrttfEYa2TdNfsRm68Kr+qkDqxLGmGo + sSvUiTE5SEpFjPE71qu6mgijV94SdCy+ikzJKcIWy6RqDqjmqD3l13P4tcaOY9/CdYS3uXCwpTxr + 4JNpNaaogwCubZdYBQLW6IfmpLWCPKm2pQIvlgsYOQQTiyOs99CmPZqO9auMqKagqGU8pUw9bilq + LQRrLiP8Sfc1McIlZlYMc/gwsIDHkUCGVDTsDtwQXrx8AhIic8WWREeKuge+oOiHEctm6vzNHK7H + jF7/Z1grkKJwR4U6QBDuI6/ZowFxb9yehoa94ZNPshOlsYFsG+ptusFWqoARttNqSd6Opz9scOnp + Gz8IfH9z815+2M9fUwrydaJHh5xs/v6mPWf/nZiwHRUZODcwoMCKvN21FMmUN95jEj3u4L6DmVl+ + mtW64EgPqWzkpEvJaF6hiKtgQSe7pwl8IbtLp2aGnB4m+VY7CFOn4djp3jAQeEPw7vbD7BxGs7bd + Cptm7I5mmbsv/zRONOW7QigputZR7O60lugOH4Tuq/nYtbGG0Lg6/Ze1j45jrnqnaUNRXLtc/LRo + XKp6+nh2/vOXL/8BAAD//wMABqZyviwHAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22ae419e4532cc-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:53:26 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:53:20Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:53:26Z' + request-id: + - req_01B7UWY6NpA4GBZ9DRu9YWdS + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:53:11.070630+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SRXWvbUAyG/4rQzW6Oi5ukKfPN2AalLWWMURhlDHPso8RebSk+0qHxgv/7sNPR + 7Uro4331IJ2wDVhgr/syv/x6fwxfwup5qId6M1zfbNpqPX5DhzYeaJ4iVb8ndBilmwtetVXzbOiw + l0AdFlh3PgXK1tlVpsJMlq3y1SbfrnJ0WAsbsWHx4/TX1Og4y5dQ4KOAZ32hCNa0CkMitVYYfF2n + 6I260cHdu64DJgpgAko+1g3sZFYQ9KIG6ZCZZMEbQcs7ib0/e1SSDB487z83vmXwHKA1hZ0kDhQv + 4IEMeoIgYI23xXOUdIGTe6MV6cqk8w2Ww815KvPLq5tfcnza7l+q6uNtHKqnT4/H3yM6ZN/PujPm + rOJDMixOOCSKIxb4vRFodYF/BQHZvVF+wGn66VBNDmUkr8L/UywNpSER14QFp65zmJY3FafzttLk + mVix2Ky3DiXZv7X319P0BwAA//8DAHnV818FAgAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22aee2bec36992-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:53:48 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:53:46Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:53:48Z' + request-id: + - req_01Jk8NvFFDybK94i9Qz4cRk5 + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "Who is the founder + of LangChain?"}, "id": "toolu_015FjoxY6gwbbAHrqbYBTxzy"}]}, {"role": "user", + "content": [{"type": "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_015FjoxY6gwbbAHrqbYBTxzy", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:53:13.741829+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RUYW8bNwz9K4RQYBtw9mynKTp/a4NhyZagxZYOGNYhoHW8O8068ipKzg5B/vtA + xUHdDvtkQ0e+9/j4pAcXWrd1o/Z3q/X58JrO1r+vf54vflnvfvpjg+v1+aVrXJ4nsipSxZ5c45JE + O0DVoBk5u8aN0lJ0W+cjlpYWZ4vzhQoz5cVmtXm5erVZucZ54Uyc3fbPh2fQTP9Ye/3Zureo1IIw + 5IFACZMfIJGWmLWBK/DIMCU5hJZglgL3IQ+1NHAnacQchAF3UnI97aRwSwmkg2vk/mLAwNuP/JFv + /+cjBIVLTCmoMFwMqLSEG0kEOpEPXfAY41wR1suvCq3VOL0snpGRW7j48d0XDEtr3izh8llAe8rO + EDETbFabzRJuK9o4Ic9wjwrSdcEHUwARC/uBWkAFZJCJeKFSkiez52/y2bDe+Sw7Sk9wxnu2hPcp + SIIsoBlTDtx/pm++nuhe0t44MiCM6IfABJEwsbXV/jKBWUIt/Cq7ohmuOFOMoSf21MD9QIlgsK62 + mnNzDZlwrGJeLo80gw0BO/T7PpklJl0z5qA5eK0umg0lUwL1waChSzKa3AOmFj5wOFDSkOcKfL48 + sfRTCX4fZ+gxMLUwyVQippBnYzFJb64MfSwc8txUst0Mb6YUovl21hyX+rSGAVtIGCyjGnquieAM + XeE2cF9XNoOSL8lSfKAELzYrGEOMFszAz5VPnh4wlqfISmcnkVCzdXxuqXP+Rp+KBIQLnELG2EAn + Mcp9xYEX68/VSmTuHUjzSKbLut8S+2HEtK/mvFrChxrO6v03aqQtJR3C1JzYZjvZkZeRQJhMn9kw + iuZnCwF74rzYlRDrRF3CkSwxeuKsTmg56MQXtSJhGHFv/0IGQp0tiS0dKMpU2Qv2BDf2lABOUwy+ + +qNwbD/J+fs5D8Lf384TqU9hyjCh32NPWuf87+X0whpass3gl9sLfUl0opq86KyZxgYmuyTe5o0z + dJJgCFrvhZV7e9/88RKlnr6aQOHb6+sb/e74SonEpzCjrzPZ9Qh+OFpNFo8uFuIcMD6LOVpTlymd + aauuH3FODFq6x78ap1mmu0Sowm7riNu7XBK74welT8Uuj9tyibFxpT7m2wcXeCr5LsueWN12s3q9 + apyU/MXhD68fH/8FAAD//wMA0hFTIS0GAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22aef3a88e32c6-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:53:54 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:53:49Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:53:54Z' + request-id: + - req_01HxWKuEjZTokR87xSj3FNGK + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:54:04.768568+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1RRW0/bUAz+K5Zf9nKCQluYdt42hLisSIUxBJpQdEjcNuPEbmMfrVGV/46SbmI8 + Wb58F33eY12hx0ZXRX789fP5nV3Wi+ty3j38/P14Kbtvtzt0aN2GhitSDStCh63EYRBUa7XAhg4b + qSiixzKGVFE2zU4yFWaybJJPZvnpJEeHpbARG/pf+3+kRrsBPhaP9wKB9Q+1YOtaYZtIrRaGUJap + DUaxc3D1KUZgogpMQCm05RqWMiAIGlGDtMlMsioYQc1LaZtw4HiRZDAPvDpbh5ohcAW1KSwlcUXt + EczJoCGoBGwdbOTsJB1h797disQi6ZDBGNzQpyI/nun317Pbxd3DzeLx4ub6/iKdPP1o0CGHZsAd + bA4o3iRDv8dtorZDj3/VQZbv1rDvnx2qyaZoKajwR+VxobRNxCWh5xSjwzS+xu8PCoXJK7Gin01P + HUqy/2dfpn3/BgAA//8DABCOjgv5AQAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b0325b034cb6-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:54:42 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:54:40Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:54:42Z' + request-id: + - req_01SsSvCQgy3vQfuMCiJiaZLM + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "founder of LangChain"}, + "id": "toolu_014sKkCQPRVMPXGMJTGu5YSm"}]}, {"role": "user", "content": [{"type": + "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_014sKkCQPRVMPXGMJTGu5YSm", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:54:07.180606+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3SUW28bNxCF/8qAKNAWWKmyfEmjt8QIHAN2YjTOS5rCGHFnVxNxhzQvUreG/3sx + lFTbafskLXdn5juHh3ww3JqFGVJ/Nzt6ffplLvdXR/2Hi7f9xYf1uy/fxr8G05g8BtKvKCXsyTQm + eqcLmBKnjJJNYwbfkjMLYx2WlibHk9NJ8iKUJ/PZ/GR2Np+ZxlgvmSSbxe8Ph6aZ/tTy+rMwbzFR + C14grwgSYbQriJSKy6mBS7AoEKLfcEsw+gJbzitg6XwcMLMXwKUvuRZ3vkhLEXwHVyj9+QpZFl/l + q9z+z0vgBO8xRk5e4HyFiaZw7SNBCmS5Y4vOjbXD0fS7D7VUZ1o/OXRGaeH83ccXE6ZaPJ/C+wNA + +3y6gMNMMJ/N54AJUMAHkknyJVpS1d/IZtiu2BFsfVyz9IAZEAa0KxYCRxhFV1PGmEsAJaYWfvPL + kjJcSibnuCexVEmOp3v6lY6DJdp1H5VLWVLGzCmzTVWK9UMomSIky9oAuugHdWGDsYXPwhuKifNY + G59M4Sayj5D9jkWh/lHawEpZ2+rY9Z796sCeCQeV9R/QjYq3K+i8LYeUUKrNFXGDjttdCnz3b1Nq + PlPlO50+8/2+sF27EXpkoRaCD8Vh5DyqC4r45lLVD0U4j02dxAJvQmSne3Xc7Hd+CCgjRGRFS9xL + jYxk6Iq0LH0DLNYV/Qt+QxF+mM9gYOcUuLr5ie6LZ4RzDJzRVdSz6cuArsVvBTofgXN6EZCbMa+8 + /HI7Bko2csgQ0K6xJ42m5oQH3Y8B1wScgTCN+tzShpwPdUrBnuBafQIMwbGtbu48ezV9irr1kril + SC2gBnNgIRXKfYn0zDbsdZmsT2PKNDQQNAtW/XVjFWH1hrD7fMSevsNI8NPV1XX6eXfOs/dul0a0 + T2AvT+KPCTac1NJa8uSdZnxJJDWTPATH3Vg5Q/SWUtLM2Ei4i9Mz9RD8tkpdjuAqozsw7iIFjtcE + Fze3k5NG7dUGnAGttuWlIx2JsFXHIKL0pLP2vlPcKYq0u+z0+cnAjsm1U/P4R2NS9uEuEiYvZmFI + 2rtcopj9i0T3RY+IWUhxrjGl3tOLB8MSSr7Lfk2SzGI+e3XWGF/yi8VfXz8+/g0AAP//AwD0aedG + CAYAAA== + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b04158a84cb1-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:54:48 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:54:42Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:54:48Z' + request-id: + - req_01Ty6RLDuDeMCfmVfunAmMKp + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:54:32.018816+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SRTW8TQQyG/4rlC5fZapO0KZ0LEqAWlB4Q4UMBodWw42QXdu1k7KGNov3vaDeg + wsnyx/v6kX3CNqLHXndVObtpZ6vHu+cPH3/Zdbh7t9ysbjerNTq0457GKVINO0KHSbqxEFRbtcCG + DnuJ1KHHugs5UrEorgoVZrJiXs4vy+W8RIe1sBEb+q+nv6ZGj6N8Ch4/CATWB0pgTatwyKTWCkOo + 65yCUXd08PZZ1wETRTABpZDqBrYyKgh6UYO8L0yKGIyg5a2kPpw9vks2uA+8e9WEliFwhNYUtpI5 + UrqAezLoCaKANcEmz6PkCxzcE61IV2UdbzAdbsxzVc7WdBVvfzRfXu7im0+vZ+v3i2aeNuiQQz/q + zpijivfZ0J/wkCkd0ePnRqDVCf4PCMj2ifIFDsM3h2qyrxIFFf6fYmooHTJxTeg5d53DPL3Jn87b + KpOfxIr+crF0KNn+rd1cD8NvAAAA//8DAPfUzMAFAgAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b0dcbd844caf-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:55:09 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:55:07Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:55:09Z' + request-id: + - req_018TP5XzbuQ82VrDc4fbETo3 + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "Who is the founder + of LangChain?"}, "id": "toolu_01Se5dFjhZBgdHVD1SR3h2rY"}]}, {"role": "user", + "content": [{"type": "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_01Se5dFjhZBgdHVD1SR3h2rY", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:54:34.582739+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA2yUYW8bNwyG/wpxGLANOHuuk3SAv2VGsWZz0CLLgA7rENA6+k61jlQkyq5R9L8P + lJ0sSffpAB1FPnz5Ul8a3zWLZsz93ezVm9vzv8ryfjVf9mfp5mJz8+HD+NvvTdvoIZJFUc7YU9M2 + SYIdYM4+K7I2bTNKR6FZNC5g6WhyNrmYZGEmncxn8/PZ6/msaRsnrMTaLP7+8pBU6bNdr59Fczsg + b+EgBTaSICbZ+c5zDyPB3usAOhBIjJK0sNcDqEAmTG6o8fZ3lKxQ4kRl0qESeN5IGlG98BR+wUwd + CNfI08VEuQTNLVyBQwYnvPEdsYYDIOc9JcNJcF8oW5LFR/7ItwPBRgp3lEA2sELulwN6Bp/hLabk + szAsB8w0reFyasXwEkGV4bPWVK+mL25YDhaFTyVr5TwVamFdjgdOJg/FkTtYvnn3DKKWnE+fUO0x + Q8DCbqAOPMM7p7KmBPPZfA6YARkkEk+ylOTIWD+RU1gfXqLtBx8IBqoZ95K2NhtUQBjRDZ4JAmFi + O82KSUsEhyFQBzeytn6uWCkE3xO7ozRn01PqwThgjW7bJ2vOOLOi+qze5dqokzEWpQTZeUsAmySj + Ie4wdfAn+x2l7PVQE59P4X3yZgo5shjUoyStNWFcpuf1iX31wK6Eo7X1P9CtaVDt5sqDl8wYpgN3 + sMPgu2o2G8k3otQlyZXv4umA7ot323CAHj1TB1FiCZjM4P7o1csr636spm8BVRO6WnQo3CXqspUz + WyW/LiopG9ivXt+WdeVak5OxQsLedxQO0PnsSrYWVKJ3Fr/D5KVkiAHVluYI+noKVwyXMflghjlr + n9qdnaQoCZW6aqQ6I+RDLZrQW/7se/Yb75AVNoVtn1u7GUpdbdlRgu/mMxh9CCZcneofdF/EIywx + esXQmrKlzqYuwKmImrRZ7frj/Qr98xRu/wv8PsNouHVotl/y3PDvDzoI/3R7iJRd8lEhottiTxl0 + MHv70Ww04pbAKxDm+vJ0tKMgsepRsCe4tvECxhi8qybILUSznrNxhsdpJsIw2sQurwB7Yj36e7W6 + hh9WmHp6kfJHiLKnZBo/yV0b/fbtcMLZd8do2PnshTE9NVKOaD5+Bmav5+CPO22hzp7uo8NWq+t8 + enxFwpHUzFe7G3H7bK9AmKyxx5f4ZGXYJBzJsudaa118qNN/VGDafP2nbbJKvEuEWbhZNMTdnZbE + zelHpvtiS9gs/lVeaU6OjlIpuDqyqlbKzCsoLYkvyc9OzStWsjIysDDQUcovLUEWNDY2q60FAAAA + //8DAHryTTDvBgAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b0ec98794caf-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:55:17 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:55:10Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:55:17Z' + request-id: + - req_011iT4S1koK6LtTJefPZgpb9 + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:54:53.846143+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1RRS2vcQAz+K0KXXsbBu5um6dySQCAQckiXPijFTD3y2slYsmc0JO7i/17sbUl7 + Enp8Dz4dsfNosU+HqtxsPrdPfjNcf2A3Svvl5uGXxKtLNKjTQMsVpeQOhAajhGXgUuqSOlY02Iun + gBbr4LKnYle8L5IwkxbbcnteXmxLNFgLK7Gi/X78S6r0usDXYnEv4Di9UARtuwRjpqSdMLi6ztEp + hcnA3bsQgIk8qEAiF+sWGlkQBL0khTwUKoV3StBxI7F3J46fkhXuHR9uWtcxOPbQaYJGMnuKZ3BP + Cj2BF9DW6co5ST7D2by5FQlVTksGa3BLn6tyczt9uv76bV9f7X39eCnj+Hpo+hc0yK5fcCebC4qH + rGiPOGaKE1r8ow7SvFnDef5hMKkMVSSXhP9XXheJxkxcE1rOIRjM62vs8aRQqTwTJ7TnuwuDkvXf + 2cfdPP8GAAD//wMAgzLCHfkBAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b1653bb54cb6-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:55:32 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:55:29Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:55:32Z' + request-id: + - req_01RU57s1YVTHykXPQ2qnPNr4 + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "founder of LangChain"}, + "id": "toolu_01FySBXYTcATdcR8oqqxgfmw"}]}, {"role": "user", "content": [{"type": + "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_01FySBXYTcATdcR8oqqxgfmw", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.\n\nSystem time: 2024-11-13T23:54:56.977251+00:00", + "tools": [{"name": "search", "description": "Search for general web results. + This function performs a search using the Tavily search engine, which is designed\nto + provide comprehensive, accurate, and trusted results. It''s particularly useful\nfor + answering questions about current events.", "input_schema": {"properties": {"query": + {"type": "string"}}, "required": ["query"], "type": "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RVbW/bRgz+K4RQYBsge4nTppu/tdnQZEnQbnWHDesQ0CdaYn3iXY48p1rR/z6c + bDd2h30yJB3Jh8/L+VPFTTWvem3vTk6vf/1w+ePpu9D8IYvww0eJv5xd/35d1ZUNkcopUsWWqrpK + wZcXqMpqKFbVVR8a8tW8ch5zQ5OzybOJBhGyyexk9vTkfHZS1ZULYiRWzf/6tG9q9LGUjz/z6iUq + NRAErCNQwuQ6SKTZm9ZwBQ4FYgobbgiGkOGBrQOWVUg9GgcBXIZsY/EqZGkoQVjBDUp70SHL/L28 + l8X/fARWuMSUWIPARYdKU1gEWBL0IRHERI6VauionCwjXJjsG6E0cPHz66OGU7ikRICJQENPsKYB + YmAx3cE8njb2OIZ6Ov36zJeRzSFwAY9GMDuZzaalbjY9XgvBhT6iDFCYogYwlSYQIslEQ06O4M1g + XZDvF0Okty5xNIjo1thS2RUNkHuwAD2uCdiAUIfy3NCGfIjjvIwtwW3xAWCMnt2oiY6QzqbwJnFI + pUYNk7G0jyDrr/acvxcAmMANNSPTtzdghD2gwW9hmdXgSoy855bEUQ0ocHvzOurBni7vnUQ6Div0 + btBzs3VKWEGPrmMh8IRJypHRwwXv0XASYxvAs6zLoT2QaxLtwrjc0+lOng4L2Ut063bLMEvZ1liN + nY4QCsJslEAdF/CwSqEv628wNfBOeENJ2Yax8bNDIe8zu7UfoEUWaiCGmD2mAo23eXlxVbr3WdiG + epuNErjEyzwKsR3VZWkSNVoY2KlHSQtTr9gu87IGdMYbgobVZdWxMggsHthsZ/WfWF1ITT0+SO4p + hazwZ8iLvCSwbCEx+q3y51O4EngRE/vi0LP6yLkupBgSWjFl4W70Ro5j44RcNFRuhVfsUAxWWRqW + du+P1xtK8GR2Aj17X1Rl2R8pEmERPH/RG61IrVYqHktGUjYklhPBilMPb+k+B0a4wMiGfu+HRccK + DnuCD8WBCA9Ea8DVlhQJWdw4F56cPnZXouKCDan1VBYo016SuK7HtB4Jev6flLNCIhda4X/2vJTL + Y8VtwfioNralJbmggxr1NcQSLFds4cesF/2F3C5sqaWvcqrw7c3NrX63NYuF4LcmLQ7YJ/cgpNu7 + 7hjsN1pIbShpx7EeE7AkV+473FsUVgl7eghpPWJaZvZbhQ4uCYjhgRI1sBzAj0j9Huk2leB5TfDq + zWLytAYSXPrS4sDAFsAlKvfgi6vJvhvGqNv7m1DZD+N2uyBNq89/15VaiHeJUINU84qkubOcpNp9 + ULrPJaXVXLL3dZXHP8D5p4olZruzsCbRaj47eX5eVyHb4cuz5+efP/8LAAD//wMAjIY6NGEHAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b178d9d04cb4-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:55:39 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:55:32Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:55:39Z' + request-id: + - req_015HcqrgirHzeLfD6UpmyE4r + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.", "tools": [{"name": "search", "description": "Search + for general web results. This function performs a search using the Tavily search + engine, which is designed\nto provide comprehensive, accurate, and trusted results. + It''s particularly useful\nfor answering questions about current events.", "input_schema": + {"properties": {"query": {"type": "string"}}, "required": ["query"], "type": + "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SR3WrcQAyFX0Xopjfj4HWzKZm7JrTQsimhBAIpxcza8nqoLe2ONLRm8bsXe1vS + Xgn9nKMP6YyxRY+jHupy8/jxOcfbB9me7qqc08uXd81Ee3Ro05GWKVINB0KHSYalEFSjWmBDh6O0 + NKDHZgi5peJtsS1UmMmKqqyuy5uqRIeNsBEb+m/nv6ZGvxb5Gjw+CQTWn5TA+qhwyqQWhSE0TU7B + aJgcfHozDMBELZiAUkhND50sCoJR1CAfC5OiDUYQuZM0hovHXrLBLvDhvg+RIXAL0RQ6ydxSuoId + GYwErYD1wVbPSfIVzu6VVmSosy43WA+35LkuN7v3U5zuuj1/eHmi/ePnr4ftw31ChxzGRXfBXFR8 + zIb+jKdMaUKPz71A1BX+DwhI90qJ8/zdoZoc60RBhf+HWBtKp0zcEHrOw+Awr1/y58uy2uQHsaK/ + 3lQOJdu/tdubef4NAAD//wMAEAehhwQCAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b64a18a54caf-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:58:52 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:58:49Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:58:52Z' + request-id: + - req_01JfEnoQAvmWG3PFoZTmVtmb + via: + - 1.1 google + status: + code: 200 + message: OK +- request: + body: '{"max_tokens": 1024, "messages": [{"role": "user", "content": "Who is the + founder of LangChain?"}, {"role": "assistant", "content": [{"text": "To answer + this question accurately, I''ll need to search for the most up-to-date information + about LangChain and its founder. Let me do that for you.", "type": "text"}, + {"type": "tool_use", "name": "search", "input": {"query": "Who is the founder + of LangChain"}, "id": "toolu_01LAyiyBfbnEZTebPJRg5MCr"}]}, {"role": "user", + "content": [{"type": "tool_result", "content": "[{\"url\": \"https://sfelc.com/speaker/harrison-chase\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://aiconference.com/speakers/harrison-chase/\", + \"content\": \"Harrison Chase is the co-founder and CEO of LangChain, a company + formed around the open-source Python/Typescript packages that aim to make it + easy to develop Language Model applications. Prior to starting LangChain, he + led the ML team at Robust Intelligence (an MLOps company focused on testing + and validation of machine learning models), led the\"}, {\"url\": \"https://tedai-sanfrancisco.ted.com/speakers-1/harrison-chase-/co-founder-and-ceo,-langchain/\", + \"content\": \"Harrison Chase, a Harvard graduate in statistics and computer + science, co-founded LangChain to streamline the development of Language Model + applications with open-source Python/Typescript packages. Chase''s experience + includes heading the Machine Learning team at Robust Intelligence, focusing + on the testing and validation of machine learning models, and leading the entity + linking team at Kensho\"}, {\"url\": \"https://techcrunch.com/author/harrison-chase/\", + \"content\": \"Harrison Chase is the CEO and co-founder of LangChain, a company + formed around the open source Python/Typescript packages that aim to make it + easy to develop Language Model applications\"}, {\"url\": \"https://www.sequoiacap.com/podcast/training-data-harrison-chase/\", + \"content\": \"Sonya Huang: Hi, and welcome to training data. We have with us + today Harrison Chase, founder and CEO of LangChain. Harrison is a legend in + the agent ecosystem, as the product visionary who first connected LLMs with + tools and actions. And LangChain is the most popular agent building framework + in the AI space.\"}, {\"url\": \"https://www.youtube.com/watch?v=7D8bw_4hTdo\", + \"content\": \"Join us for an insightful interview with Harrison Chase, the + CEO and co-founder of Langchain, as he provides a comprehensive overview of + Langchain''s innovati\"}, {\"url\": \"https://www.forbes.com/profile/harrison-chase/\", + \"content\": \"Harrison Chase only cofounded LangChain in late 2022, but the + company caught instant attention for enabling anyone to build apps powered by + large language models like GPT-4 in as little as two\"}, {\"url\": \"https://en.wikipedia.org/wiki/LangChain\", + \"content\": \"In October 2023 LangChain introduced LangServe, a deployment + tool designed to facilitate the transition from LCEL (LangChain Expression Language) + prototypes to production-ready applications.[5]\\nIntegrations[edit]\\nAs of + March 2023, LangChain included integrations with systems including Amazon, Google, + and Microsoft Azure cloud storage; API wrappers for news, movie information, + and weather; Bash for summarization, syntax and semantics checking, and execution + of shell scripts; multiple web scraping subsystems and templates; few-shot learning + prompt generation support; finding and summarizing \\\"todo\\\" tasks in code; + Google Drive documents, spreadsheets, and presentations summarization, extraction, + and creation; Google Search and Microsoft Bing web search; OpenAI, Anthropic, + and Hugging Face language models; iFixit repair guides and wikis search and + summarization; MapReduce for question answering, combining documents, and question + generation; N-gram overlap scoring; PyPDF, pdfminer, fitz, and pymupdf for PDF + file text extraction and manipulation; Python and JavaScript code generation, + analysis, and debugging; Milvus vector database[6] to store and retrieve vector + embeddings; Weaviate vector database[7] to cache embedding and data objects; + Redis cache database storage; Python RequestsWrapper and other methods for API + requests; SQL and NoSQL databases including JSON support; Streamlit, including + for logging; text mapping for k-nearest neighbors search; time zone conversion + and calendar operations; tracing and recording stack symbols in threaded and + asynchronous subprocess runs; and the Wolfram Alpha website and SDK.[8] As a + language model integration framework, LangChain''s use-cases largely overlap + with those of language models in general, including document analysis and summarization, + chatbots, and code analysis.[2]\\nHistory[edit]\\nLangChain was launched in + October 2022 as an open source project by Harrison Chase, while working at machine + learning startup Robust Intelligence. In April 2023, LangChain had incorporated + and the new startup raised over $20 million in funding at a valuation of at + least $200 million from venture firm Sequoia Capital, a week after announcing + a $10 million seed investment from Benchmark.[3][4]\\n The project quickly garnered + popularity, with improvements from hundreds of contributors on GitHub, trending + discussions on Twitter, lively activity on the project''s Discord server, many + YouTube tutorials, and meetups in San Francisco and London. As of April 2023, + it can read from more than 50 document types and data sources.[9]\\nReferences[edit]\\nExternal + links[edit]\"}, {\"url\": \"https://analyticsindiamag.com/people/harrison-chase/\", + \"content\": \"By AIM The dynamic co-founder and CEO of LangChain, Harrison + Chase is simplifying the creation of applications powered by LLMs. With a background + in statistics and computer science from Harvard University, Chase has carved + a niche in the AI landscape. AIM Brand Solutions, a marketing division within + AIM, specializes in creating diverse content such as documentaries, public artworks, + podcasts, videos, articles, and more to effectively tell compelling stories. + AIM Research produces a series of annual reports on AI & Data Science covering + every aspect of the industry. Discover how Cypher 2024 expands to the USA, bridging + AI innovation gaps and tackling the challenges of enterprise AI adoption AIM + India AIM Research AIM Leaders Council 50 Best Data Science Firms\"}, {\"url\": + \"https://www.turingpost.com/p/harrison-chase-langchain-ai-agents\", \"content\": + \"Harrison Chase, founder of LangChain, shared insights on the evolution of + AI agents and their applications during Sequoia Capital''s AI Ascent. ... Saves + you a lot of research time, plus gives a flashback to ML history and insights + into the future. Stay ahead alongside over 73,000 professionals from top AI + labs, ML startups, and enterprises\"}]", "tool_use_id": "toolu_01LAyiyBfbnEZTebPJRg5MCr", + "is_error": false}]}], "model": "claude-3-5-sonnet-20240620", "system": "You + are a helpful AI assistant.", "tools": [{"name": "search", "description": "Search + for general web results. This function performs a search using the Tavily search + engine, which is designed\nto provide comprehensive, accurate, and trusted results. + It''s particularly useful\nfor answering questions about current events.", "input_schema": + {"properties": {"query": {"type": "string"}}, "required": ["query"], "type": + "object"}}]}' + headers: {} + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RU0W4bRwz8FeIQoC1wEmQ5KVC/2UZQu5DQNEmf6iKg9nh3jPa45yVPqhD03wuu + LEdx26cFdpfkcGbILxU31VU1aPdpcbFu9q8vfnv/y0/j5frx1j7vbldv391UdWWHkfwXqWJHVV3l + FP0CVVkNxaq6GlJDsbqqQsSpodnl7M1MkwjZbLlYvl78uFxUdRWSGIlVV398OSU1+svDy3FV3aBS + A0nAegIlzKGHTDpF0xruIaBASNJyQ2LxACi6pwyHNGV4nEiNkwBu0mQlQZsmaShDamGF0t32yDJ/ + kAf5+D+PwAp3mDNrErjtUWkO65QJdKTALQeM8XDlGS7mLz56qNcMaXbKjNLA7dtfX5ZfzuHuVL45 + ry0Q0QiWi+WyBhY29nKACiiQRpKZpikHgjGnzxQM9j1Hgn3KW5YO0ABhwNCzEETCLH6rhtmmERw6 + NfA+bSY1uBejGLkjCTR/kMv5GY49KoRMaNSAJRhwS8AGhMqU/aahHcU0Ao5j5IBOusKY9pSpgc0B + Vpg7Kgkn7AjW7gyF71ertf5Q6P+QBgJsGvZQjMBilF096aDFYPqk4QuGnc9nmCcVjk+9kwQbDNsu + O7FOphoaq3HQEhnSME5GGTSwtw1tToOX2GFu4HfhHWVlOxwVepc5lWYLfw7suXINvfPbFLnXT3yv + Tnwb4eBS/AfRNbQpTOq/3OFPDTu2HUZuCpFuln9pWGZLX+r0OHHYxgN0yEINjGmcIma2gzfv2K7v + velhErZDDXu23ofHMm+mo2aFgX6SJlOjXvlJWcrqCH9mu5s2BSAG4x1Bwxom1RKcBHaYOU0KY0Rr + Ux4c4us53Atcj5mjO/my/sbhIeUx5WKtopiLgnIoNTKyD79yJ2XUxKCdpGHp5vCxpwNkCsQ73w87 + yvBquYCBY3TSWE5fj1Owwzg904nmTKp5xNeQ0vsHepwSI9ziyIaxBu1TLoultTLAkiYJJS28uvga + rERusR2pDeQ4PdkNSegHzNv5g7w5F8rNuaHgpk9CDsnVGZLaSTTAjsRmm4ljaaLNOJDPtZ5pqSO6 + h7aS9gJtyi6mUDiac7XWo8KWUtRnzZJombhvJ+k7hR27iseQl0hJivF5GCO3h1J+zCmQFo+U1eA1 + r+9np6E/3wS1bwx/Z4PBdycGD+VNpLPl4RZzkBufB1IlBYy8pXn19591pZbGT/kfpSYW5+cpWSml + 5qXEl5QW5SlBJYpTC0tB+UnJKq80J0dHqRRcK1lVK2XmFZSWxJfkZ6fmFStZGRmYmuoo5ZeWIAsa + mxjW1gIAAAD//wMALzV23/YGAAA= + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 8e22b65b6bda4caf-PHL + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Wed, 13 Nov 2024 23:58:59 GMT + Server: + - cloudflare + Transfer-Encoding: + - chunked + X-Robots-Tag: + - none + anthropic-ratelimit-requests-limit: + - '4000' + anthropic-ratelimit-requests-remaining: + - '3999' + anthropic-ratelimit-requests-reset: + - '2024-11-13T23:58:52Z' + anthropic-ratelimit-tokens-limit: + - '400000' + anthropic-ratelimit-tokens-remaining: + - '400000' + anthropic-ratelimit-tokens-reset: + - '2024-11-13T23:58:59Z' + request-id: + - req_013QF9W14BVn6VHs6PZwfFs7 + via: + - 1.1 google + status: + code: 200 + message: OK +version: 1 diff --git a/services/orchestrator/tests/conftest.py b/services/orchestrator/tests/conftest.py new file mode 100644 index 0000000..e58f5b7 --- /dev/null +++ b/services/orchestrator/tests/conftest.py @@ -0,0 +1,198 @@ +"""Shared pytest fixtures for orchestrator tests. + +Provides reusable fixtures for Context, Runtime, State, graph compilation, +and live service connections (MongoDB, DB Toolbox, Ollama). +""" +import os +from pathlib import Path +from unittest.mock import MagicMock +from uuid import uuid4 + +import orjson +import pytest +from langchain.tools import ToolRuntime +from langchain_core.messages import HumanMessage +from langchain_core.runnables import RunnableConfig +from langgraph.checkpoint.memory import MemorySaver +from langgraph.runtime import Runtime + +from react_agent.constants import AvailableModel, FrameworkEnum, TranslationType +from react_agent.context import Context +from react_agent.graph import graph +from react_agent.state import State + +FIXTURES_DIR = Path(__file__).parent / "fixtures" + +# --------------------------------------------------------------------------- +# Pytest Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(scope="session") +def anyio_backend(): + return "asyncio" + + +@pytest.fixture(scope="session") +def vcr_config(): + return { + "filter_headers": ["authorization"], + "ignore_localhost": True, + "record_mode": "once", + } + + +@pytest.fixture(scope="module") +def check_api_keys(): + if not os.environ.get("OPENAI_API_KEY"): + pytest.skip("OPENAI_API_KEY not set") + +# --------------------------------------------------------------------------- +# Context & Runtime +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def context() -> Context: + """Create a Context wired to real endpoints (reads from env / defaults).""" + return Context(model=AvailableModel.EINFRA_MINI) + + +@pytest.fixture() +def runtime(context: Context) -> MagicMock: + """A mock Runtime whose `.context` points to the real Context.""" + rt = MagicMock(spec=Runtime) + rt.context = context + return rt + + +@pytest.fixture() +def runnable_config() -> RunnableConfig: + """A minimal RunnableConfig for node invocations.""" + return RunnableConfig() + + +# --------------------------------------------------------------------------- +# Graph +# --------------------------------------------------------------------------- + + +@pytest.fixture() +def compiled_graph(): + """Return the default compiled graph (no checkpointer).""" + try: + return graph + except Exception as exc: + pytest.skip(f"Graph build unavailable for integration tests: {exc}") + + +@pytest.fixture() +def compiled_graph_with_checkpointer(): + """Return a freshly compiled graph with an in-memory checkpointer.""" + try: + return graph.builder.compile( + checkpointer=MemorySaver(), + name="UOM Orchestrator Workflow (test)", + ) + except Exception as exc: + pytest.skip(f"Graph build with checkpointer unavailable: {exc}") + + +# --------------------------------------------------------------------------- +# Sample States +# --------------------------------------------------------------------------- + +SAMPLE_EFCORE_CODE = """\ +public class Customer +{ + public int Id { get; set; } + public string Name { get; set; } + public ICollection Orders { get; set; } +} + +public class Order +{ + public int Id { get; set; } + public DateTime OrderDate { get; set; } + public int CustomerId { get; set; } + public Customer Customer { get; set; } +} + +// Query +var customers = await dbContext.Customers + .Include(c => c.Orders) + .Where(c => c.Orders.Any(o => o.OrderDate > DateTime.UtcNow.AddDays(-30))) + .ToListAsync(); +""" + + +@pytest.fixture() +def sample_state() -> State: + """A pre-populated State for EFCore → Spring Data MongoDB translation.""" + return State( + messages=[ + HumanMessage( + content=( + "Translate this EFCore code to Spring Data MongoDB:\n" + + SAMPLE_EFCORE_CODE + ) + ) + ], + translation_type=TranslationType.BOTH, + source_target=FrameworkEnum.DOTNET_EFCORE, + destination_target=FrameworkEnum.JAVA_SPRING_DATA_MONGODB, + ) + + +@pytest.fixture() +def empty_state() -> State: + """A State with no source data — forces extract_input to call the LLM.""" + return State( + messages=[ + HumanMessage( + content=( + "Convert this EFCore LINQ code to Spring Data MongoDB:\n" + + SAMPLE_EFCORE_CODE + ) + ) + ], + ) + + +@pytest.fixture() +def sample_tool_runtime(runtime, runnable_config, sample_state) -> ToolRuntime[Context, State]: + # Create the ToolRuntime + tool_runtime = ToolRuntime( + state=sample_state, + config=runnable_config, + context=runtime.context, + store=runtime.store, + stream_writer=runtime.stream_writer, + tools=[], + tool_call_id=f"mocked_call_{uuid4()}", + ) + return tool_runtime + + +@pytest.fixture() +def sample_config_with_runtime(runtime: Runtime, sample_tool_runtime: ToolRuntime) -> RunnableConfig: + # Mock the internal Pregel Runtime + mock_pregel_runtime = runtime + return { + "configurable": { + "__pregel_runtime": mock_pregel_runtime, + "__tool_runtime__": sample_tool_runtime, + } + } + + +@pytest.fixture() +def sample_efcore_results() -> dict: + """Sample EFCore results for testing.""" + return orjson.loads((FIXTURES_DIR / "efcore_results.json").read_bytes()) + + +@pytest.fixture() +def sample_mongo_results() -> dict: + """Sample MongoDB results for testing.""" + return orjson.loads((FIXTURES_DIR / "mongo_results.json").read_bytes()) diff --git a/services/orchestrator/tests/fixtures/efcore_results.json b/services/orchestrator/tests/fixtures/efcore_results.json new file mode 100644 index 0000000..9f407e3 --- /dev/null +++ b/services/orchestrator/tests/fixtures/efcore_results.json @@ -0,0 +1,148 @@ +{ + "query1": { + "count": 1883, + "firstSample": { + "description": "Tape dispenser (Red)", + "lastEditedBy": 19, + "lastEditedWhen": "2014-12-29T11:00:00.000Z", + "orderId": 10417, + "orderLineId": 33036, + "packageTypeId": 7, + "pickedQuantity": 80, + "pickingCompletedWhen": "2014-12-29T11:00:00.000Z", + "quantity": 80, + "stockItemId": 204, + "taxRate": 15, + "unitPrice": 32 + }, + "lastSample": { + "description": "Air cushion film 200mmx200mm 325m", + "lastEditedBy": 16, + "lastEditedWhen": "2014-12-30T11:00:00.000Z", + "orderId": 40554, + "orderLineId": 128234, + "packageTypeId": 7, + "pickedQuantity": 10, + "pickingCompletedWhen": "2014-12-30T11:00:00.000Z", + "quantity": 10, + "stockItemId": 214, + "taxRate": 15, + "unitPrice": 90 + }, + "sqlString": "DECLARE @from datetime2 = '2014-12-20T00:00:00.0000000';\nDECLARE @to datetime2 = '2014-12-31T00:00:00.0000000';\n\nSELECT [o].[OrderLineID], [o].[Description], [o].[LastEditedBy], [o].[LastEditedWhen], [o].[OrderID], [o].[PackageTypeID], [o].[PickedQuantity], [o].[PickingCompletedWhen], [o].[Quantity], [o].[StockItemID], [o].[TaxRate], [o].[UnitPrice]\nFROM [Sales].[OrderLines] AS [o]\nWHERE [o].[PickingCompletedWhen] >= @from AND [o].[PickingCompletedWhen] <= @to" + }, + "query2": { + "count": 129, + "firstSample": { + "customer": { + "accountOpenedDate": "2013-01-01T00:00:00.000Z", + "creditLimit": null, + "customerId": 1, + "customerName": "Tailspin Toys (Head Office)", + "customerTransactions": [ + { + "customerId": 1, + "customerTransactionId": 7, + "transactionAmount": 103.5, + "transactionDate": "2013-01-01T00:00:00.000Z" + }, + { + "customerId": 1, + "customerTransactionId": 11, + "transactionAmount": 511.98, + "transactionDate": "2013-01-01T00:00:00.000Z" + } + ] + }, + "customerId": 1, + "orderId": 73290, + "orderLines": [ + { + "description": "Tape dispenser (Black)", + "lastEditedBy": 18, + "lastEditedWhen": "2016-05-27T11:00:00.000Z", + "orderId": 73290, + "orderLineId": 230477, + "packageTypeId": 7, + "pickedQuantity": 50, + "pickingCompletedWhen": "2016-05-27T11:00:00.000Z", + "quantity": 50, + "stockItemId": 203, + "taxRate": 15, + "unitPrice": 32 + }, + { + "description": "10 mm Double sided bubble wrap 20m", + "lastEditedBy": 18, + "lastEditedWhen": "2016-05-27T11:00:00.000Z", + "orderId": 73290, + "orderLineId": 230478, + "packageTypeId": 7, + "pickedQuantity": 20, + "pickingCompletedWhen": "2016-05-27T11:00:00.000Z", + "quantity": 20, + "stockItemId": 157, + "taxRate": 15, + "unitPrice": 30 + } + ] + }, + "sqlString": "SELECT [o].[OrderID], [o].[CustomerID], [c].[CustomerID], [c].[AccountOpenedDate], [c].[CreditLimit], [c].[CustomerName]\nFROM [Sales].[Orders] AS [o]\nINNER JOIN [Sales].[Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID]\nWHERE [o].[CustomerID] = 1\nORDER BY [o].[OrderID], [c].[CustomerID]\n\nThis LINQ query is being executed in split-query mode, and the SQL shown is for the first query to be executed. Additional queries may also be executed depending on the results of the first query." + }, + "query3": { + "count": 2, + "firstSample": { + "count": 1036, + "taxRate": 10 + }, + "lastSample": { + "count": 230376, + "taxRate": 15 + }, + "sqlString": "SELECT [o].[TaxRate], COUNT(*) AS [Count]\nFROM [Sales].[OrderLines] AS [o]\nGROUP BY [o].[TaxRate]\nORDER BY COUNT(*) DESC" + }, + "query4": { + "count": 50, + "firstSample": { + "description": "Black and orange fragile despatch tape 48mmx75m", + "lastEditedBy": 2, + "lastEditedWhen": "2013-01-31T11:00:00.000Z", + "orderId": 1646, + "orderLineId": 5195, + "packageTypeId": 7, + "pickedQuantity": 360, + "pickingCompletedWhen": "2013-01-31T11:00:00.000Z", + "quantity": 360, + "stockItemId": 191, + "taxRate": 15, + "unitPrice": 3.7 + }, + "lastSample": { + "description": "Black and orange fragile despatch tape 48mmx100m", + "lastEditedBy": 8, + "lastEditedWhen": "2016-05-26T11:00:00.000Z", + "orderId": 73229, + "orderLineId": 230307, + "packageTypeId": 7, + "pickedQuantity": 360, + "pickingCompletedWhen": "2016-05-26T11:00:00.000Z", + "quantity": 360, + "stockItemId": 192, + "taxRate": 15, + "unitPrice": 4.1 + }, + "sqlString": "DECLARE @p int = 50;\n\nSELECT TOP(@p) [o].[OrderLineID], [o].[Description], [o].[LastEditedBy], [o].[LastEditedWhen], [o].[OrderID], [o].[PackageTypeID], [o].[PickedQuantity], [o].[PickingCompletedWhen], [o].[Quantity], [o].[StockItemID], [o].[TaxRate], [o].[UnitPrice]\nFROM [Sales].[OrderLines] AS [o]\nORDER BY [o].[Quantity] DESC" + }, + "query5": { + "count": 231412, + "firstSample": { + "orderLineId": 1, + "quantity": 50 + }, + "lastSample": { + "orderLineId": 231412, + "quantity": 8 + }, + "sqlString": "SELECT [o].[OrderLineID], [o].[Quantity]\nFROM [Sales].[OrderLines] AS [o]" + } +} \ No newline at end of file diff --git a/services/orchestrator/tests/fixtures/mongo_results.json b/services/orchestrator/tests/fixtures/mongo_results.json new file mode 100644 index 0000000..7d9dae2 --- /dev/null +++ b/services/orchestrator/tests/fixtures/mongo_results.json @@ -0,0 +1,185 @@ +{ + "query1": { + "count": 1883, + "firstSample": { + "description": "Tape dispenser (Red)", + "id": "69d7d2bf7d34d59f1cfbbaf1", + "lastEditedBy": 19, + "lastEditedWhen": "2014-12-29T12:00:00.000Z", + "orderId": 10417, + "orderLineId": 33036, + "packageTypeId": 7, + "pickedQuantity": 80, + "pickingCompletedWhen": "2014-12-29T12:00:00.000Z", + "quantity": 80, + "stockItemId": 204, + "taxRate": 15, + "unitPrice": 32 + }, + "lastSample": { + "description": "Air cushion film 200mmx200mm 325m", + "id": "69d7d2c77d34d59f1cfd429d", + "lastEditedBy": 16, + "lastEditedWhen": "2014-12-30T12:00:00.000Z", + "orderId": 40554, + "orderLineId": 128234, + "packageTypeId": 7, + "pickedQuantity": 10, + "pickingCompletedWhen": "2014-12-30T12:00:00.000Z", + "quantity": 10, + "stockItemId": 214, + "taxRate": 15, + "unitPrice": 90 + }, + "mongoQuery": { + "collection": "orderLines", + "filter": { + "pickingCompletedWhen": { + "$gte": "2014-12-20T00:00:00.000Z", + "$lte": "2014-12-31T00:00:00.000Z" + } + } + } + }, + "query2": { + "count": 129, + "firstSample": { + "customer": { + "accountOpenedDate": "2013-01-01T00:00:00.000Z", + "creditLimit": null, + "customerId": 1, + "customerName": "Tailspin Toys (Head Office)", + "customerTransactions": [ + { + "customerId": 1, + "customerTransactionId": 30608, + "transactionAmount": 993.6, + "transactionDate": "2013-05-08T00:00:00.000Z" + }, + { + "customerId": 1, + "customerTransactionId": 30610, + "transactionAmount": 993.6, + "transactionDate": "2013-05-08T00:00:00.000Z" + } + ] + }, + "customerId": 1, + "id": "69d7d2cb7d34d59f1cff3e7d", + "orderId": 73290, + "orderLines": [ + { + "description": "Tape dispenser (Black)", + "id": "69d7d2cb7d34d59f1cfe2f10", + "lastEditedBy": 18, + "lastEditedWhen": "2016-05-27T13:00:00.000Z", + "orderId": 73290, + "orderLineId": 230477, + "packageTypeId": 7, + "pickedQuantity": 50, + "pickingCompletedWhen": "2016-05-27T13:00:00.000Z", + "quantity": 50, + "stockItemId": 203, + "taxRate": 15, + "unitPrice": 32 + }, + { + "description": "10 mm Double sided bubble wrap 20m", + "id": "69d7d2cb7d34d59f1cfe2f1f", + "lastEditedBy": 18, + "lastEditedWhen": "2016-05-27T13:00:00.000Z", + "orderId": 73290, + "orderLineId": 230478, + "packageTypeId": 7, + "pickedQuantity": 20, + "pickingCompletedWhen": "2016-05-27T13:00:00.000Z", + "quantity": 20, + "stockItemId": 157, + "taxRate": 15, + "unitPrice": 30 + } + ] + }, + "mongoQuery": { + "collection": "orders", + "filter": { + "customerId": 1 + } + } + }, + "query3": { + "count": 2, + "firstSample": { + "count": 1036, + "taxRate": 10 + }, + "lastSample": { + "count": 230376, + "taxRate": 15 + }, + "mongoAggregation": { + "collection": "orderLines", + "pipeline": "{ \"aggregate\" : \"__collection__\", \"pipeline\" : [{ \"$group\" : { \"_id\" : \"$taxRate\", \"count\" : { \"$sum\" : 1}}}, { \"$project\" : { \"count\" : 1, \"_id\" : 0, \"taxRate\" : \"$_id\"}}, { \"$sort\" : { \"count\" : -1}}]}" + } + }, + "query4": { + "count": 50, + "firstSample": { + "description": "Black and orange fragile despatch tape 48mmx100m", + "id": "69d7d2be7d34d59f1cfb201b", + "lastEditedBy": 17, + "lastEditedWhen": "2013-01-08T12:00:00.000Z", + "orderId": 433, + "orderLineId": 1136, + "packageTypeId": 7, + "pickedQuantity": 360, + "pickingCompletedWhen": "2013-01-08T12:00:00.000Z", + "quantity": 360, + "stockItemId": 192, + "taxRate": 15, + "unitPrice": 4.1 + }, + "lastSample": { + "description": "Black and orange fragile despatch tape 48mmx75m", + "id": "69d7d2bf7d34d59f1cfbb5c3", + "lastEditedBy": 6, + "lastEditedWhen": "2013-10-24T13:00:00.000Z", + "orderId": 15820, + "orderLineId": 50147, + "packageTypeId": 7, + "pickedQuantity": 360, + "pickingCompletedWhen": "2013-10-24T13:00:00.000Z", + "quantity": 360, + "stockItemId": 191, + "taxRate": 15, + "unitPrice": 3.7 + }, + "mongoQuery": { + "collection": "orderLines", + "filter": {}, + "sort": { + "orderLineId": 1, + "quantity": -1 + } + } + }, + "query5": { + "count": 231412, + "firstSample": { + "orderLineId": 1, + "quantity": 50 + }, + "lastSample": { + "orderLineId": 231412, + "quantity": 8 + }, + "mongoQuery": { + "collection": "orderLines", + "fields": { + "orderLineId": 1, + "quantity": 1 + }, + "filter": {} + } + } +} \ No newline at end of file diff --git a/services/orchestrator/tests/integration_tests/__init__.py b/services/orchestrator/tests/integration_tests/__init__.py new file mode 100644 index 0000000..d02981b --- /dev/null +++ b/services/orchestrator/tests/integration_tests/__init__.py @@ -0,0 +1 @@ +"""Define any integration tests you want in this directory.""" diff --git a/services/orchestrator/tests/integration_tests/test_deepdiff_validator.py b/services/orchestrator/tests/integration_tests/test_deepdiff_validator.py new file mode 100644 index 0000000..824e9ee --- /dev/null +++ b/services/orchestrator/tests/integration_tests/test_deepdiff_validator.py @@ -0,0 +1,179 @@ +import logging +from typing import cast + +import orjson +import pytest +from langchain.tools import ToolRuntime +from langchain_core.tools import StructuredTool + +from react_agent.context import Context +from react_agent.custom_tools.query_validator import check_query_equivalence +from react_agent.state import State + +logger = logging.getLogger(__name__) + + +def _make_validation_output(prefix: str, payload: dict) -> str: + """Build a validation output string with inline JSON payload.""" + return f"{prefix}\\n===JSON===\\n{orjson.dumps(payload).decode('utf-8')}" + + +def _parse_query_equivalence_payload(result: str) -> dict: + """Parse the JSON payload from check_query_equivalence output.""" + prefix = "[Query Equivalence Results]\\n" + assert result.startswith(prefix) + return cast(dict, orjson.loads(result[len(prefix) :])) + + +@pytest.mark.asyncio +async def test_deepdiff_equivalence_exact_match(sample_tool_runtime): + source_json = { + "query1": { + "count": 1, + "firstSample": {"id": 1, "name": "Test"}, + "lastSample": {"id": 1, "name": "Test"}, + } + } + target_json = { + "query1": { + "count": 1, + "firstSample": {"id": 1, "name": "Test"}, + "lastSample": {"id": 1, "name": "Test"}, + } + } + + source_output = _make_validation_output("[Source Query Validation Passed]", source_json) + target_output = _make_validation_output("[Target Query Validation Passed]", target_json) + + sample_tool_runtime.state.source_query_validation_results = source_json + sample_tool_runtime.state.target_query_validation_results = target_json + + func = cast(StructuredTool, check_query_equivalence) + assert func is not None, "check_query_equivalence tool not found" + assert func.coroutine is not None, "check_query_equivalence does not have a coroutine method" + result = await func.coroutine( + source_validation_output=source_output, + target_validation_output=target_output, + runtime=sample_tool_runtime, + ) + logger.debug(f"`test_deepdiff_equivalence_exact_match` result: {result}") + + payload = _parse_query_equivalence_payload(result) + assert payload["query1"]["status"] == "Equivalent" + + +@pytest.mark.asyncio +async def test_deepdiff_equivalence_swapped_samples(sample_tool_runtime): + source_json = { + "query1": { + "count": 2, + "firstSample": {"id": 1, "name": "A"}, + "lastSample": {"id": 2, "name": "B"}, + } + } + + # Target has samples swapped. Current validator compares first/last directly. + target_json = { + "query1": { + "count": 2, + "firstSample": {"id": 2, "name": "B"}, + "lastSample": {"id": 1, "name": "A"}, + } + } + + source_output = _make_validation_output("[Source Query Validation Passed]", source_json) + target_output = _make_validation_output("[Target Query Validation Passed]", target_json) + + sample_tool_runtime.state.source_query_validation_results = source_json + sample_tool_runtime.state.target_query_validation_results = target_json + + func = cast(StructuredTool, check_query_equivalence) + assert func is not None, "check_query_equivalence tool not found" + assert func.coroutine is not None, "check_query_equivalence does not have a coroutine method" + + result = await func.coroutine( + source_validation_output=source_output, + target_validation_output=target_output, + runtime=sample_tool_runtime, + ) + + logger.debug(f"`test_deepdiff_equivalence_swapped_samples` result: {result}") + payload = _parse_query_equivalence_payload(result) + assert payload["query1"]["status"] == "Differences Found" + assert "firstSampleDiff" in payload["query1"]["diffs"] + assert "lastSampleDiff" in payload["query1"]["diffs"] + + +@pytest.mark.asyncio +async def test_deepdiff_equivalence_difference_found(sample_tool_runtime): + source_json = { + "query1": { + "count": 1, + "firstSample": {"id": 1, "name": "Test"}, + } + } + target_json = { + "query1": { + "count": 2, + "firstSample": {"id": 1, "name": "Test"}, + } + } + + source_output = _make_validation_output("[Source Query Validation Passed]", source_json) + target_output = _make_validation_output("[Target Query Validation Passed]", target_json) + + sample_tool_runtime.state.source_query_validation_results = source_json + sample_tool_runtime.state.target_query_validation_results = target_json + + func = cast(StructuredTool, check_query_equivalence) + assert func is not None, "check_query_equivalence tool not found" + assert func.coroutine is not None, "check_query_equivalence does not have a coroutine method" + result = await func.coroutine( + source_validation_output=source_output, + target_validation_output=target_output, + runtime=sample_tool_runtime, + ) + + logger.debug(f"`test_deepdiff_equivalence_difference_found` result: {result}") + payload = _parse_query_equivalence_payload(result) + assert payload["query1"]["status"] == "Differences Found" + assert payload["query1"]["diffs"]["countDiff"] + + +@pytest.mark.asyncio +async def test_deepdiff_equivalence_real_fixture_data( + sample_tool_runtime: ToolRuntime[Context, State], + sample_efcore_results: dict, + sample_mongo_results: dict, +): + """Compare real fixture outputs captured from EF Core and MongoDB validations.""" + # source_real_subset = {"query5": sample_efcore_results["query5"]} + # target_real_subset = {"query5": sample_mongo_results["query5"]} + + source_output = _make_validation_output( + "[Source Query Validation Passed]", + sample_efcore_results, + ) + target_output = _make_validation_output( + "[Target Query Validation Passed]", + sample_mongo_results, + ) + + sample_tool_runtime.state.source_query_validation_results = sample_efcore_results + sample_tool_runtime.state.target_query_validation_results = sample_mongo_results + + func = cast(StructuredTool, check_query_equivalence) + assert func is not None, "check_query_equivalence tool not found" + assert func.coroutine is not None, "check_query_equivalence does not have a coroutine method" + + result = await func.coroutine( + source_validation_output=source_output, + target_validation_output=target_output, + runtime=sample_tool_runtime, + ) + + payload = _parse_query_equivalence_payload(result) + assert set(payload.keys()) == {"query1", "query2", "query3", "query4", "query5"} + statuses = {entry.get("status") for entry in payload.values() if isinstance(entry, dict)} + assert statuses == {"Equivalent"} + assert payload["query5"]["status"] == "Equivalent" diff --git a/services/orchestrator/tests/integration_tests/test_graph.py b/services/orchestrator/tests/integration_tests/test_graph.py new file mode 100644 index 0000000..f5df8e6 --- /dev/null +++ b/services/orchestrator/tests/integration_tests/test_graph.py @@ -0,0 +1,136 @@ +"""Integration tests for the UOM orchestrator graph. + +Tests cover graph structure, individual node invocations (both with and +without real LLM calls), and partial graph execution via LangGraph's +update_state / interrupt_after patterns. +""" +from contextlib import asynccontextmanager +from typing import Awaitable, cast +from unittest.mock import MagicMock, patch + +import pytest +from langchain_core.runnables import RunnableConfig + +from react_agent.constants import FrameworkEnum, TranslationType +from react_agent.graph import ( + extract_input, + schema_inspection, +) +from react_agent.state import State + +pytestmark = pytest.mark.anyio + + +# ── extract_input Node ─────────────────────────────────────────────────────── + + +@pytest.mark.asyncio +class TestExtractInput: + """Tests for the extract_input node function.""" + + async def test_skips_when_state_populated( + self, runnable_config: RunnableConfig, runtime: MagicMock + ): + """When source_code and targets are set, returns empty dict.""" + populated_state = State( + source_schema_code="public class Customer {}", + source_query_code="var customers = await db.Customers.ToListAsync();", + translation_type=TranslationType.BOTH, + source_target=FrameworkEnum.DOTNET_EFCORE, + destination_target=FrameworkEnum.JAVA_SPRING_DATA_MONGODB, + ) + result = await cast(Awaitable, extract_input(populated_state, runnable_config, runtime)) + assert result == {} + + @pytest.mark.integration + async def test_with_real_llm( + self, empty_state: State, runnable_config: RunnableConfig, runtime: MagicMock + ): + """Call extract_input with a real EINFRA_MINI model. + + Asserts structural correctness of the output (valid FrameworkType, + non-empty source_code) without checking exact content. + """ + result = await cast(Awaitable, extract_input(empty_state, runnable_config, runtime)) + + assert "source_schema_code" in result + assert len(result["source_schema_code"]) > 0 + + assert "source_target" in result + assert isinstance(result["source_target"], FrameworkEnum) + assert result["source_target"] is not None + + assert "destination_target" in result + assert isinstance(result["destination_target"], FrameworkEnum) + assert result["destination_target"] is not None + + +# ── schema_inspection Node ─────────────────────────────────────────────────── + + +class TestSchemaInspection: + """Tests for the schema_inspection node function.""" + + @pytest.mark.integration + async def test_returns_schema_context( + self, sample_state: State, runnable_config: RunnableConfig, runtime: MagicMock + ): + """Schema inspection produces a non-empty schema_context string.""" + @asynccontextmanager + async def empty_tools(): + yield [] + + with patch( + "react_agent.graph.load_database_tools", + side_effect=empty_tools, + ): + result = await schema_inspection(sample_state, runnable_config, runtime) + + assert "schema_context" in result + assert isinstance(result["schema_context"], str) + assert len(result["schema_context"]) > 0 + + +# ── Partial Graph Execution ───────────────────────────────────────────────── + + +class TestPartialGraphExecution: + """Test running subsets of the graph using LangGraph patterns.""" + + async def test_individual_node_invocation(self, compiled_graph): + """Invoke a single node via compiled_graph.nodes[...].invoke().""" + node = compiled_graph.nodes.get("extract_input") + assert node is not None, "extract_input node not found in compiled graph" + + @pytest.mark.integration + @pytest.mark.asyncio + async def test_extract_to_schema_partial( + self, compiled_graph_with_checkpointer, empty_state: State + ): + """Run extract_input → schema_inspection using partial execution. + + Uses update_state to seed state, then invokes with interrupt_after. + """ + g = compiled_graph_with_checkpointer + thread_config = {"configurable": {"thread_id": "test-partial-1"}} + + # Run the full graph but interrupt after schema_inspection + try: + _result = await g.ainvoke( + { + "messages": empty_state.messages, + "source_schema_code": empty_state.source_schema_code, + "source_target": empty_state.source_target, + "destination_target": empty_state.destination_target, + }, + config=thread_config, + interrupt_after=["schema_inspection"], + ) + + # If we got here, the graph ran extract_input and schema_inspection + state = await g.aget_state(thread_config) + assert state is not None + except Exception as e: + # Schema inspection may fail if db-toolbox is not running — + # that's acceptable, we're testing the graph flow itself + pytest.skip(f"Partial execution skipped due to service unavailability: {e}") diff --git a/services/orchestrator/tests/integration_tests/test_mcp_database.py b/services/orchestrator/tests/integration_tests/test_mcp_database.py new file mode 100644 index 0000000..b449024 --- /dev/null +++ b/services/orchestrator/tests/integration_tests/test_mcp_database.py @@ -0,0 +1,66 @@ +"""Integration tests for MCP database tools. + +Tests real connections to MongoDB and the DB Toolbox service. +""" + +from unittest.mock import MagicMock, patch + +import pytest +from langgraph.runtime import Runtime + +from react_agent.context import Context +from react_agent.custom_tools.mcp_database import ( + load_database_tools, +) + +pytestmark = [pytest.mark.anyio, pytest.mark.asyncio] + + +# ── load_database_toolbox_tools ────────────────────────────────────────────── + + +class TestLoadDatabaseToolboxTools: + """Tests for loading tools from the DB Toolbox server.""" + + @pytest.mark.integration + async def test_loads_tools_from_real_server(self, runtime: MagicMock): + """Connect to the real db-toolbox and load tools.""" + with patch( + "react_agent.custom_tools.mcp_database.get_runtime", + return_value=runtime, + ): + try: + async with load_database_tools() as tools: + tool_names = [t.name for t in tools] + except RuntimeError as exc: + pytest.skip(f"DB toolbox unavailable in this environment: {exc}") + + assert isinstance(tool_names, list) + if tool_names: + print(tool_names) + real_tools = ["execute_sql", "list_tables", "execute_cypher", "get_schema"] + assert any(name in tool_names for name in real_tools), ( + f"Expected at least one database tool, got: {tool_names}" + ) + + async def test_fallback_on_invalid_uri(self): + """When toolbox is unreachable, still returns native tools.""" + bad_context = Context() + bad_context.db_toolbox_uri = "http://nonexistent:9999" + bad_runtime = MagicMock(spec=Runtime) + bad_runtime.context = bad_context + + with patch( + "react_agent.custom_tools.mcp_database.get_runtime", + return_value=bad_runtime, + ): + try: + async with load_database_tools() as tools: + tool_names = [t.name for t in tools] + except RuntimeError as exc: + # Current implementation may raise when async generator exits early. + assert "generator didn't yield" in str(exc) + else: + assert tool_names == [], ( + f"Expected no tools loaded from toolbox, got: {tool_names}" + ) diff --git a/services/orchestrator/tests/integration_tests/test_sandboxed_validation.py b/services/orchestrator/tests/integration_tests/test_sandboxed_validation.py new file mode 100644 index 0000000..3dc5abe --- /dev/null +++ b/services/orchestrator/tests/integration_tests/test_sandboxed_validation.py @@ -0,0 +1,61 @@ +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from langchain_core.tools import StructuredTool + +from react_agent.constants import DotnetFramework, TranslationType +from react_agent.custom_tools.dotnet_validator import validate_dotnet_code + + +@pytest.mark.asyncio +async def test_dotnet_sandbox_compilation_error(sample_tool_runtime): + """Provide invalid C# with class declaration and verify compilation failure shape.""" + invalid_code = """ + public class TestHarness { + public void Main() { + invalid_syntax_here; + } + } + """ + sample_tool_runtime.state = {"translation_type": TranslationType.QUERY} + + func = validate_dotnet_code + structured = func if isinstance(func, StructuredTool) else None + assert structured is not None and structured.coroutine is not None + + with patch( + "react_agent.custom_tools.dotnet_validator.get_framework_config_content", + AsyncMock(return_value=""), + ), patch( + "react_agent.custom_tools.dotnet_validator.execute_in_sandbox", + new=MagicMock(ainvoke=AsyncMock(return_value="Build FAILED\nerror CS1002: ; expected")), + ): + result = await structured.coroutine( + source_code=invalid_code, + framework=DotnetFramework.DOTNET_EFCORE, + runtime=sample_tool_runtime, + ) + + assert isinstance(result, dict) + assert "output" in result + assert "[Dotnet Compilation Failed]" in result["output"] + + +@pytest.mark.asyncio +async def test_dotnet_sandbox_rejects_missing_class_keyword(sample_tool_runtime): + """Validation should reject source without class or record before sandbox call.""" + invalid_code = "totally_invalid_input_without_type_declarations" + sample_tool_runtime.state = {"translation_type": TranslationType.QUERY} + + func = validate_dotnet_code + structured = func if isinstance(func, StructuredTool) else None + assert structured is not None and structured.coroutine is not None + + result = await structured.coroutine( + source_code=invalid_code, + framework=DotnetFramework.DOTNET_EFCORE, + runtime=sample_tool_runtime, + ) + + assert isinstance(result, dict) + assert result["output"] == "Compilation Error: No class or record defined in C# source code." diff --git a/services/orchestrator/tests/integration_tests/test_tools.py b/services/orchestrator/tests/integration_tests/test_tools.py new file mode 100644 index 0000000..3b43258 --- /dev/null +++ b/services/orchestrator/tests/integration_tests/test_tools.py @@ -0,0 +1,206 @@ +"""Integration tests for orchestrator tools (validators, docs search). + +Validator tools are tested directly (no external services required). +Documentation tools are tested with real HTTP endpoints. +""" + +import os +from typing import cast +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from langchain_core.tools import StructuredTool + +from react_agent.constants import JavaFramework, TranslationType +from react_agent.custom_tools.docs_search import fetch_web_docs, load_docs_mcp_tools +from react_agent.custom_tools.java_validator import validate_java_code + +pytestmark = [pytest.mark.anyio, pytest.mark.asyncio] + +# ── Java Validator ─────────────────────────────────────────────────────────── + + +SAMPLE_JAVA = """import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; +import org.springframework.data.mongodb.core.mapping.Field; + +@Document(collection = "orders") +class Order { + @Id + private String id; + @Field("orderId") + private Integer orderId; + @Field("customerId") + private Integer customerId; + @Field("orderDate") + private LocalDateTime orderDate; + private Customer customer; + public Order() {} + public String getId() { return id; } +} + +class Customer { + private Integer customerId; + private String customerName; + private BigDecimal creditLimit; + private List customerTransactions = new ArrayList<>(); + public Customer() {} +} + +class CustomerTransaction { + private Integer customerTransactionId; + private BigDecimal transactionAmount; + public CustomerTransaction() {} +} + +@Document(collection = "orderLines") +class OrderLine { + @Id + private String id; + @Field("orderLineId") + private Integer orderLineId; + private Integer quantity; + private BigDecimal unitPrice; + private StockItem stockItem; + public OrderLine() {} +} + +class StockItem { + private Integer stockItemId; + private String stockItemName; + public StockItem() {} +}""" + + +class TestJavaValidator: + """Tests for validate_java_code tool.""" + + async def test_valid_java_code(self): + runtime = MagicMock() + runtime.state = {"translation_type": TranslationType.SCHEMA} + func = cast(StructuredTool, validate_java_code) + + with patch( + "react_agent.custom_tools.java_validator.get_framework_config_content", + AsyncMock(return_value=""), + ), patch( + "react_agent.custom_tools.java_validator.execute_in_sandbox", + new=MagicMock(ainvoke=AsyncMock(return_value="BUILD SUCCESS")), + ): + assert func.coroutine is not None + result = await func.coroutine( + source_code=SAMPLE_JAVA, + framework=JavaFramework.JAVA_SPRING_DATA_MONGODB, + entry_type_name="Order", + runtime=runtime, + ) + + assert isinstance(result, dict) + assert "[Java Validation Passed]" in result["output"] + + async def test_invalid_java_no_class(self): + runtime = MagicMock() + runtime.state = {"translation_type": TranslationType.SCHEMA} + func = cast(StructuredTool, validate_java_code) + + with patch( + "react_agent.custom_tools.java_validator.get_framework_config_content", + AsyncMock(return_value=""), + ), patch( + "react_agent.custom_tools.java_validator.execute_in_sandbox", + new=MagicMock(ainvoke=AsyncMock(return_value="BUILD FAILURE\nCompilation error")), + ): + assert func.coroutine is not None + result = await func.coroutine( + source_code="System.out.println('hello');", + framework=JavaFramework.JAVA_SPRING_DATA_MONGODB, + entry_type_name="ValidationEntryPoint", + runtime=runtime, + ) + + assert "[Java Compilation Failed]" in result["output"] + + async def test_maven_fallback(self): + runtime = MagicMock() + runtime.state = {"translation_type": TranslationType.QUERY} + func = cast(StructuredTool, validate_java_code) + + with patch( + "react_agent.custom_tools.java_validator.get_framework_config_content", + AsyncMock(return_value=""), + ), patch( + "react_agent.custom_tools.java_validator.execute_in_sandbox", + new=MagicMock(ainvoke=AsyncMock(return_value="BUILD SUCCESS\nError occurred during startup")), + ): + assert func.coroutine is not None + result = await func.coroutine( + source_code=SAMPLE_JAVA, + framework=JavaFramework.JAVA_SPRING_DATA_MONGODB, + entry_type_name="Order", + runtime=runtime, + ) + + assert "[Java Validation Failed]" in result["output"] + + +# ── Documentation Search ──────────────────────────────────────────────────── + + +class TestFetchWebDocs: + """Tests for fetch_web_docs tool (HTTP fallback, no Tavily key needed).""" + + @pytest.mark.integration + async def test_http_fallback_efcore(self): + """Fetch docs for EF Core via HTTP fallback (no Tavily key).""" + # Ensure Tavily is not used + original = os.environ.pop("TAVILY_API_KEY", None) + try: + result = await fetch_web_docs.ainvoke( + { + "query": "Entity Framework Core DbContext", + "framework_version": "EF Core 9", + } + ) + assert isinstance(result, str) + assert len(result) > 100, "Expected substantial documentation content" + finally: + if original is not None: + os.environ["TAVILY_API_KEY"] = original + + @pytest.mark.integration + async def test_http_fallback_spring(self): + """Fetch docs for Spring Data via HTTP fallback.""" + original = os.environ.pop("TAVILY_API_KEY", None) + try: + result = await fetch_web_docs.ainvoke( + {"query": "Spring Data MongoDB repository", "framework_version": ""} + ) + assert isinstance(result, str) + # May get "No documentation found" if the URLs don't resolve, + # but should not raise an exception + finally: + if original is not None: + os.environ["TAVILY_API_KEY"] = original + + +class TestLoadDocsMcpTools: + """Tests for loading MCP documentation tools.""" + + @pytest.mark.integration + async def test_loads_at_least_fallback(self): + """Always loads at least the fetch_web_docs fallback tool.""" + async with load_docs_mcp_tools() as tools: + assert len(tools) >= 1 + tool_names = [t.name for t in tools] + expected_names = { + "fetch_web_docs", + "microsoft_docs_search", + "microsoft_code_sample_search", + "microsoft_docs_fetch", + } + assert any(name in expected_names for name in tool_names) diff --git a/services/orchestrator/tests/unit_tests/__init__.py b/services/orchestrator/tests/unit_tests/__init__.py new file mode 100644 index 0000000..f2900f2 --- /dev/null +++ b/services/orchestrator/tests/unit_tests/__init__.py @@ -0,0 +1 @@ +"""Define any unit tests you may want in this directory.""" diff --git a/services/orchestrator/tests/unit_tests/test_configuration.py b/services/orchestrator/tests/unit_tests/test_configuration.py new file mode 100644 index 0000000..2a18044 --- /dev/null +++ b/services/orchestrator/tests/unit_tests/test_configuration.py @@ -0,0 +1,21 @@ +import os + +from react_agent.constants import AvailableModel +from react_agent.context import Context + + +def test_context_init_with_passed_kwargs() -> None: + context = Context(model=AvailableModel.EINFRA_MINI) + assert context.model == AvailableModel.EINFRA_MINI + + +def test_context_init_with_env_vars() -> None: + os.environ["MODEL"] = AvailableModel.EINFRA_MINI.value + context = Context() + assert context.model == AvailableModel.EINFRA_MINI.value + + +def test_context_init_with_env_vars_and_passed_values() -> None: + os.environ["MODEL"] = AvailableModel.EINFRA_MINI.value + context = Context(model=AvailableModel.EINFRA_AGENTIC) + assert context.model == AvailableModel.EINFRA_AGENTIC diff --git a/services/orchestrator/tests/unit_tests/test_state.py b/services/orchestrator/tests/unit_tests/test_state.py new file mode 100644 index 0000000..ed7eb06 --- /dev/null +++ b/services/orchestrator/tests/unit_tests/test_state.py @@ -0,0 +1,26 @@ +from react_agent.state import State + + +def test_state_initialization() -> None: + """State initializes properly with default values.""" + state = State( + messages=[], + ) + + assert state.messages == [] + assert state.schema_context == "" + assert state.council_responses == [] + assert state.translation_messages == [] + assert state.translation_loop_count == 0 + + +def test_state_inherits_input_state_defaults() -> None: + """State defaults come from InputState and can be overridden.""" + state = State() + assert state.source_schema_code is None + assert state.source_query_code is None + assert state.source_target is None + assert state.destination_target is None + assert state.schema_context == "" + assert state.translated_schema_code is None + assert state.translated_query_code is None diff --git a/services/orchestrator/tests/unit_tests/test_utils.py b/services/orchestrator/tests/unit_tests/test_utils.py new file mode 100644 index 0000000..bea4423 --- /dev/null +++ b/services/orchestrator/tests/unit_tests/test_utils.py @@ -0,0 +1,80 @@ +import pytest + +from react_agent.constants import AvailableModel +from react_agent.context import Context +from react_agent.utils import ( + get_mongodb_standalone_mapping, + get_neo4j_standalone_mapping, + load_chat_model, +) + + +@pytest.mark.asyncio +async def test_load_chat_model(context: Context) -> None: + model = await load_chat_model( + AvailableModel.EINFRA_MINI, + { + "openai_api_url": context.openai_api_url, + "openai_api_key": context.openai_api_key, + }, + ) + assert model is not None + + +@pytest.mark.asyncio +async def test_get_mongodb_standalone_mapping() -> None: + mapping = await get_mongodb_standalone_mapping() + + assert mapping is not None + assert "collections" in mapping + + orders_collection = mapping["collections"].get("orders") + assert isinstance(orders_collection, dict) + + orders_mappings = orders_collection.get("mappings") + assert isinstance(orders_mappings, list) + + assert any( + item.get("mappingType") == "NEW_DOCUMENT" + and item.get("sourceSchema") == "WideWorldImporters.Sales" + and item.get("sourceTable") == "Orders" + and any( + property_mapping.get("sourceColumn") == "OrderID" + and property_mapping.get("targetProperty") == "orderId" + and property_mapping.get("isPrimaryKey") is True + for property_mapping in item.get("propertyMappings", []) + ) + for item in orders_mappings + ) + + assert any( + item.get("mappingType") == "EMBEDDED_DOCUMENT" + and item.get("embeddedPath") == "customer" + and item.get("sourceTable") == "Customers" + for item in orders_mappings + ) + + +@pytest.mark.asyncio +async def test_get_neo4j_standalone_mapping() -> None: + mapping = await get_neo4j_standalone_mapping() + + assert mapping is not None + assert "nodes" in mapping + assert "relationships" in mapping + + stock_item_node = mapping["nodes"].get("StockItem") + assert isinstance(stock_item_node, dict) + + stock_item_mappings = stock_item_node.get("propertyMappings") + assert isinstance(stock_item_mappings, list) + assert any( + item.get("sourceColumn") == "StockItemID" + and item.get("targetProperty") == "stockItemId" + and item.get("isPrimaryKey") is True + for item in stock_item_mappings + ) + + people_relationships = mapping["relationships"].get("PEOPLE") + assert isinstance(people_relationships, list) + assert any(item.get("sourceTable") == "Orders" for item in people_relationships) diff --git a/services/orchestrator/uv.lock b/services/orchestrator/uv.lock new file mode 100644 index 0000000..3d8ab27 --- /dev/null +++ b/services/orchestrator/uv.lock @@ -0,0 +1,4082 @@ +version = 1 +revision = 3 +requires-python = ">=3.12, <4.0" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.5" +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/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" }, + { url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" }, + { url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" }, + { url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" }, + { url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" }, + { url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" }, + { url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" }, + { url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" }, + { url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" }, + { url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" }, + { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" }, + { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" }, + { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" }, + { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" }, + { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" }, + { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" }, + { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" }, + { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" }, + { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" }, + { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" }, + { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" }, + { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" }, + { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" }, + { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" }, + { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" }, + { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" }, + { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" }, + { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" }, + { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" }, + { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" }, + { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" }, +] + +[[package]] +name = "aiohttp-retry" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/ebda4d8e3d8cfa1fd3db0fb428db2dd7461d5742cea35178277ad180b033/aiohttp_retry-2.9.1.tar.gz", hash = "sha256:8eb75e904ed4ee5c2ec242fefe85bf04240f685391c4879d8f541d6028ff01f1", size = 13608, upload-time = "2024-11-06T10:44:54.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/99/84ba7273339d0f3dfa57901b846489d2e5c2cd731470167757f1935fffbd/aiohttp_retry-2.9.1-py3-none-any.whl", hash = "sha256:66d2759d1921838256a05a3f80ad7e724936f083e35be5abb5e16eed6be6dc54", size = 9981, upload-time = "2024-11-06T10:44:52.917Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.101.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/cb/9d0123243e749ac3a579972b2c398971bce1dc57bcc4efb08066df610360/anthropic-0.101.0.tar.gz", hash = "sha256:1116a6a87c55757e0fbe3e1ba40804fbd04de7963601a6dd6b539a889f18de3e", size = 758603, upload-time = "2026-05-11T15:46:33.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/b2/74ff06762d005ecf1658929a292df0acb786d025f6a6c54fcb30e2dc7761/anthropic-0.101.0-py3-none-any.whl", hash = "sha256:cc3cc6576989471e2aa9132258034ad0ff0d8fe500b04ac499e4e46ed68c5ed0", size = 753594, upload-time = "2026-05-11T15:46:32.216Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + +[[package]] +name = "blockbuster" +version = "1.5.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "forbiddenfruit", marker = "implementation_name == 'cpython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e0/dcbab602790a576b0b94108c07e2c048e5897df7cc83722a89582d733987/blockbuster-1.5.26.tar.gz", hash = "sha256:cc3ce8c70fa852a97ee3411155f31e4ad2665cd1c6c7d2f8bb1851dab61dc629", size = 36085, upload-time = "2025-12-05T10:43:47.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/c1/84fc6811122f54b20de2e5afb312ee07a3a47a328755587d1e505475239b/blockbuster-1.5.26-py3-none-any.whl", hash = "sha256:f8e53fb2dd4b6c6ec2f04907ddbd063ca7cd1ef587d24448ef4e50e81e3a79bb", size = 13226, upload-time = "2025-12-05T10:43:48.778Z" }, +] + +[[package]] +name = "bracex" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" }, +] + +[[package]] +name = "certifi" +version = "2025.11.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "croniter" +version = "6.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, + { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, +] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, +] + +[[package]] +name = "daytona" +version = "0.175.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "aiohttp" }, + { name = "daytona-api-client" }, + { name = "daytona-api-client-async" }, + { name = "daytona-toolbox-api-client" }, + { name = "daytona-toolbox-api-client-async" }, + { name = "deprecated" }, + { name = "httpx" }, + { name = "httpx-ws" }, + { name = "obstore" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation-aiohttp-client" }, + { name = "opentelemetry-sdk" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "python-multipart" }, + { name = "toml" }, + { name = "urllib3" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/79/de2f17f02ccc43093e7a187e3f5b991a59de278bf7674b40fd4db9899c53/daytona-0.175.0.tar.gz", hash = "sha256:54aa05646d53bcadfd61006e242db01ec75f9a018b3dcd5c48e4f74e3e8c9c40", size = 137059, upload-time = "2026-05-12T19:12:06.311Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/b8/070d25015d9dd83a51a11a3bc2a6566126e925ad52c5b3602c441562a96c/daytona-0.175.0-py3-none-any.whl", hash = "sha256:c342edf234e3fa89c0649ae44225c478542832d486c6301972e30aedce93e1ca", size = 167824, upload-time = "2026-05-12T19:12:04.527Z" }, +] + +[[package]] +name = "daytona-api-client" +version = "0.175.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/ac/791429a097e0ccd86b07286cb187901784d5ca187873a4c5e28dafd92cba/daytona_api_client-0.175.0.tar.gz", hash = "sha256:47ff5bc5c0246e8cca0e0f15928479f0c1791223bf75ef7e7f35f37a4592c0fa", size = 148151, upload-time = "2026-05-12T19:11:00.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/92/6e7a476e3ef8a3d03f260ab4bd9107101d20b2103b3a913a3816a0ec24a1/daytona_api_client-0.175.0-py3-none-any.whl", hash = "sha256:f1f686915e2187b2ffc80de5550e3216cbc18713d430b2ac7d50ba1978e4f917", size = 408006, upload-time = "2026-05-12T19:10:58.724Z" }, +] + +[[package]] +name = "daytona-api-client-async" +version = "0.175.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aiohttp-retry" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/36/f470faaa9ec6a1162b23593374dd24d5a8602e1231eaa8bdcc732c564508/daytona_api_client_async-0.175.0.tar.gz", hash = "sha256:2621facb6aa57e1979f6ad3af3ac285fd956d5f8af2fb38332e3e86e0925b392", size = 148647, upload-time = "2026-05-12T19:11:30.467Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/69/ee635288ff79f2de454ef8819d43781608da5f3fbc09cea2ecdd0adeb11f/daytona_api_client_async-0.175.0-py3-none-any.whl", hash = "sha256:a252e062e654de68c5674a08f0f7bbd8c7600a1cf07bc4c5f8ae57033c8c14dc", size = 411248, upload-time = "2026-05-12T19:11:28.521Z" }, +] + +[[package]] +name = "daytona-toolbox-api-client" +version = "0.175.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/25/3d88dfbe0453a1e411be89e4f7cc410bf2ba9e9b910b18fd69662caf8a11/daytona_toolbox_api_client-0.175.0.tar.gz", hash = "sha256:30d06719d2b54d5d19cba04847d648da5ce207c2a3be9ce0b9792dc7e2190cd7", size = 74031, upload-time = "2026-05-12T19:11:03.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/1e/f007100304ed3976b22aa7a5af1ea35135f79031b9d1a9695797634a1a43/daytona_toolbox_api_client-0.175.0-py3-none-any.whl", hash = "sha256:5b04cc55a6b72ebe03671ed4c06f2c38372941a622f451b0163d2d2db6b34b1c", size = 191438, upload-time = "2026-05-12T19:11:02.085Z" }, +] + +[[package]] +name = "daytona-toolbox-api-client-async" +version = "0.175.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aiohttp-retry" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/03/f4476199078a50789dd6adc0af1e436c6f5d72ba042ed30cdc667505ce14/daytona_toolbox_api_client_async-0.175.0.tar.gz", hash = "sha256:6c1847956730591b6b1b7c4a6b3090c9106aa38631f4a52dd1b862e73cd60fca", size = 67569, upload-time = "2026-05-12T19:11:26.469Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/f4/8d9080baf94bd5dbf6f5ce6557a86073da046983ffbfcab4b114a4c0683c/daytona_toolbox_api_client_async-0.175.0-py3-none-any.whl", hash = "sha256:cfe45a76eae9fda35a2246dbfcbf5c335efa057157cb09000103f13300d1d97a", size = 189565, upload-time = "2026-05-12T19:11:24.851Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "deepagents" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain" }, + { name = "langchain-anthropic" }, + { name = "langchain-core" }, + { name = "langchain-google-genai" }, + { name = "langsmith" }, + { name = "wcmatch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/91/ef86a273365bcfafbfc2d1ec82b2978b72b8d14eb64ffc327276db1bcd4c/deepagents-0.6.1.tar.gz", hash = "sha256:7799e195286728379183d16ab0f153247c92e4403101484729fa7977e5efb3d8", size = 173930, upload-time = "2026-05-12T17:44:34.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/38/ddf9ecc0b4f59ce79d9396d8e88c0649e00fd1dd3c2f921e580ca2de25fa/deepagents-0.6.1-py3-none-any.whl", hash = "sha256:d06d9b88a484b4dbc24f5f0b6f4de10df2fb847b2d2d8980c1c35d9021f28db4", size = 197889, upload-time = "2026-05-12T17:44:32.949Z" }, +] + +[[package]] +name = "deepdiff" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "orderly-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/20/63dd34163ed07393968128dc8c7ab948c96e47c4ce76976ea533de64909d/deepdiff-9.0.0.tar.gz", hash = "sha256:4872005306237b5b50829803feff58a1dfd20b2b357a55de22e7ded65b2008a7", size = 151952, upload-time = "2026-03-30T05:52:23.769Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/c4/da7089cd7aa4ab554f56e18a7fb08dcfed8fd2ae91fa528f5b1be207a148/deepdiff-9.0.0-py3-none-any.whl", hash = "sha256:b1ae0dd86290d86a03de5fbee728fde43095c1472ae4974bdab23ab4656305bd", size = 170540, upload-time = "2026-03-30T05:52:22.008Z" }, +] + +[package.optional-dependencies] +optimize = [ + { name = "orjson" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +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, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastapi" +version = "0.136.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/45/c130091c2dfa061bbfe3150f2a5091ef1adf149f2a8d2ae769ecaf6e99a2/fastapi-0.136.1.tar.gz", hash = "sha256:7af665ad7acfa0a3baf8983d393b6b471b9da10ede59c60045f49fbc89a0fa7f", size = 397448, upload-time = "2026-04-23T16:49:44.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/ff/2e4eca3ade2c22fe1dea7043b8ee9dabe47753349eb1b56a202de8af6349/fastapi-0.136.1-py3-none-any.whl", hash = "sha256:a6e9d7eeada96c93a4d69cb03836b44fa34e2854accb7244a1ece36cd4781c3f", size = 117683, upload-time = "2026-04-23T16:49:42.437Z" }, +] + +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, +] + +[[package]] +name = "forbiddenfruit" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/79/d4f20e91327c98096d605646bdc6a5ffedae820f38d378d3515c42ec5e60/forbiddenfruit-0.1.4.tar.gz", hash = "sha256:e3f7e66561a29ae129aac139a85d610dbf3dd896128187ed5454b6421f624253", size = 43756, upload-time = "2021-01-16T21:03:35.401Z" } + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "google-auth" +version = "2.52.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/f8/80d2493cbedece1c623dc3e3cb1883300871af0dcdae254409522985ac23/google_auth-2.52.0.tar.gz", hash = "sha256:01f30e1a9e3638698d89464f5e603ce29d18e1c0e63ec31ac570aba4e164aaf5", size = 335027, upload-time = "2026-05-07T19:45:24.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/fc/2cdc74252746f547f81ff3f02d4d4234a3f411b5de5b61af97e633a060b9/google_auth-2.52.0-py3-none-any.whl", hash = "sha256:aee92803ba0ff93a70a3b8a35c7b4797837751cd6380b63ff38372b98f3ed627", size = 245614, upload-time = "2026-05-07T19:45:21.914Z" }, +] + +[package.optional-dependencies] +requests = [ + { name = "requests" }, +] + +[[package]] +name = "google-genai" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "google-auth", extra = ["requests"] }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "sniffio" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/59/3ed61240ef20b3ae6ed54e82c6f8b6d1f194947bc6679679dd6cdb037594/google_genai-1.75.0.tar.gz", hash = "sha256:56bac3991b311c93f980c0a2abcd287b672146905df1fbd71c92ed633d5a07cf", size = 539039, upload-time = "2026-05-04T22:48:54.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/b6/552d40e96da22921eb1fead7c14b00b5b5473a20e45959488660fab35ee2/google_genai-1.75.0-py3-none-any.whl", hash = "sha256:8dc4c096e7d6288c3087f6893f582fe52468932464781edb8193bd92b9fefb2c", size = 793726, upload-time = "2026-05-04T22:48:53.033Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.75.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/c8/f439cffde755cffa462bfbb156278fa6f9d09119719af9814b858fd4f81f/googleapis_common_protos-1.75.0.tar.gz", hash = "sha256:53a062ff3c32552fbd62c11fe23768b78e4ddf0494d5e5fd97d3f4689c75fbbd", size = 151035, upload-time = "2026-05-07T08:04:49.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/c8/e2645aa8ed02fd4c7a2f59d68783b65b1f3cbdfe39a6308e156509d1fee8/googleapis_common_protos-1.75.0-py3-none-any.whl", hash = "sha256:961ed60399c457ceb0ee8f285a84c870aabc9c6a832b9d37bb281b5bebde43ed", size = 300631, upload-time = "2026-05-07T08:03:30.345Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/32/f2ce6d4cac3e55bc6173f92dbe627e782e1850f89d986c3606feb63aafa7/greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f", size = 286228, upload-time = "2026-04-27T12:20:34.421Z" }, + { url = "https://files.pythonhosted.org/packages/b7/aa/caed9e5adf742315fc7be2a84196373aab4816e540e38ba0d76cb7584d68/greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628", size = 601775, upload-time = "2026-04-27T12:52:41.045Z" }, + { url = "https://files.pythonhosted.org/packages/c7/af/90ae08497400a941595d12774447f752d3dfe0fbb012e35b76bc5c0ff37e/greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b", size = 614436, upload-time = "2026-04-27T12:59:41.595Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e0/2e13df68f367e2f9960616927d60857dd7e56aaadd59a47c644216b2f920/greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c", size = 611388, upload-time = "2026-04-27T12:25:28.008Z" }, + { url = "https://files.pythonhosted.org/packages/82/f7/393c64055132ac0d488ef6be549253b7e6274194863967ddc0bc8f5b87b8/greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588", size = 1570768, upload-time = "2026-04-27T12:53:28.099Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4b/eaf7735253522cf56d1b74d672a58f54fc114702ceaf05def59aae72f6e1/greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e", size = 1635983, upload-time = "2026-04-27T12:25:26.903Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/4fb3a0805bd5165da5ebf858da7cc01cce8061674106d2cf5bdab32cbfde/greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8", size = 238840, upload-time = "2026-04-27T12:23:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/cb/cb/baa584cb00532126ffe12d9787db0a60c5a4f55c27bfe2666df5d4c30a32/greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2", size = 235615, upload-time = "2026-04-27T12:21:38.57Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/fc576f99037ce19c5aa16628e4c3226b6d1419f72a62c79f5f40576e6eb3/greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106", size = 285066, upload-time = "2026-04-27T12:23:05.033Z" }, + { url = "https://files.pythonhosted.org/packages/4a/ba/b28ddbe6bfad6a8ac196ef0e8cff37bc65b79735995b9e410923fffeeb70/greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b", size = 604414, upload-time = "2026-04-27T12:52:42.358Z" }, + { url = "https://files.pythonhosted.org/packages/09/06/4b69f8f0b67603a8be2790e55107a190b376f2627fe0eaf5695d85ffb3cd/greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e", size = 617349, upload-time = "2026-04-27T12:59:43.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/a3918541fd0ddefe024a69de6d16aa7b46d36ac19562adaa63c7fa180eff/greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13", size = 613927, upload-time = "2026-04-27T12:25:30.28Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e1/bd0af6213c7dd33175d8a462d4c1fe1175124ebed4855bc1475a5b5242c2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba", size = 1570893, upload-time = "2026-04-27T12:53:29.483Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2a/0789702f864f5382cb476b93d7a9c823c10472658102ccd65f415747d2e2/greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846", size = 1636060, upload-time = "2026-04-27T12:25:28.845Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8f/22bf9df92bbff0eb07842b60f7e63bf7675a9742df628437a9f02d09137f/greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5", size = 238740, upload-time = "2026-04-27T12:24:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b7/9c5c3d653bd4ff614277c049ac676422e2c557db47b4fe43e6313fc005dc/greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b", size = 235525, upload-time = "2026-04-27T12:23:12.308Z" }, + { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" }, + { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" }, + { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" }, + { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" }, + { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" }, + { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" }, +] + +[[package]] +name = "grpcio" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/f4/7384ed0178203d6074446b3c4f46c90a22ddf7ae0b3aee521627f54cfc2a/grpcio-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:f9ab915a267fc47c7e88c387a3a28325b58c898e23d4995f765728f4e3dedb97", size = 5913985, upload-time = "2026-02-06T09:55:26.832Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/be1caa25f06594463f685b3790b320f18aea49b33166f4141bfdc2bfb236/grpcio-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3f8904a8165ab21e07e58bf3e30a73f4dffc7a1e0dbc32d51c61b5360d26f43e", size = 11811853, upload-time = "2026-02-06T09:55:29.224Z" }, + { url = "https://files.pythonhosted.org/packages/24/a7/f06d151afc4e64b7e3cc3e872d331d011c279aaab02831e40a81c691fb65/grpcio-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:859b13906ce098c0b493af92142ad051bf64c7870fa58a123911c88606714996", size = 6475766, upload-time = "2026-02-06T09:55:31.825Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a8/4482922da832ec0082d0f2cc3a10976d84a7424707f25780b82814aafc0a/grpcio-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b2342d87af32790f934a79c3112641e7b27d63c261b8b4395350dad43eff1dc7", size = 7170027, upload-time = "2026-02-06T09:55:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/54/bf/f4a3b9693e35d25b24b0b39fa46d7d8a3c439e0a3036c3451764678fec20/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12a771591ae40bc65ba67048fa52ef4f0e6db8279e595fd349f9dfddeef571f9", size = 6690766, upload-time = "2026-02-06T09:55:36.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b9/521875265cc99fe5ad4c5a17010018085cae2810a928bf15ebe7d8bcd9cc/grpcio-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:185dea0d5260cbb2d224c507bf2a5444d5abbb1fa3594c1ed7e4c709d5eb8383", size = 7266161, upload-time = "2026-02-06T09:55:39.824Z" }, + { url = "https://files.pythonhosted.org/packages/05/86/296a82844fd40a4ad4a95f100b55044b4f817dece732bf686aea1a284147/grpcio-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51b13f9aed9d59ee389ad666b8c2214cc87b5de258fa712f9ab05f922e3896c6", size = 8253303, upload-time = "2026-02-06T09:55:42.353Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e4/ea3c0caf5468537f27ad5aab92b681ed7cc0ef5f8c9196d3fd42c8c2286b/grpcio-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd5f135b1bd58ab088930b3c613455796dfa0393626a6972663ccdda5b4ac6ce", size = 7698222, upload-time = "2026-02-06T09:55:44.629Z" }, + { url = "https://files.pythonhosted.org/packages/d7/47/7f05f81e4bb6b831e93271fb12fd52ba7b319b5402cbc101d588f435df00/grpcio-1.78.0-cp312-cp312-win32.whl", hash = "sha256:94309f498bcc07e5a7d16089ab984d42ad96af1d94b5a4eb966a266d9fcabf68", size = 4066123, upload-time = "2026-02-06T09:55:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e7/d6914822c88aa2974dbbd10903d801a28a19ce9cd8bad7e694cbbcf61528/grpcio-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:9566fe4ababbb2610c39190791e5b829869351d14369603702e890ef3ad2d06e", size = 4797657, upload-time = "2026-02-06T09:55:49.86Z" }, + { url = "https://files.pythonhosted.org/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://files.pythonhosted.org/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://files.pythonhosted.org/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://files.pythonhosted.org/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://files.pythonhosted.org/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://files.pythonhosted.org/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://files.pythonhosted.org/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +] + +[[package]] +name = "grpcio-health-checking" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/ac/8eb871f4e47b11abfe45497e6187a582ec680ccd7232706d228474a8c7a5/grpcio_health_checking-1.78.0.tar.gz", hash = "sha256:78526d5c60b9b99fd18954b89f86d70033c702e96ad6ccc9749baf16136979b3", size = 17008, upload-time = "2026-02-06T10:01:47.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/30/dbaf47e2210697e2923b49eb62a6a2c07d5ee55bb40cff1e6cc0c5bb22e1/grpcio_health_checking-1.78.0-py3-none-any.whl", hash = "sha256:309798c098c5de72a9bff7172d788fdf309d246d231db9955b32e7c1c773fbeb", size = 19010, upload-time = "2026-02-06T10:01:37.949Z" }, +] + +[[package]] +name = "grpcio-tools" +version = "1.78.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/d1/cbefe328653f746fd319c4377836a25ba64226e41c6a1d7d5cdbc87a459f/grpcio_tools-1.78.0.tar.gz", hash = "sha256:4b0dd86560274316e155d925158276f8564508193088bc43e20d3f5dff956b2b", size = 5393026, upload-time = "2026-02-06T09:59:59.53Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ae/5b1fa5dd8d560a6925aa52de0de8731d319f121c276e35b9b2af7cc220a2/grpcio_tools-1.78.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:9eb122da57d4cad7d339fc75483116f0113af99e8d2c67f3ef9cae7501d806e4", size = 2546823, upload-time = "2026-02-06T09:58:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ed/d33ccf7fa701512efea7e7e23333b748848a123e9d3bbafde4e126784546/grpcio_tools-1.78.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d0c501b8249940b886420e6935045c44cb818fa6f265f4c2b97d5cff9cb5e796", size = 5706776, upload-time = "2026-02-06T09:58:20.944Z" }, + { url = "https://files.pythonhosted.org/packages/c6/69/4285583f40b37af28277fc6b867d636e3b10e1b6a7ebd29391a856e1279b/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:77e5aa2d2a7268d55b1b113f958264681ef1994c970f69d48db7d4683d040f57", size = 2593972, upload-time = "2026-02-06T09:58:23.29Z" }, + { url = "https://files.pythonhosted.org/packages/d7/eb/ecc1885bd6b3147f0a1b7dff5565cab72f01c8f8aa458f682a1c77a9fb08/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8e3c0b0e6ba5275322ba29a97bf890565a55f129f99a21b121145e9e93a22525", size = 2905531, upload-time = "2026-02-06T09:58:25.406Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a9/511d0040ced66960ca10ba0f082d6b2d2ee6dd61837b1709636fdd8e23b4/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975d4cb48694e20ebd78e1643e5f1cd94cdb6a3d38e677a8e84ae43665aa4790", size = 2656909, upload-time = "2026-02-06T09:58:28.022Z" }, + { url = "https://files.pythonhosted.org/packages/06/a3/3d2c707e7dee8df842c96fbb24feb2747e506e39f4a81b661def7fed107c/grpcio_tools-1.78.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:553ff18c5d52807dedecf25045ae70bad7a3dbba0b27a9a3cdd9bcf0a1b7baec", size = 3109778, upload-time = "2026-02-06T09:58:30.091Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4b/646811ba241bf05da1f0dc6f25764f1c837f78f75b4485a4210c84b79eae/grpcio_tools-1.78.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8c7f5e4af5a84d2e96c862b1a65e958a538237e268d5f8203a3a784340975b51", size = 3658763, upload-time = "2026-02-06T09:58:32.875Z" }, + { url = "https://files.pythonhosted.org/packages/45/de/0a5ef3b3e79d1011375f5580dfee3a9c1ccb96c5f5d1c74c8cee777a2483/grpcio_tools-1.78.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96183e2b44afc3f9a761e9d0f985c3b44e03e8bb98e626241a6cbfb3b6f7e88f", size = 3325116, upload-time = "2026-02-06T09:58:34.894Z" }, + { url = "https://files.pythonhosted.org/packages/95/d2/6391b241ad571bc3e71d63f957c0b1860f0c47932d03c7f300028880f9b8/grpcio_tools-1.78.0-cp312-cp312-win32.whl", hash = "sha256:2250e8424c565a88573f7dc10659a0b92802e68c2a1d57e41872c9b88ccea7a6", size = 993493, upload-time = "2026-02-06T09:58:37.242Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8f/7d0d3a39ecad76ccc136be28274daa660569b244fa7d7d0bbb24d68e5ece/grpcio_tools-1.78.0-cp312-cp312-win_amd64.whl", hash = "sha256:217d1fa29de14d9c567d616ead7cb0fef33cde36010edff5a9390b00d52e5094", size = 1158423, upload-time = "2026-02-06T09:58:40.072Z" }, + { url = "https://files.pythonhosted.org/packages/53/ce/17311fb77530420e2f441e916b347515133e83d21cd6cc77be04ce093d5b/grpcio_tools-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2d6de1cc23bdc1baafc23e201b1e48c617b8c1418b4d8e34cebf72141676e5fb", size = 2546284, upload-time = "2026-02-06T09:58:43.073Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/79e101483115f0e78223397daef71751b75eba7e92a32060c10aae11ca64/grpcio_tools-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2afeaad88040894c76656202ff832cb151bceb05c0e6907e539d129188b1e456", size = 5705653, upload-time = "2026-02-06T09:58:45.533Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a7/52fa3ccb39ceeee6adc010056eadfbca8198651c113e418dafebbdf2b306/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33cc593735c93c03d63efe7a8ba25f3c66f16c52f0651910712490244facad72", size = 2592788, upload-time = "2026-02-06T09:58:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/68/08/682ff6bb548225513d73dc9403742d8975439d7469c673bc534b9bbc83a7/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2921d7989c4d83b71f03130ab415fa4d66e6693b8b8a1fcbb7a1c67cff19b812", size = 2905157, upload-time = "2026-02-06T09:58:51.478Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/264f3836a96423b7018e5ada79d62576a6401f6da4e1f4975b18b2be1265/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6a0df438e82c804c7b95e3f311c97c2f876dcc36376488d5b736b7bcf5a9b45", size = 2656166, upload-time = "2026-02-06T09:58:54.117Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/f108276611522e03e98386b668cc7e575eff6952f2db9caa15b2a3b3e883/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9c6070a9500798225191ef25d0055a15d2c01c9c8f2ee7b681fffa99c98c822", size = 3109110, upload-time = "2026-02-06T09:58:56.891Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c7/cf048dbcd64b3396b3c860a2ffbcc67a8f8c87e736aaa74c2e505a7eee4c/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:394e8b57d85370a62e5b0a4d64c96fcf7568345c345d8590c821814d227ecf1d", size = 3657863, upload-time = "2026-02-06T09:58:59.176Z" }, + { url = "https://files.pythonhosted.org/packages/b6/37/e2736912c8fda57e2e57a66ea5e0bc8eb9a5fb7ded00e866ad22d50afb08/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3ef700293ab375e111a2909d87434ed0a0b086adf0ce67a8d9cf12ea7765e63", size = 3324748, upload-time = "2026-02-06T09:59:01.242Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/726abc75bb5bfc2841e88ea05896e42f51ca7c30cb56da5c5b63058b3867/grpcio_tools-1.78.0-cp313-cp313-win32.whl", hash = "sha256:6993b960fec43a8d840ee5dc20247ef206c1a19587ea49fe5e6cc3d2a09c1585", size = 993074, upload-time = "2026-02-06T09:59:03.085Z" }, + { url = "https://files.pythonhosted.org/packages/c5/68/91b400bb360faf9b177ffb5540ec1c4d06ca923691ddf0f79e2c9683f4da/grpcio_tools-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:275ce3c2978842a8cf9dd88dce954e836e590cf7029649ad5d1145b779039ed5", size = 1158185, upload-time = "2026-02-06T09:59:05.036Z" }, + { url = "https://files.pythonhosted.org/packages/cf/5e/278f3831c8d56bae02e3acc570465648eccf0a6bbedcb1733789ac966803/grpcio_tools-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:8b080d0d072e6032708a3a91731b808074d7ab02ca8fb9847b6a011fdce64cd9", size = 2546270, upload-time = "2026-02-06T09:59:07.426Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d9/68582f2952b914b60dddc18a2e3f9c6f09af9372b6f6120d6cf3ec7f8b4e/grpcio_tools-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8c0ad8f8f133145cd7008b49cb611a5c6a9d89ab276c28afa17050516e801f79", size = 5705731, upload-time = "2026-02-06T09:59:09.856Z" }, + { url = "https://files.pythonhosted.org/packages/70/68/feb0f9a48818ee1df1e8b644069379a1e6ef5447b9b347c24e96fd258e5d/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f8ea092a7de74c6359335d36f0674d939a3c7e1a550f4c2c9e80e0226de8fe4", size = 2593896, upload-time = "2026-02-06T09:59:12.23Z" }, + { url = "https://files.pythonhosted.org/packages/1f/08/a430d8d06e1b8d33f3e48d3f0cc28236723af2f35e37bd5c8db05df6c3aa/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:da422985e0cac822b41822f43429c19ecb27c81ffe3126d0b74e77edec452608", size = 2905298, upload-time = "2026-02-06T09:59:14.458Z" }, + { url = "https://files.pythonhosted.org/packages/71/0a/348c36a3eae101ca0c090c9c3bc96f2179adf59ee0c9262d11cdc7bfe7db/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fab1faa3fbcb246263e68da7a8177d73772283f9db063fb8008517480888d26", size = 2656186, upload-time = "2026-02-06T09:59:16.949Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3f/18219f331536fad4af6207ade04142292faa77b5cb4f4463787988963df8/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dd9c094f73f734becae3f20f27d4944d3cd8fb68db7338ee6c58e62fc5c3d99f", size = 3109859, upload-time = "2026-02-06T09:59:19.202Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d9/341ea20a44c8e5a3a18acc820b65014c2e3ea5b4f32a53d14864bcd236bc/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2ed51ce6b833068f6c580b73193fc2ec16468e6bc18354bc2f83a58721195a58", size = 3657915, upload-time = "2026-02-06T09:59:21.839Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f4/5978b0f91611a64371424c109dd0027b247e5b39260abad2eaee66b6aa37/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05803a5cdafe77c8bdf36aa660ad7a6a1d9e49bc59ce45c1bade2a4698826599", size = 3324724, upload-time = "2026-02-06T09:59:24.402Z" }, + { url = "https://files.pythonhosted.org/packages/b2/80/96a324dba99cfbd20e291baf0b0ae719dbb62b76178c5ce6c788e7331cb1/grpcio_tools-1.78.0-cp314-cp314-win32.whl", hash = "sha256:f7c722e9ce6f11149ac5bddd5056e70aaccfd8168e74e9d34d8b8b588c3f5c7c", size = 1015505, upload-time = "2026-02-06T09:59:26.3Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d1/909e6a05bfd44d46327dc4b8a78beb2bae4fb245ffab2772e350081aaf7e/grpcio_tools-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d58ade518b546120ec8f0a8e006fc8076ae5df151250ebd7e82e9b5e152c229", size = 1190196, upload-time = "2026-02-06T09:59:28.359Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" }, + { url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, + { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, +] + +[[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, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "httpx-ws" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore" }, + { name = "httpx" }, + { name = "wsproto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/cd/ca91a07ae446451f7476bf3fcc909e98cb942ff032ebfda0e3fe449aca7b/httpx_ws-0.9.0.tar.gz", hash = "sha256:797373326f70eec1ae96f6e43ae9f12002fd7d73aee139a4985eaab964338a08", size = 107105, upload-time = "2026-03-28T14:11:10.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/f8/a6bc80313a9e93c888fa10534dfce2ad76ff86911b6f485777ce6de6a073/httpx_ws-0.9.0-py3-none-any.whl", hash = "sha256:71640d2fb1bf9a225775015b33cd755cfd4c5f7e21c885192fe3adc4c387b248", size = 15759, upload-time = "2026-03-28T14:11:11.887Z" }, +] + +[[package]] +name = "idna" +version = "3.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/68/7390a418f10897da93b158f2d5a8bd0bcd73a0f9ec3bb36917085bb759ef/jiter-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2fb2ce3a7bc331256dfb14cefc34832366bb28a9aca81deaf43bbf2a5659e607", size = 316295, upload-time = "2026-04-10T14:26:24.887Z" }, + { url = "https://files.pythonhosted.org/packages/60/a0/5854ac00ff63551c52c6c89534ec6aba4b93474e7924d64e860b1c94165b/jiter-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5252a7ca23785cef5d02d4ece6077a1b556a410c591b379f82091c3001e14844", size = 315898, upload-time = "2026-04-10T14:26:26.601Z" }, + { url = "https://files.pythonhosted.org/packages/41/a1/4f44832650a16b18e8391f1bf1d6ca4909bc738351826bcc198bba4357f4/jiter-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c409578cbd77c338975670ada777add4efd53379667edf0aceea730cabede6fb", size = 343730, upload-time = "2026-04-10T14:26:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/48/64/a329e9d469f86307203594b1707e11ae51c3348d03bfd514a5f997870012/jiter-0.14.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ede4331a1899d604463369c730dbb961ffdc5312bc7f16c41c2896415b1304a", size = 370102, upload-time = "2026-04-10T14:26:30.089Z" }, + { url = "https://files.pythonhosted.org/packages/94/c1/5e3dfc59635aa4d4c7bd20a820ac1d09b8ed851568356802cf1c08edb3cf/jiter-0.14.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92cd8b6025981a041f5310430310b55b25ca593972c16407af8837d3d7d2ca01", size = 461335, upload-time = "2026-04-10T14:26:31.911Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1b/dd157009dbc058f7b00108f545ccb72a2d56461395c4fc7b9cfdccb00af4/jiter-0.14.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:351bf6eda4e3a7ceb876377840c702e9a3e4ecc4624dbfb2d6463c67ae52637d", size = 378536, upload-time = "2026-04-10T14:26:33.595Z" }, + { url = "https://files.pythonhosted.org/packages/91/78/256013667b7c10b8834f8e6e54cd3e562d4c6e34227a1596addccc05e38c/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1dcfbeb93d9ecd9ca128bbf8910120367777973fa193fb9a39c31237d8df165", size = 353859, upload-time = "2026-04-10T14:26:35.098Z" }, + { url = "https://files.pythonhosted.org/packages/de/d9/137d65ade9093a409fe80955ce60b12bb753722c986467aeda47faf450ad/jiter-0.14.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ae039aaef8de3f8157ecc1fdd4d85043ac4f57538c245a0afaecb8321ec951c3", size = 357626, upload-time = "2026-04-10T14:26:36.685Z" }, + { url = "https://files.pythonhosted.org/packages/2e/48/76750835b87029342727c1a268bea8878ab988caf81ee4e7b880900eeb5a/jiter-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7d9d51eb96c82a9652933bd769fe6de66877d6eb2b2440e281f2938c51b5643e", size = 393172, upload-time = "2026-04-10T14:26:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/a6/60/456c4e81d5c8045279aefe60e9e483be08793828800a4e64add8fdde7f2a/jiter-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d824ca4148b705970bf4e120924a212fdfca9859a73e42bd7889a63a4ea6bb98", size = 520300, upload-time = "2026-04-10T14:26:39.532Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/2020e0984c235f678dced38fe4eec3058cf528e6af36ebf969b410305941/jiter-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff3a6465b3a0f54b1a430f45c3c0ba7d61ceb45cbc3e33f9e1a7f638d690baf3", size = 553059, upload-time = "2026-04-10T14:26:40.991Z" }, + { url = "https://files.pythonhosted.org/packages/ef/32/e2d298e1a22a4bbe6062136d1c7192db7dba003a6975e51d9a9eecabc4c2/jiter-0.14.0-cp312-cp312-win32.whl", hash = "sha256:5dec7c0a3e98d2a3f8a2e67382d0d7c3ac60c69103a4b271da889b4e8bb1e129", size = 206030, upload-time = "2026-04-10T14:26:42.517Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/96369141b3d8a4a8e4590e983085efe1c436f35c0cda940dd76d942e3e40/jiter-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:fc7e37b4b8bc7e80a63ad6cfa5fc11fab27dbfea4cc4ae644b1ab3f273dc348f", size = 201603, upload-time = "2026-04-10T14:26:44.328Z" }, + { url = "https://files.pythonhosted.org/packages/01/c3/75d847f264647017d7e3052bbcc8b1e24b95fa139c320c5f5066fa7a0bdd/jiter-0.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:ee4a72f12847ef29b072aee9ad5474041ab2924106bdca9fcf5d7d965853e057", size = 191525, upload-time = "2026-04-10T14:26:46Z" }, + { url = "https://files.pythonhosted.org/packages/97/2a/09f70020898507a89279659a1afe3364d57fc1b2c89949081975d135f6f5/jiter-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:af72f204cf4d44258e5b4c1745130ac45ddab0e71a06333b01de660ab4187a94", size = 315502, upload-time = "2026-04-10T14:26:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/d6/be/080c96a45cd74f9fce5db4fd68510b88087fb37ffe2541ff73c12db92535/jiter-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b77da71f6e819be5fbcec11a453fde5b1d0267ef6ed487e2a392fd8e14e4e3a", size = 314870, upload-time = "2026-04-10T14:26:49.149Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5e/2d0fee155826a968a832cc32438de5e2a193292c8721ca70d0b53e58245b/jiter-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f4ea612fe8b84b8b04e51d0e78029ecf3466348e25973f953de6e6a59aa4c1", size = 343406, upload-time = "2026-04-10T14:26:50.762Z" }, + { url = "https://files.pythonhosted.org/packages/70/af/bf9ee0d3a4f8dc0d679fc1337f874fe60cdbf841ebbb304b374e1c9aaceb/jiter-0.14.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62fe2451f8fcc0240261e6a4df18ecbcd58327857e61e625b2393ea3b468aac9", size = 369415, upload-time = "2026-04-10T14:26:52.188Z" }, + { url = "https://files.pythonhosted.org/packages/0f/83/8e8561eadba31f4d3948a5b712fb0447ec71c3560b57a855449e7b8ddc98/jiter-0.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6112f26f5afc75bcb475787d29da3aa92f9d09c7858f632f4be6ffe607be82e9", size = 461456, upload-time = "2026-04-10T14:26:53.611Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c9/c5299e826a5fe6108d172b344033f61c69b1bb979dd8d9ddd4278a160971/jiter-0.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:215a6cb8fb7dc702aa35d475cc00ddc7f970e5c0b1417fb4b4ac5d82fa2a29db", size = 378488, upload-time = "2026-04-10T14:26:55.211Z" }, + { url = "https://files.pythonhosted.org/packages/5d/37/c16d9d15c0a471b8644b1abe3c82668092a707d9bedcf076f24ff2e380cd/jiter-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ab96a30fb3cb2c7e0cd33f7616c8860da5f5674438988a54ac717caccdbaa", size = 353242, upload-time = "2026-04-10T14:26:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/58/ea/8050cb0dc654e728e1bfacbc0c640772f2181af5dedd13ae70145743a439/jiter-0.14.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3a99c1387b1f2928f799a9de899193484d66206a50e98233b6b088a7f0c1edb2", size = 356823, upload-time = "2026-04-10T14:26:58.281Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/cf71506d270e5f84d97326bf220e47aed9b95e9a4a060758fb07772170ab/jiter-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab18d11074485438695f8d34a1b6da61db9754248f96d51341956607a8f39985", size = 392564, upload-time = "2026-04-10T14:27:00.018Z" }, + { url = "https://files.pythonhosted.org/packages/b0/cc/8c6c74a3efb5bd671bfd14f51e8a73375464ca914b1551bc3b40e26ac2c9/jiter-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:801028dcfc26ac0895e4964cbc0fd62c73be9fd4a7d7b1aaf6e5790033a719b7", size = 520322, upload-time = "2026-04-10T14:27:01.664Z" }, + { url = "https://files.pythonhosted.org/packages/41/24/68d7b883ec959884ddf00d019b2e0e82ba81b167e1253684fa90519ce33c/jiter-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ad425b087aafb4a1c7e1e98a279200743b9aaf30c3e0ba723aec93f061bd9bc8", size = 552619, upload-time = "2026-04-10T14:27:03.316Z" }, + { url = "https://files.pythonhosted.org/packages/b6/89/b1a0985223bbf3150ff9e8f46f98fc9360c1de94f48abe271bbe1b465682/jiter-0.14.0-cp313-cp313-win32.whl", hash = "sha256:882bcb9b334318e233950b8be366fe5f92c86b66a7e449e76975dfd6d776a01f", size = 205699, upload-time = "2026-04-10T14:27:04.662Z" }, + { url = "https://files.pythonhosted.org/packages/4c/19/3f339a5a7f14a11730e67f6be34f9d5105751d547b615ef593fa122a5ded/jiter-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:9b8c571a5dba09b98bd3462b5a53f27209a5cbbe85670391692ede71974e979f", size = 201323, upload-time = "2026-04-10T14:27:06.139Z" }, + { url = "https://files.pythonhosted.org/packages/50/56/752dd89c84be0e022a8ea3720bcfa0a8431db79a962578544812ce061739/jiter-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:34f19dcc35cb1abe7c369b3756babf8c7f04595c0807a848df8f26ef8298ef92", size = 191099, upload-time = "2026-04-10T14:27:07.564Z" }, + { url = "https://files.pythonhosted.org/packages/91/28/292916f354f25a1fe8cf2c918d1415c699a4a659ae00be0430e1c5d9ffea/jiter-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e89bcd7d426a75bb4952c696b267075790d854a07aad4c9894551a82c5b574ab", size = 320880, upload-time = "2026-04-10T14:27:09.326Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c7/b002a7d8b8957ac3d469bd59c18ef4b1595a5216ae0de639a287b9816023/jiter-0.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b25beaa0d4447ea8c7ae0c18c688905d34840d7d0b937f2f7bdd52162c98a40", size = 346563, upload-time = "2026-04-10T14:27:11.287Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3b/f8d07580d8706021d255a6356b8fab13ee4c869412995550ce6ed4ddf97d/jiter-0.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a8758dd413c51e3b7f6557cdc6921faf70b14106f45f969f091f5cda990ea", size = 357928, upload-time = "2026-04-10T14:27:12.729Z" }, + { url = "https://files.pythonhosted.org/packages/47/5b/ac1a974da29e35507230383110ffec59998b290a8732585d04e19a9eb5ba/jiter-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e1a7eead856a5038a8d291f1447176ab0b525c77a279a058121b5fccee257f6f", size = 203519, upload-time = "2026-04-10T14:27:14.125Z" }, + { url = "https://files.pythonhosted.org/packages/96/6d/9fc8433d667d2454271378a79747d8c76c10b51b482b454e6190e511f244/jiter-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e692633a12cda97e352fdcd1c4acc971b1c28707e1e33aeef782b0cbf051975", size = 190113, upload-time = "2026-04-10T14:27:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, + { url = "https://files.pythonhosted.org/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, + { url = "https://files.pythonhosted.org/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, + { url = "https://files.pythonhosted.org/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, + { url = "https://files.pythonhosted.org/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, + { url = "https://files.pythonhosted.org/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, + { url = "https://files.pythonhosted.org/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, + { url = "https://files.pythonhosted.org/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, + { url = "https://files.pythonhosted.org/packages/21/42/9042c3f3019de4adcb8c16591c325ec7255beea9fcd33a42a43f3b0b1000/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:fbd9e482663ca9d005d051330e4d2d8150bb208a209409c10f7e7dfdf7c49da9", size = 308810, upload-time = "2026-04-10T14:28:34.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/cf/a7e19b308bd86bb04776803b1f01a5f9a287a4c55205f4708827ee487fbf/jiter-0.14.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:33a20d838b91ef376b3a56896d5b04e725c7df5bc4864cc6569cf046a8d73b6d", size = 308443, upload-time = "2026-04-10T14:28:36.658Z" }, + { url = "https://files.pythonhosted.org/packages/ca/44/e26ede3f0caeff93f222559cb0cc4ca68579f07d009d7b6010c5b586f9b1/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:432c4db5255d86a259efde91e55cb4c8d18c0521d844c9e2e7efcce3899fb016", size = 343039, upload-time = "2026-04-10T14:28:38.356Z" }, + { url = "https://files.pythonhosted.org/packages/da/e9/1f9ada30cef7b05e74bb06f52127e7a724976c225f46adb65c37b1dadfb6/jiter-0.14.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67f00d94b281174144d6532a04b66a12cb866cbdc47c3af3bfe2973677f9861a", size = 349613, upload-time = "2026-04-10T14:28:40.066Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpath-ng" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/32/58/250751940d75c8019659e15482d548a4aa3b6ce122c515102a4bfdac50e3/jsonpath_ng-1.8.0.tar.gz", hash = "sha256:54252968134b5e549ea5b872f1df1168bd7defe1a52fed5a358c194e1943ddc3", size = 74513, upload-time = "2026-02-24T14:42:06.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/99/33c7d78a3fb70d545fd5411ac67a651c81602cc09c9cf0df383733f068c5/jsonpath_ng-1.8.0-py3-none-any.whl", hash = "sha256:b8dde192f8af58d646fc031fac9c99fe4d00326afc4148f1f043c601a8cfe138", size = 67844, upload-time = "2026-02-28T00:53:19.637Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-rs" +version = "0.44.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/88/f0cc7013ad6a3d0b86275a6d0a3112eaa705545c89134ab2a057865c054c/jsonschema_rs-0.44.1.tar.gz", hash = "sha256:49ca909cc3017990a732145b9a7c2f1a0727b2f95dba4190c05a514575b5f4bf", size = 1975289, upload-time = "2026-03-03T19:08:21.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/59/57efa11b8a7069687c7d741849a75092cbb4a6bdce30d52a2832a168c3c5/jsonschema_rs-0.44.1-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6f8be6467ee403e126e4e0abb68f13cfbf7199db54d5a4c0f2a1b00e1304f2e3", size = 7365683, upload-time = "2026-03-03T19:07:34.512Z" }, + { url = "https://files.pythonhosted.org/packages/02/39/b1ec92bd383d9e8e0cd70f019f0c047313e4980a3f7e653cfb3270a84310/jsonschema_rs-0.44.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:95434b4858da6feb4b3769c955b78204dbc90988941e9e848596ab93c6005d00", size = 3828559, upload-time = "2026-03-03T19:07:36.965Z" }, + { url = "https://files.pythonhosted.org/packages/39/97/0b581ce2ca6b6ca3f29cea189609c893aa3c033356a7cb6950cb7559bdc0/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0329af23e7674d88c3117b55c89a0c36e06ee359e696be16796a29c8b1c33e85", size = 3572164, upload-time = "2026-03-03T19:07:38.651Z" }, + { url = "https://files.pythonhosted.org/packages/35/a9/6d750088795947a5366cdfa6b9064680a3b0a86f61806521beb35d88c8fb/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8078c834c3cea6303796fc4925bb8646d1f68313bd54f6d3dde08c8b8eb74bc1", size = 3926333, upload-time = "2026-03-03T19:07:40.369Z" }, + { url = "https://files.pythonhosted.org/packages/a8/19/6475da01b4e81c0445698290a7b8f237e678a0dc9fbf55df663243597b70/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:502af60c802cf149185ea01edbd31a143b09aaf06b27b6422f8b8893984b1998", size = 3589764, upload-time = "2026-03-03T19:07:42.113Z" }, + { url = "https://files.pythonhosted.org/packages/fe/43/dd8d1a8dcd3dd44e7242944433d86433540ed71a5906d0d75b5dd4fb3352/jsonschema_rs-0.44.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f2760c4791ecc3c7e6196cec7e7dbf191205e36dd050119cfab421e108e8508", size = 3782136, upload-time = "2026-03-03T19:07:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/8ada7636eb2119482fecc6289c3115b27cb045384896e45b8bd0fec98d5b/jsonschema_rs-0.44.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:16d663e6c4838e4d594bd9d10c5939a6737c171d9c8600659fe6612098863d3d", size = 4151840, upload-time = "2026-03-03T19:07:46.754Z" }, + { url = "https://files.pythonhosted.org/packages/bf/7e/f163531f203fa4e11871a40a04dc280a94a2b88c2eaa32db7c71cb64b5b4/jsonschema_rs-0.44.1-cp310-abi3-win32.whl", hash = "sha256:cbec5ef1a0cc327cbc829f44a9c76778881003ada99c871a14438c7e8b264e76", size = 3197538, upload-time = "2026-03-03T19:07:48.12Z" }, + { url = "https://files.pythonhosted.org/packages/73/4b/c080db0f50b7a320c80991f3cc9069d865f6a8baadd2952fda7473cf3816/jsonschema_rs-0.44.1-cp310-abi3-win_amd64.whl", hash = "sha256:cee075749f0479599586b4f591940418e45eae65485ed29e84763a28ec9dd40c", size = 3748176, upload-time = "2026-03-03T19:07:49.698Z" }, + { url = "https://files.pythonhosted.org/packages/ed/9f/2f602bf9d3958866f03732abefc51f8bc6caa0f8ea913b8f0ac01923e886/jsonschema_rs-0.44.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:99c0c3e4a786d1e9c25dbd58cc9781f3c3d25c9fbd76310a350de55315f05948", size = 3817433, upload-time = "2026-03-03T19:07:51.161Z" }, + { url = "https://files.pythonhosted.org/packages/42/cf/d899a52ca5fd7846614e15a230845d19070eec865b0791108b14341ef39e/jsonschema_rs-0.44.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:516bfb8926de7d396e4bc9a1c5085870de0035e8e2324014251d091a55a03623", size = 3570909, upload-time = "2026-03-03T19:07:53.117Z" }, + { url = "https://files.pythonhosted.org/packages/53/bb/cc3fda5594cdc3626e479f868f28b5a1d9091296e764ca041d2580d0a292/jsonschema_rs-0.44.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225074845f6a67e8e3ac18311f87a0ab925ae5adf16466be61c7d1df01eca20a", size = 3920084, upload-time = "2026-03-03T19:07:54.827Z" }, + { url = "https://files.pythonhosted.org/packages/bc/75/49e09ce6b72f8d25813842d9184678d6be92f0a3e90f0276a995c5712986/jsonschema_rs-0.44.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:782d01412e77c83bb376d31aac8afbd06b97e3594f09d1e0304ad22c2382077b", size = 3584852, upload-time = "2026-03-03T19:07:56.508Z" }, + { url = "https://files.pythonhosted.org/packages/51/67/4e52d1ab98c8656a66ca1b0422af18da5a5525d6aa23c57be455bdcc6515/jsonschema_rs-0.44.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2afe720dfa1f93235b78e812937039537b63bf4eab6ca3c9ecb7fd7ba08a865d", size = 3776400, upload-time = "2026-03-03T19:07:58.392Z" }, + { url = "https://files.pythonhosted.org/packages/d8/44/5cd424c80df74ad159aceaf59071f26a7b7decf925a952c8929c2e097375/jsonschema_rs-0.44.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:548a1f466ce5b904c9cc52eee8f887c3838377ed95f4525d0ee5896a321e89d5", size = 4144701, upload-time = "2026-03-03T19:08:00.476Z" }, + { url = "https://files.pythonhosted.org/packages/6f/4f/4f8c9a423b2f539b22f0fc314063b724da82df3116a57149c2d730943150/jsonschema_rs-0.44.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8a758e422c4ec265e64f2232409ddc5976b28e94e84a8e5565a2bce169ab72e9", size = 3739509, upload-time = "2026-03-03T19:08:01.97Z" }, + { url = "https://files.pythonhosted.org/packages/76/85/759020a30874df8053c2abf91ed8abe8f27e69e683ed1d94ac2bbf92e7a8/jsonschema_rs-0.44.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ca8ddd724b73678f5f3d3d8f948ae40fa817ad9edd5ce4e732ae26cb0f9dd300", size = 3816826, upload-time = "2026-03-03T19:08:03.411Z" }, + { url = "https://files.pythonhosted.org/packages/45/77/47720717c3008483ff54365f65dbfb264d6dd3b3e7a2367d7f4f0a0a76e4/jsonschema_rs-0.44.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1ff6c9868c8f2834952efa0555fd82d0ab19664ba6b17f481330c64f7af7177d", size = 3569065, upload-time = "2026-03-03T19:08:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/06/88/734eb132228c09b2e0a442da6686efe8bd0c8f0095b13d52b46dcacea735/jsonschema_rs-0.44.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec883313f3782f1c0ffc58ceda55136e26967198523b9cd111af782e273659a3", size = 3918339, upload-time = "2026-03-03T19:08:07.004Z" }, + { url = "https://files.pythonhosted.org/packages/01/9c/6c4ca6c6bc906e4d74425d48c7fd49e558ec4ec98fb792d549c3ed95a632/jsonschema_rs-0.44.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f971acf2910e64f0960080db6b6c73df483318d9db992273885f596cc3a9a5d9", size = 3583541, upload-time = "2026-03-03T19:08:08.683Z" }, + { url = "https://files.pythonhosted.org/packages/14/13/f907c17fc0de4d653cac237846303a164ae58d26656d4161ad4c13d5267a/jsonschema_rs-0.44.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:50f5c28fd54236e43f392041f06132b0e9f09dd261cb00236045078d98e3cf84", size = 3774990, upload-time = "2026-03-03T19:08:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/83/1c/141b5b43db5aeac7d54cf3022cfa6a2941fe4d550afacfcf7bbcd49e66fa/jsonschema_rs-0.44.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbc59d68f38a377117b84b8109af269813a39b4b961e803876767e4fab6bac98", size = 4143282, upload-time = "2026-03-03T19:08:12.229Z" }, + { url = "https://files.pythonhosted.org/packages/79/8a/6d4d55583e97d37ca7ac5595d978a83ecfbf8c113ebe31496f7330c72a49/jsonschema_rs-0.44.1-cp314-cp314t-win_amd64.whl", hash = "sha256:049203fd4876f2ec96191c0f8befabf33289988c57e4f191b5fd5974de1fb07f", size = 3738147, upload-time = "2026-03-03T19:08:13.58Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "langchain" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/74/03fd4c07993c49c4b80635bb4c723643ff78af81c9471d1266f879f68df1/langchain-1.3.0.tar.gz", hash = "sha256:8ec70ee0cef94255f3e522423b254093a3dd34509638d353c50f3d9dd498debc", size = 580604, upload-time = "2026-05-12T14:45:50.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/6f/b9a9721c27fbb6d29a6a7cd89d6a41eeffc7c79b49b9a5cf5beb1d60952d/langchain-1.3.0-py3-none-any.whl", hash = "sha256:9ce48828c706c00c586304b519fbd910e75d9f5ab3f319c31834e42107437f43", size = 114079, upload-time = "2026-05-12T14:45:48.818Z" }, +] + +[[package]] +name = "langchain-anthropic" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anthropic" }, + { name = "langchain-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/e3/d2f9dec95602524b1cfb4be2747ba5bc38d32501b2a56cb4bcb76e80bb45/langchain_anthropic-1.4.3.tar.gz", hash = "sha256:f8a2442463c0629b1b3110eaeaa56fdbdc87df2a802f8c7f5ecf611eb4874ec8", size = 685219, upload-time = "2026-05-03T17:33:27.118Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/55/482a1968c95275e8be6d8c1e53b54f0f7be0b8b155ce1608c947a95cf543/langchain_anthropic-1.4.3-py3-none-any.whl", hash = "sha256:65466e0f2f95909a009708f2958e917dfdbfab79c612b4484a30866a85e1f291", size = 50389, upload-time = "2026-05-03T17:33:25.671Z" }, +] + +[[package]] +name = "langchain-classic" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langchain-text-splitters" }, + { name = "langsmith" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/78/84b5065816f348c39fefa4316f209f0135e8410216340a953bec17d9e4e4/langchain_classic-1.0.7.tar.gz", hash = "sha256:debbec8065e69b95108d2652e8d5c44f4516e19aa8d716c02ed2211c3aee099d", size = 10554118, upload-time = "2026-05-07T15:46:56.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/78/2d9980d028ff0523eea503a77c200e2ff252a3a75eb6e7842bcf5f9c979b/langchain_classic-1.0.7-py3-none-any.whl", hash = "sha256:d9d9be38f7aa534ed0259c2410432e34a1f80b1d491e686749bb55af56479be3", size = 1041386, upload-time = "2026-05-07T15:46:54.845Z" }, +] + +[[package]] +name = "langchain-community" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "dataclasses-json" }, + { name = "httpx-sse" }, + { name = "langchain-classic" }, + { name = "langchain-core" }, + { name = "langsmith" }, + { name = "numpy" }, + { name = "pydantic-settings" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/97/a03585d42b9bdb6fbd935282d6e3348b10322a24e6ce12d0c99eb461d9af/langchain_community-0.4.1.tar.gz", hash = "sha256:f3b211832728ee89f169ddce8579b80a085222ddb4f4ed445a46e977d17b1e85", size = 33241144, upload-time = "2025-10-27T15:20:32.504Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/a4/c4fde67f193401512337456cabc2148f2c43316e445f5decd9f8806e2992/langchain_community-0.4.1-py3-none-any.whl", hash = "sha256:2135abb2c7748a35c84613108f7ebf30f8505b18c3c18305ffaecfc7651f6c6a", size = 2533285, upload-time = "2025-10-27T15:20:30.767Z" }, +] + +[[package]] +name = "langchain-core" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langchain-protocol" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/de/679a53472c25860837e32c0442c962fa86e95317a36460e2c9d5c91b17c2/langchain_core-1.4.0.tar.gz", hash = "sha256:1dc341eed802ed9c117c0df3923c991e5e9e226571e5725c194eeb5bd93d1a7f", size = 920260, upload-time = "2026-05-11T18:42:35.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1a/86c38c27b81913a1c6c12448cab55defb5a1097c7dc9a4cea83f55477a2d/langchain_core-1.4.0-py3-none-any.whl", hash = "sha256:23cbbdb46e38ddd1dd5247e6167e96013eae74bea4c5949c550809970a9e565c", size = 548120, upload-time = "2026-05-11T18:42:33.992Z" }, +] + +[[package]] +name = "langchain-google-genai" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filetype" }, + { name = "google-genai" }, + { name = "langchain-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/78/dfe068937338727b0dee637d971d59fe2fa275f9d0f0edee3fa80e811846/langchain_google_genai-4.2.2.tar.gz", hash = "sha256:5fc774bf41d1dc1c1a5ba8d7b9f2017dfa77e30653c9b44d2dfbaf0e877e7388", size = 267457, upload-time = "2026-04-15T15:08:32.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/5c/adf81d68ab89b4cf505e690f8c1956d11b5969c831c951c7b4b1b1818080/langchain_google_genai-4.2.2-py3-none-any.whl", hash = "sha256:c8d09aac0304d26f1c2483e41a350f15587af1fbe034c39a304e1e17a3b743f3", size = 67605, upload-time = "2026-04-15T15:08:31.346Z" }, +] + +[[package]] +name = "langchain-mcp-adapters" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "mcp" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/66/1cc7039e2daaddcdea9d8887851fe6eb67401925999b2aa394aa855c7132/langchain_mcp_adapters-0.2.2.tar.gz", hash = "sha256:12d39e91ae4389c54b61b221094e53850b6e152934d8bc10c80665d600e76530", size = 37942, upload-time = "2026-03-16T17:13:30.35Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/2f/15d5e6c1765d8404a9cce38d8c81d7b33fb3392f9db5b992c000dddbd2a3/langchain_mcp_adapters-0.2.2-py3-none-any.whl", hash = "sha256:d08e64954e86281002653071b7430e0377c9a577cb4ac3143abfeb3e24ef8797", size = 23288, upload-time = "2026-03-16T17:13:29.073Z" }, +] + +[[package]] +name = "langchain-ollama" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ollama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/9b/6641afe8a5bf807e454fd464eddfc7eb2f2df53cb0b29744381171f9c609/langchain_ollama-1.1.0.tar.gz", hash = "sha256:f776f56f6782ae4da7692579b94a6575906118318d1023b455d7207f9d059811", size = 133075, upload-time = "2026-04-07T02:48:00.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/b2/c2acb076590a98bee2816ed5f285e00df162a34238f9e276e175e14ebc35/langchain_ollama-1.1.0-py3-none-any.whl", hash = "sha256:43ac83a6eacb0f43855810739794dd55019e0d9b17bdcf3ecb3b1991ac3b59dd", size = 31413, upload-time = "2026-04-07T02:47:59.642Z" }, +] + +[[package]] +name = "langchain-openai" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/0e/d8e16c28aa67106d285e63b8ffc04c5af68341e345ce24a0751dbf2e167e/langchain_openai-1.2.1.tar.gz", hash = "sha256:ee4480b787706361b7125fad46930589a624df87aa158c6986ef1fad10d10675", size = 1146092, upload-time = "2026-04-24T19:46:43.328Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/55/2865b18ee3a3dd11160b8c4b2cf37e75bf2a4a8d1d38868ffffc7b7cc180/langchain_openai-1.2.1-py3-none-any.whl", hash = "sha256:a80732185030d4f453dda6c25feef46f645f665423fdffe38ae3edf1ac3c6c4d", size = 98626, upload-time = "2026-04-24T19:46:41.971Z" }, +] + +[[package]] +name = "langchain-protocol" +version = "0.0.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4f/24/9777489d6fbbee64af0c8f96d4f840239c408cf694f3394672807dafc490/langchain_protocol-0.0.15.tar.gz", hash = "sha256:9ab2d11ee73944754f10e037e717098d3a6796f0e58afa9cadda6154e7655ade", size = 5862, upload-time = "2026-05-01T22:30:04.748Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/7a/9c97a7b9cbe4c5dc6a44cdb1545450c28f0c8ce89b9c1f0ee7fbad896263/langchain_protocol-0.0.15-py3-none-any.whl", hash = "sha256:461eb794358f83d5e42635a5797799ffec7b4702314e34edf73ac21e75d3ef79", size = 6982, upload-time = "2026-05-01T22:30:03.877Z" }, +] + +[[package]] +name = "langchain-redis" +version = "0.2.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "hf-xet" }, + { name = "httpcore" }, + { name = "jinja2" }, + { name = "langchain-core" }, + { name = "python-ulid" }, + { name = "redisvl" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/14/e2c89a0e1d9b8faed20c03a7d5c1f9b10a998bc9af024b58405dc657feed/langchain_redis-0.2.5.tar.gz", hash = "sha256:f73cde65a1d2ada301ce51d3d5f403ff845e51f8100ee6d1039fa55786eee37e", size = 36274, upload-time = "2025-11-25T00:19:19.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/5f/705ae80a50e25290476c76a7798696833a397de69aaa986a189c0915ceef/langchain_redis-0.2.5-py3-none-any.whl", hash = "sha256:73ad5c7d4039401f11f4f8de7d37615d9736ad2eca7fac71ee93c8abc3d5086a", size = 36471, upload-time = "2025-11-25T00:19:18.292Z" }, +] + +[[package]] +name = "langchain-tavily" +version = "0.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "langchain" }, + { name = "langchain-core" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/6c/b309ef3062b189a82463dc93553804566e71aa393f9ba8954750793c1a6f/langchain_tavily-0.2.18.tar.gz", hash = "sha256:cd7859ae1a6ce79236580ef67072ff5fc43c7ded94e7eac38ff04209ca85a320", size = 25378, upload-time = "2026-04-16T15:23:24.526Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/9c/0c043e4434b1823f0ac194f66036cbb0569275a99dcb890e0891ecd34fb2/langchain_tavily-0.2.18-py3-none-any.whl", hash = "sha256:dccf3ad1c50e2cb2a89bec11727555805c9df8abd42c1f3ad42ccad86e28aa44", size = 30814, upload-time = "2026-04-16T15:23:23.424Z" }, +] + +[[package]] +name = "langchain-text-splitters" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/9f/6c545900fefb7b00ddfa3f16b80d61338a0ec68c31c5451eeeab99082760/langchain_text_splitters-1.1.2.tar.gz", hash = "sha256:782a723db0a4746ac91e251c7c1d57fd23636e4f38ed733074e28d7a86f41627", size = 293580, upload-time = "2026-04-16T14:20:39.162Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/26/1ef06f56198d631296d646a6223de35bcc6cf9795ceb2442816bc963b84c/langchain_text_splitters-1.1.2-py3-none-any.whl", hash = "sha256:a2de0d799ff31886429fd6e2e0032df275b60ec817c19059a7b46181cc1c2f10", size = 35903, upload-time = "2026-04-16T14:20:38.243Z" }, +] + +[[package]] +name = "langfuse" +version = "4.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "httpx" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/31/4b7157be23e7c8c3581ac5f6547c5c003e232e7044c92398c468ef78a809/langfuse-4.6.1.tar.gz", hash = "sha256:7f256c669e610909c2e93ca3e9e4168dbef344b753b6874f14b0edd673863f17", size = 281379, upload-time = "2026-05-08T14:08:15.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/bf/3a6082f7809bdcc1269e9920c07d7c7f92a53cc265a4a879e59c92b23b36/langfuse-4.6.1-py3-none-any.whl", hash = "sha256:a696ac3089a0c8431bf7f1b47b7f6417da311f418dd04ce9ef62d63608fd8797", size = 481237, upload-time = "2026-05-08T14:08:17.141Z" }, +] + +[[package]] +name = "langgraph" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/61/d5d25e783035aa307d289b37e082258a6061c0fb4caa4a284f3bf1e87169/langgraph-1.2.0.tar.gz", hash = "sha256:4a9baaf62afc5d5f63144a50095140a34b9aa9b7cea695d25326d564775348e7", size = 690248, upload-time = "2026-05-12T03:46:39.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/e8/e3304ac0015c2bdb04ad9785e4ed65c788855ce7857ce6104dd2f5d322db/langgraph-1.2.0-py3-none-any.whl", hash = "sha256:03fd5895a8d4b70db1ff63ebc3bacead29dd20cd794a8b1a483e7ec9018f7a65", size = 234262, upload-time = "2026-05-12T03:46:37.971Z" }, +] + +[[package]] +name = "langgraph-api" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "cryptography" }, + { name = "grpcio" }, + { name = "grpcio-health-checking" }, + { name = "grpcio-tools" }, + { name = "httptools", marker = "sys_platform != 'win32'" }, + { name = "httpx" }, + { name = "jsonschema-rs" }, + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-runtime-inmem" }, + { name = "langgraph-sdk" }, + { name = "langsmith", extra = ["otel"] }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "orjson" }, + { name = "protobuf" }, + { name = "pyjwt" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "structlog" }, + { name = "tenacity" }, + { name = "truststore" }, + { name = "uuid-utils" }, + { name = "uvicorn" }, + { name = "uvloop", marker = "sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/d16bbacc2505f1ea809d5605bc0254ea628555f88b60c5385433784198cd/langgraph_api-0.8.7.tar.gz", hash = "sha256:f25a43fc6b2366803013a2434f264d1bd8da8bbf9668321d90d06c1a86886d0f", size = 621064, upload-time = "2026-05-05T16:35:33.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/db/70ff4701409240ead4a8ced54f7545c927149f2fdf65f3d1919cb50c8310/langgraph_api-0.8.7-py3-none-any.whl", hash = "sha256:c3563a577b92239b780ae8467bbd1a220aa8d74f040e423654cbbb09463829ea", size = 498922, upload-time = "2026-05-05T16:35:31.851Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/b4/6005c5dd88ad484fe6235d4c43a0d2cee7e91b08ad85a180985c2662df87/langgraph_checkpoint-4.1.0.tar.gz", hash = "sha256:e5bb304e30fc1363ac8fcb5f7dee5ca2185d77fe475b0d01de2c5f91324c2c21", size = 181942, upload-time = "2026-05-12T03:33:49.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/74/d3be2b41955e20ccd624dba5f6fe9d38dcee385ba470a6e13ed86732fc86/langgraph_checkpoint-4.1.0-py3-none-any.whl", hash = "sha256:8bc2a0466a20c38b865ce6671b42093fd5c041133f32351cae4222e0eeaf7fb5", size = 56047, upload-time = "2026-05-12T03:33:48.548Z" }, +] + +[[package]] +name = "langgraph-cli" +version = "0.4.26" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "httpx" }, + { name = "langgraph-sdk" }, + { name = "pathspec" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/94/6864c94fcc998d7b72f60a4166222f003483426446a10979c0215220dd65/langgraph_cli-0.4.26.tar.gz", hash = "sha256:d81867e45bc198f4c61a4064e8f347aba9e6ffa256db387d8daae594c59bf0f6", size = 1031744, upload-time = "2026-05-12T00:28:14.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/bc/04c66e9433e9cb2e9a764bbe08abda949224c214783aab5cb213fcabdd59/langgraph_cli-0.4.26-py3-none-any.whl", hash = "sha256:01a364b3e1b0efcef936d7a6608bd7b2c9372fbcd00e98762f44b2335696985b", size = 77464, upload-time = "2026-05-12T00:28:13.075Z" }, +] + +[package.optional-dependencies] +inmem = [ + { name = "langgraph-api" }, + { name = "langgraph-runtime-inmem" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/66/ed9b93f56bc17ef22d551892f0ac2b225a97fe0fcf23a511b857f70d590b/langgraph_prebuilt-1.1.0.tar.gz", hash = "sha256:3c579cf6eed2d17f9c157c2d0fcaddcd8688524e7022d3b22b37a3bf4589d528", size = 178833, upload-time = "2026-05-12T03:37:49.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/43/3fe1a700b8490ed02679cdbbc8c915eb23a092faf496c9c1118abcd10be3/langgraph_prebuilt-1.1.0-py3-none-any.whl", hash = "sha256:51e311747d755b751d5c6b39b0c1446124d3a7643d2515017e6714b323508fc9", size = 41043, upload-time = "2026-05-12T03:37:48.007Z" }, +] + +[[package]] +name = "langgraph-runtime-inmem" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blockbuster" }, + { name = "croniter" }, + { name = "langgraph" }, + { name = "langgraph-checkpoint" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "structlog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/c5/862cb0040b8df1b6a4a98c59bfa535465f3a255e2f6d0c4326267a9678f0/langgraph_runtime_inmem-0.28.1.tar.gz", hash = "sha256:9457cb25554df69b04aee4df9da1172cd1fa7bc9cb590a9e807f2f9e45ec8ebd", size = 122237, upload-time = "2026-05-07T18:55:52.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/df/2ba74530fdbf9664977ce0be96e32f3367175869e061a5b3d0cc25fbf073/langgraph_runtime_inmem-0.28.1-py3-none-any.whl", hash = "sha256:408af1ac462fd7e910881566e1a6d2b3837c712376f66ee04d2de11136dc84d1", size = 50393, upload-time = "2026-05-07T18:55:51.549Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.3.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/134046c20bc4a4a15d410d1d21c9e298a3e9923777b4cc867b8669bc636b/langgraph_sdk-0.3.14.tar.gz", hash = "sha256:acd1674c538e97f3cdaa610f6dd7e34bc9bad30167f0ccc482dcd563325e81f5", size = 198162, upload-time = "2026-05-05T18:40:03.524Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/96/1c9f9fbfe756ddd850a2585e7f1949d8ebb97fdaa7a5eff8f45ed1314670/langgraph_sdk-0.3.14-py3-none-any.whl", hash = "sha256:68935bf6f4924eda92617a9e5dfb4f4281197508c648cb9d62ff083907607f9d", size = 97028, upload-time = "2026-05-05T18:40:02.099Z" }, +] + +[[package]] +name = "langsmith" +version = "0.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "uuid-utils" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/8a/1e8ea5e8bab2a65fa95bd36229ef38e8723ec46e430e20ca2d953487a7f1/langsmith-0.8.3.tar.gz", hash = "sha256:767ff7a8d136ed42926bf99059ac631dc6883542d6e3104b32e71c7625e1fa05", size = 4460330, upload-time = "2026-05-07T19:56:56.18Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/a9/51e644c1f1dbc3dd7d22dfd6412eab206d538c81e024e4f287373544bdcb/langsmith-0.8.3-py3-none-any.whl", hash = "sha256:b2e40e308222fa0beb2dccee3b4b30bfee9062d7a4f20a3e3e93df3c51a08ab4", size = 399048, upload-time = "2026-05-07T19:56:53.994Z" }, +] + +[package.optional-dependencies] +otel = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, +] + +[[package]] +name = "logfire" +version = "4.32.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "executing" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-sdk" }, + { name = "protobuf" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/d7/70c6def7f3f459b2d57aa7fb37863d31b8d877e391547f200ee8c31d2e30/logfire-4.32.1.tar.gz", hash = "sha256:8e7ff418b5f2629c8a8e9426283ff82c760a30f24516c4c389d6cbb1d9768c58", size = 1089612, upload-time = "2026-04-15T14:11:57.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/77/70f6d97d7d74d2f2eeb695fe491b28906ae5c350b48516bb237ace9a1778/logfire-4.32.1-py3-none-any.whl", hash = "sha256:cb7873efec0e94a3de6e603539daaa6509a454599621c80dd227fbfa0ade37d4", size = 313021, upload-time = "2026-04-15T14:11:54.024Z" }, +] + +[package.optional-dependencies] +httpx = [ + { name = "opentelemetry-instrumentation-httpx" }, +] +requests = [ + { name = "opentelemetry-instrumentation-requests" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "marshmallow" +version = "3.26.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/79/de6c16cc902f4fc372236926b0ce2ab7845268dcc30fb2fbb7f71b418631/marshmallow-3.26.2.tar.gz", hash = "sha256:bbe2adb5a03e6e3571b573f42527c6fe926e17467833660bebd11593ab8dfd57", size = 222095, upload-time = "2025-12-22T06:53:53.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/2f/5108cb3ee4ba6501748c4908b908e55f42a5b66245b4cfe0c99326e1ef6e/marshmallow-3.26.2-py3-none-any.whl", hash = "sha256:013fa8a3c4c276c24d26d84ce934dc964e2aa794345a0f8c7e5a7191482c8a73", size = 50964, upload-time = "2025-12-22T06:53:51.801Z" }, +] + +[[package]] +name = "mcp" +version = "1.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/83/d1efe7c2980d8a3afa476f4e3d42d53dd54c0ab94c27bee5d755b45c8b73/mcp-1.27.1.tar.gz", hash = "sha256:0f47e1820f8f8f941466b39749eb1d1839a04caddca2bc60e9d46e8a99914924", size = 608458, upload-time = "2026-05-08T16:50:12.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/73/42d9596facebdb533b7f0b86c1b0364ef350d1f8ba78b1052e8a58b48b65/mcp-1.27.1-py3-none-any.whl", hash = "sha256:1af3c4203b329430fde7a87b4fcb6392a041f5cb851fd68fc674016ab4e7c06f", size = 216260, upload-time = "2026-05-08T16:50:10.547Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, + { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, + { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, + { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, +] + +[[package]] +name = "obstore" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/8c/9ec984edd0f3b72226adfaa19b1c61b15823b35b52f311ca4af36d009d15/obstore-0.8.2.tar.gz", hash = "sha256:a467bc4e97169e2ba749981b4fd0936015428d9b8f3fb83a5528536b1b6f377f", size = 168852, upload-time = "2025-09-16T15:34:55.786Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/dc/60fefbb5736e69eab56657bca04ca64dc07fdeccb3814164a31b62ad066b/obstore-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bb70ce297a47392b1d9a3e310f18d59cd5ebbb9453428210fef02ed60e4d75d1", size = 3612955, upload-time = "2025-09-16T15:33:29.527Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/844e8f382e5a12b8a3796a05d76a03e12c7aedc13d6900419e39207d7868/obstore-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1619bf618428abf1f607e0b219b2e230a966dcf697b717deccfa0983dd91f646", size = 3346564, upload-time = "2025-09-16T15:33:30.698Z" }, + { url = "https://files.pythonhosted.org/packages/89/73/8537f99e09a38a54a6a15ede907aa25d4da089f767a808f0b2edd9c03cec/obstore-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a4605c3ed7c9515aeb4c619b5f7f2c9986ed4a79fe6045e536b5e59b804b1476", size = 3460809, upload-time = "2025-09-16T15:33:31.837Z" }, + { url = "https://files.pythonhosted.org/packages/b4/99/7714dec721e43f521d6325a82303a002cddad089437640f92542b84e9cc8/obstore-0.8.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce42670417876dd8668cbb8659e860e9725e5f26bbc86449fd259970e2dd9d18", size = 3692081, upload-time = "2025-09-16T15:33:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/4ac4175fe95a24c220a96021c25c432bcc0c0212f618be0737184eebbaad/obstore-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4a3e893b2a06585f651c541c1972fe1e3bf999ae2a5fda052ee55eb7e6516f5", size = 3957466, upload-time = "2025-09-16T15:33:34.528Z" }, + { url = "https://files.pythonhosted.org/packages/4e/04/caa288fb735484fc5cb019bdf3d896eaccfae0ac4622e520d05692c46790/obstore-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08462b32f95a9948ed56ed63e88406e2e5a4cae1fde198f9682e0fb8487100ed", size = 3951293, upload-time = "2025-09-16T15:33:35.733Z" }, + { url = "https://files.pythonhosted.org/packages/44/2f/d380239da2d6a1fda82e17df5dae600a404e8a93a065784518ff8325d5f6/obstore-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a0bf7763292a8fc47d01cd66e6f19002c5c6ad4b3ed4e6b2729f5e190fa8a0d", size = 3766199, upload-time = "2025-09-16T15:33:36.904Z" }, + { url = "https://files.pythonhosted.org/packages/28/41/d391be069d3da82969b54266948b2582aeca5dd735abeda4d63dba36e07b/obstore-0.8.2-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:bcd47f8126cb192cbe86942b8f73b1c45a651ce7e14c9a82c5641dfbf8be7603", size = 3529678, upload-time = "2025-09-16T15:33:38.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/4c/4862fdd1a3abde459ee8eea699b1797df638a460af235b18ca82c8fffb72/obstore-0.8.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:57eda9fd8c757c3b4fe36cf3918d7e589cc1286591295cc10b34122fa36dd3fd", size = 3698079, upload-time = "2025-09-16T15:33:39.696Z" }, + { url = "https://files.pythonhosted.org/packages/68/ca/014e747bc53b570059c27e3565b2316fbe5c107d4134551f4cd3e24aa667/obstore-0.8.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ea44442aad8992166baa69f5069750979e4c5d9ffce772e61565945eea5774b9", size = 3687154, upload-time = "2025-09-16T15:33:40.92Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/6db5f8edd93028e5b8bfbeee15e6bd3e56f72106107d31cb208b57659de4/obstore-0.8.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:41496a3ab8527402db4142aaaf0d42df9d7d354b13ba10d9c33e0e48dd49dd96", size = 3773444, upload-time = "2025-09-16T15:33:42.123Z" }, + { url = "https://files.pythonhosted.org/packages/26/e5/c9e2cc540689c873beb61246e1615d6e38301e6a34dec424f5a5c63c1afd/obstore-0.8.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43da209803f052df96c7c3cbec512d310982efd2407e4a435632841a51143170", size = 3939315, upload-time = "2025-09-16T15:33:43.252Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c9/bb53280ca50103c1ffda373cdc9b0f835431060039c2897cbc87ddd92e42/obstore-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:1836f5dcd49f9f2950c75889ab5c51fb290d3ea93cdc39a514541e0be3af016e", size = 3978234, upload-time = "2025-09-16T15:33:44.393Z" }, + { url = "https://files.pythonhosted.org/packages/f0/5d/8c3316cc958d386d5e6ab03e9db9ddc27f8e2141cee4a6777ae5b92f3aac/obstore-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:212f033e53fe6e53d64957923c5c88949a400e9027f7038c705ec2e9038be563", size = 3612027, upload-time = "2025-09-16T15:33:45.6Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4d/699359774ce6330130536d008bfc32827fab0c25a00238d015a5974a3d1d/obstore-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bee21fa4ba148d08fa90e47a96df11161661ed31e09c056a373cb2154b0f2852", size = 3344686, upload-time = "2025-09-16T15:33:47.185Z" }, + { url = "https://files.pythonhosted.org/packages/82/37/55437341f10512906e02fd9fa69a8a95ad3f2f6a916d3233fda01763d110/obstore-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c66594b59832ff1ced4c72575d9beb8b5f9b4e404ac1150a42bfb226617fd50", size = 3459860, upload-time = "2025-09-16T15:33:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/7a/51/4245a616c94ee4851965e33f7a563ab4090cc81f52cc73227ff9ceca2e46/obstore-0.8.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:089f33af5c2fe132d00214a0c1f40601b28f23a38e24ef9f79fb0576f2730b74", size = 3691648, upload-time = "2025-09-16T15:33:49.524Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/4e2fb24171e3ca3641a4653f006be826e7e17634b11688a5190553b00b83/obstore-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87f658dfd340d5d9ea2d86a7c90d44da77a0db9e00c034367dca335735110cf", size = 3956867, upload-time = "2025-09-16T15:33:51.082Z" }, + { url = "https://files.pythonhosted.org/packages/42/f5/b703115361c798c9c1744e1e700d5908d904a8c2e2bd38bec759c9ffb469/obstore-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e2e4fa92828c4fbc2d487f3da2d3588701a1b67d9f6ca3c97cc2afc912e9c63", size = 3950599, upload-time = "2025-09-16T15:33:52.173Z" }, + { url = "https://files.pythonhosted.org/packages/53/20/08c6dc0f20c1394e2324b9344838e4e7af770cdcb52c30757a475f50daeb/obstore-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab440e89c5c37a8ec230857dd65147d4b923e0cada33297135d05e0f937d696a", size = 3765865, upload-time = "2025-09-16T15:33:53.291Z" }, + { url = "https://files.pythonhosted.org/packages/77/20/77907765e29b2eba6bd8821872284d91170d7084f670855b2dfcb249ea14/obstore-0.8.2-cp313-cp313-manylinux_2_24_aarch64.whl", hash = "sha256:b9beed107c5c9cd995d4a73263861fcfbc414d58773ed65c14f80eb18258a932", size = 3529807, upload-time = "2025-09-16T15:33:54.535Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f5/f629d39cc30d050f52b1bf927e4d65c1cc7d7ffbb8a635cd546b5c5219a0/obstore-0.8.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b75b4e7746292c785e31edcd5aadc8b758238372a19d4c5e394db5c305d7d175", size = 3693629, upload-time = "2025-09-16T15:33:56.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/ff/106763fd10f2a1cb47f2ef1162293c78ad52f4e73223d8d43fc6b755445d/obstore-0.8.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f33e6c366869d05ab0b7f12efe63269e631c5450d95d6b4ba4c5faf63f69de70", size = 3686176, upload-time = "2025-09-16T15:33:57.247Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/d2ccb6f32feeca906d5a7c4255340df5262af8838441ca06c9e4e37b67d5/obstore-0.8.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:12c885a9ce5ceb09d13cc186586c0c10b62597eff21b985f6ce8ff9dab963ad3", size = 3773081, upload-time = "2025-09-16T15:33:58.475Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/40d1cc504cefc89c9b3dd8874287f3fddc7d963a8748d6dffc5880222013/obstore-0.8.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4accc883b93349a81c9931e15dd318cc703b02bbef2805d964724c73d006d00e", size = 3938589, upload-time = "2025-09-16T15:33:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/916c6777222db3271e9fb3cf9a97ed92b3a9b3e465bdeec96de9ab809d53/obstore-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ec850adf9980e5788a826ccfd5819989724e2a2f712bfa3258e85966c8d9981e", size = 3977768, upload-time = "2025-09-16T15:34:01.25Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/66f8dc98bbf5613bbfe5bf21747b4c8091442977f4bd897945895ab7325c/obstore-0.8.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1431e40e9bb4773a261e51b192ea6489d0799b9d4d7dbdf175cdf813eb8c0503", size = 3623364, upload-time = "2025-09-16T15:34:02.957Z" }, + { url = "https://files.pythonhosted.org/packages/1a/66/6d527b3027e42f625c8fc816ac7d19b0d6228f95bfe7666e4d6b081d2348/obstore-0.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ddb39d4da303f50b959da000aa42734f6da7ac0cc0be2d5a7838b62c97055bb9", size = 3347764, upload-time = "2025-09-16T15:34:04.236Z" }, + { url = "https://files.pythonhosted.org/packages/0d/79/c00103302b620192ea447a948921ad3fed031ce3d19e989f038e1183f607/obstore-0.8.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e01f4e13783db453e17e005a4a3ceff09c41c262e44649ba169d253098c775e8", size = 3460981, upload-time = "2025-09-16T15:34:05.595Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d9/bfe4ed4b1aebc45b56644dd5b943cf8e1673505cccb352e66878a457e807/obstore-0.8.2-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df0fc2d0bc17caff9b538564ddc26d7616f7e8b7c65b1a3c90b5048a8ad2e797", size = 3692711, upload-time = "2025-09-16T15:34:06.796Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/cd6c2cbb18e1f40c77e7957a4a03d2d83f1859a2e876a408f1ece81cad4c/obstore-0.8.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e439d06c99a140348f046c9f598ee349cc2dcd9105c15540a4b231f9cc48bbae", size = 3958362, upload-time = "2025-09-16T15:34:08.277Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ea/5ee82bf23abd71c7d6a3f2d008197ae8f8f569d41314c26a8f75318245be/obstore-0.8.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e37d9046669fcc59522d0faf1d105fcbfd09c84cccaaa1e809227d8e030f32c", size = 3957082, upload-time = "2025-09-16T15:34:09.477Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ee/46650405e50fdaa8d95f30375491f9c91fac9517980e8a28a4a6af66927f/obstore-0.8.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2646fdcc4bbe92dc2bb5bcdff15574da1211f5806c002b66d514cee2a23c7cb8", size = 3775539, upload-time = "2025-09-16T15:34:10.726Z" }, + { url = "https://files.pythonhosted.org/packages/35/d6/348a7ebebe2ca3d94dfc75344ea19675ae45472823e372c1852844078307/obstore-0.8.2-cp314-cp314-manylinux_2_24_aarch64.whl", hash = "sha256:e31a7d37675056d93dfc244605089dee67f5bba30f37c88436623c8c5ad9ba9d", size = 3535048, upload-time = "2025-09-16T15:34:12.076Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/b7a16cc0da91a4b902d47880ad24016abfe7880c63f7cdafda45d89a2f91/obstore-0.8.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:656313dd8170dde0f0cd471433283337a63912e8e790a121f7cc7639c83e3816", size = 3699035, upload-time = "2025-09-16T15:34:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/3269a3a58347e0b019742d888612c4b765293c9c75efa44e144b1e884c0d/obstore-0.8.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329038c9645d6d1741e77fe1a53e28a14b1a5c1461cfe4086082ad39ebabf981", size = 3687307, upload-time = "2025-09-16T15:34:14.501Z" }, + { url = "https://files.pythonhosted.org/packages/01/f9/4fd4819ad6a49d2f462a45be453561f4caebded0dc40112deeffc34b89b1/obstore-0.8.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1e4df99b369790c97c752d126b286dc86484ea49bff5782843a265221406566f", size = 3776076, upload-time = "2025-09-16T15:34:16.207Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/7c4f958fa0b9fc4778fb3d232e38b37db8c6b260f641022fbba48b049d7e/obstore-0.8.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9e1c65c65e20cc990414a8a9af88209b1bbc0dd9521b5f6b0293c60e19439bb7", size = 3947445, upload-time = "2025-09-16T15:34:17.423Z" }, +] + +[[package]] +name = "ollama" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/72/5f12423b6b39ca8430fbe56f77fcf4ef60f63067c7c4a2e30e200ed9ec16/ollama-0.6.2.tar.gz", hash = "sha256:936d55daa684f474364c098611c933626f8d6c7d67065c5b7ae0c477b508b07f", size = 53145, upload-time = "2026-04-29T21:21:15.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/d6722beeb2d10f7a3b9ff49375708904fde18f82b5609a0bc4aeb5996a4d/ollama-0.6.2-py3-none-any.whl", hash = "sha256:3ad7daab28e5a973445c36a73882a3ef698c2ebb00e21e308652741577509f7d", size = 15115, upload-time = "2026-04-29T21:21:13.794Z" }, +] + +[[package]] +name = "openai" +version = "2.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/a1/4d5e84cf51720fc1526cc49e10ac1961abcccb55b0efb3d970db1e9a2728/openai-2.36.0.tar.gz", hash = "sha256:139dea0edd2f1b30c33d46ae1a6929e03906254140318e4608e98fe8c566f2e7", size = 753003, upload-time = "2026-05-07T17:33:17.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/1c/5d43735b2553baae2a5e899dcbcd0670a86930d993184d72ca909bf11c9b/openai-2.36.0-py3-none-any.whl", hash = "sha256:143f6194b548dbc2c921af1f1b03b9f14c85fed8a75b5b516f5bcc11a2a50c63", size = 1302361, upload-time = "2026-05-07T17:33:15.063Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz", hash = "sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa", size = 20416, upload-time = "2026-03-04T14:17:23.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl", hash = "sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149", size = 18369, upload-time = "2026-03-04T14:17:04.796Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz", hash = "sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c", size = 17772, upload-time = "2026-03-04T14:17:25.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl", hash = "sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069", size = 19960, upload-time = "2026-03-04T14:17:07.153Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz", hash = "sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7", size = 32606, upload-time = "2026-03-04T14:20:16.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl", hash = "sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63", size = 33448, upload-time = "2026-03-04T14:19:02.447Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-aiohttp-client" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/24fed4de661de107f2426b28bbd87b51eaab28a2339b62f269a36ae24505/opentelemetry_instrumentation_aiohttp_client-0.61b0.tar.gz", hash = "sha256:c53ab3b88efcb7ce98c1129cc0389f0a1f214eb3675269b6c157770adcf47877", size = 19292, upload-time = "2026-03-04T14:20:18.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/f3/1edc42716521a3f754ac32ffb908f102e0f131f8e43fcd9ab29cab286723/opentelemetry_instrumentation_aiohttp_client-0.61b0-py3-none-any.whl", hash = "sha256:09bc47514c162507b357366ce15578743fd6305078cf7d872db1c99c13fa6972", size = 14534, upload-time = "2026-03-04T14:19:05.165Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-httpx" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/2a/e2becd55e33c29d1d9ef76e2579040ed1951cb33bacba259f6aff2fdd2a6/opentelemetry_instrumentation_httpx-0.61b0.tar.gz", hash = "sha256:6569ec097946c5551c2a4252f74c98666addd1bf047c1dde6b4ef426719ff8dd", size = 24104, upload-time = "2026-03-04T14:20:34.752Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/88/dde310dce56e2d85cf1a09507f5888544955309edc4b8d22971d6d3d1417/opentelemetry_instrumentation_httpx-0.61b0-py3-none-any.whl", hash = "sha256:dee05c93a6593a5dc3ae5d9d5c01df8b4e2c5d02e49275e5558534ee46343d5e", size = 17198, upload-time = "2026-03-04T14:19:33.585Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-requests" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-util-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/c7/7a47cb85c7aa93a9c820552e414889185bcf91245271d12e5d443e5f834d/opentelemetry_instrumentation_requests-0.61b0.tar.gz", hash = "sha256:15f879ce8fb206bd7e6fdc61663ea63481040a845218c0cf42902ce70bd7e9d9", size = 18379, upload-time = "2026-03-04T14:20:46.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/a1/a7a133b273d1f53950f16a370fc94367eff472c9c2576e8e9e28c62dcc9f/opentelemetry_instrumentation_requests-0.61b0-py3-none-any.whl", hash = "sha256:cce19b379949fe637eb73ba39b02c57d2d0805447ca6d86534aa33fcb141f683", size = 14207, upload-time = "2026-03-04T14:19:51.765Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz", hash = "sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd", size = 45667, upload-time = "2026-03-04T14:17:31.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl", hash = "sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f", size = 72073, upload-time = "2026-03-04T14:17:16.673Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz", hash = "sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2", size = 184252, upload-time = "2026-03-04T14:17:31.87Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl", hash = "sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1", size = 141951, upload-time = "2026-03-04T14:17:17.961Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz", hash = "sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a", size = 145755, upload-time = "2026-03-04T14:17:32.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl", hash = "sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2", size = 231621, upload-time = "2026-03-04T14:17:19.33Z" }, +] + +[[package]] +name = "opentelemetry-util-http" +version = "0.61b0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz", hash = "sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572", size = 11361, upload-time = "2026-03-04T14:20:57.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl", hash = "sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33", size = 9281, upload-time = "2026-03-04T14:20:08.364Z" }, +] + +[[package]] +name = "orderly-set" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/88/39c83c35d5e97cc203e9e77a4f93bf87ec89cf6a22ac4818fdcc65d66584/orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce", size = 27414, upload-time = "2025-07-10T20:10:55.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7", size = 13068, upload-time = "2025-07-10T20:10:54.377Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, + { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, + { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, + { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, +] + +[[package]] +name = "pytest-recording" +version = "0.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "vcrpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/9c/f4027c5f1693847b06d11caf4b4f6bb09f22c1581ada4663877ec166b8c6/pytest_recording-0.13.4.tar.gz", hash = "sha256:568d64b2a85992eec4ae0a419c855d5fd96782c5fb016784d86f18053792768c", size = 26576, upload-time = "2025-05-08T10:41:11.231Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/c2/ce34735972cc42d912173e79f200fe66530225190c06655c5632a9d88f1e/pytest_recording-0.13.4-py3-none-any.whl", hash = "sha256:ad49a434b51b1c4f78e85b1e6b74fdcc2a0a581ca16e52c798c6ace971f7f439", size = 13723, upload-time = "2025-05-08T10:41:09.684Z" }, +] + +[[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, upload-time = "2024-03-01T18:36:20.211Z" } +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, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/54/a85eb421fbdd5007bc5af39d0f4ed9fa609e0fedbfdc2adcf0b34526870e/python_multipart-0.0.28.tar.gz", hash = "sha256:8550da197eac0f7ab748961fc9509b999fa2662ea25cef857f05249f6893c0f8", size = 45314, upload-time = "2026-05-10T11:05:16.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/a2/43bbc5860b5034e2af4ef99a0e04d726ff329c43e192ef3abaa8d7ecfce5/python_multipart-0.0.28-py3-none-any.whl", hash = "sha256:10faac07eb966c3f48dc415f9dee46c04cb10d58d30a35677db8027c825ed9b6", size = 29438, upload-time = "2026-05-10T11:05:15.052Z" }, +] + +[[package]] +name = "python-ulid" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/7e/0d6c82b5ccc71e7c833aed43d9e8468e1f2ff0be1b3f657a6fcafbb8433d/python_ulid-3.1.0.tar.gz", hash = "sha256:ff0410a598bc5f6b01b602851a3296ede6f91389f913a5d5f8c496003836f636", size = 93175, upload-time = "2025-08-18T16:09:26.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/a0/4ed6632b70a52de845df056654162acdebaf97c20e3212c559ac43e7216e/python_ulid-3.1.0-py3-none-any.whl", hash = "sha256:e2cdc979c8c877029b4b7a38a6fba3bc4578e4f109a308419ff4d3ccf0a46619", size = 11577, upload-time = "2025-08-18T16:09:25.047Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "redis" +version = "7.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/7f/3759b1d0d72b7c92f0d70ffd9dc962b7b7b5ee74e135f9d7d8ab06b8a318/redis-7.4.0.tar.gz", hash = "sha256:64a6ea7bf567ad43c964d2c30d82853f8df927c5c9017766c55a1d1ed95d18ad", size = 4943913, upload-time = "2026-03-24T09:14:37.53Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/3a/95deec7db1eb53979973ebd156f3369a72732208d1391cd2e5d127062a32/redis-7.4.0-py3-none-any.whl", hash = "sha256:a9c74a5c893a5ef8455a5adb793a31bb70feb821c86eccb62eebef5a19c429ec", size = 409772, upload-time = "2026-03-24T09:14:35.968Z" }, +] + +[[package]] +name = "redisvl" +version = "0.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpath-ng" }, + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "pydantic" }, + { name = "python-ulid" }, + { name = "pyyaml" }, + { name = "redis" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/32/1f8ade47e91c440002a8430abcfdb8a277c24f5eea03fc9f218c0e9cf471/redisvl-0.18.2.tar.gz", hash = "sha256:2323e153012767f8613a89a019dc1ff25f199f42360e350b10a21cb5aaf8c795", size = 887604, upload-time = "2026-05-12T15:40:24.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/c1/f18d49c5f39f3970e221ccb93850485a4d77ad5c7b7abdf84899f9f9f505/redisvl-0.18.2-py3-none-any.whl", hash = "sha256:8bec89de047e5264fae068a13c75f7920e1b0eacd3d8bf0f5862949124132f24", size = 225554, upload-time = "2026-05-12T15:40:22.712Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.5.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/0e/49aee608ad09480e7fd276898c99ec6192985fa331abe4eb3a986094490b/regex-2026.5.9.tar.gz", hash = "sha256:a8234aa23ec39894bfe4a3f1b85616a7032481964a13ac6fc9f10de4f6fca270", size = 416074, upload-time = "2026-05-09T23:15:19.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/9b/6550044bc44e17c84d312c031c2ec42fbdb6a4ec4e29093be3a172d08772/regex-2026.5.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57eeeb05db7979413dec5438f2db21d7ecbba787cde7a711df1a6f6df672aa06", size = 490451, upload-time = "2026-05-09T23:12:34.72Z" }, + { url = "https://files.pythonhosted.org/packages/1e/95/fc7ba4303b5a0f92446a12ee6778ef2c6c799233f5060042a31bf390cfe9/regex-2026.5.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:398c521292f4c7fb807001dcd54694d3a1fcafc179a36ad9cc56f98df85930b6", size = 292112, upload-time = "2026-05-09T23:12:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/4b/ee27938d1b2c443e89a9a10e00d2d19aa5ee300cd3d61140644e93bb083e/regex-2026.5.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f7a7c26137296beba7784de6eba69c6a93a63ccebc385e4962fe67e267a91225", size = 289599, upload-time = "2026-05-09T23:12:38.089Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dd/ba103dc19614e25f3880800ca67ce093d6e21b325d72b8383c7bf906e9fa/regex-2026.5.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6441cc660d76107934a09c22167200839a0e89604a6297f78a974e66e931d2c0", size = 796732, upload-time = "2026-05-09T23:12:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e7/f035b4fd858b050b0080bf302968dc0f59ba34e391872d54936758e6844e/regex-2026.5.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:91328f1c23d47595ca3ef0a7557fa129c5a23404b775c770697d2f35b33e0107", size = 865440, upload-time = "2026-05-09T23:12:42.059Z" }, + { url = "https://files.pythonhosted.org/packages/0a/51/8cd301ecc899aea28124357f729f4272f44de7806fc7ca02490bfbe253e8/regex-2026.5.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:93a7860539414dddaefba2b40f8771765ae17949d4c7182b876ce429e11a8309", size = 912329, upload-time = "2026-05-09T23:12:44.373Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1e/3fbe2fa1e8cebd62f3bb7d3321cff1640aca2e240b51d9bd624aad949260/regex-2026.5.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd2810d22146b6d838acc5ec15602cb6b47920aa4e33015df3868eedfd20bab8", size = 801239, upload-time = "2026-05-09T23:12:46.268Z" }, + { url = "https://files.pythonhosted.org/packages/17/2f/6f6008682bf2cf98040a0d3153a8e557b6ab728d7713d045cee4ce544ab8/regex-2026.5.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daff2bdbaf1d23e52fdff7c0b7bc2048b68f978df6a4d107ac981f94caef2e66", size = 777054, upload-time = "2026-05-09T23:12:48.051Z" }, + { url = "https://files.pythonhosted.org/packages/19/2b/eee0d20a6842ba04df4b8847a920b57ef56853f14ef85405473e586b605a/regex-2026.5.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4eeb011098fcb77af513dcef521a3dbecbf8849b1e38940759d293b7a93f5026", size = 785098, upload-time = "2026-05-09T23:12:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/4a/98/6fc1e6410feefb92159edaed5041992bfe390e8d26c721865434acbca558/regex-2026.5.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ea9c8ecfa1b73c73b626534d6626e5340d429630943672b8480724f44e84b962", size = 860095, upload-time = "2026-05-09T23:12:51.666Z" }, + { url = "https://files.pythonhosted.org/packages/18/a3/bd855e0f2cb1a978ecf6fa6bb69632dd9c3f6ea3b81cde62fde14c9daec7/regex-2026.5.9-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cd2846168eb9ee3c513902bc8225409cb1caab31d04728b145171fa1625d9621", size = 765762, upload-time = "2026-05-09T23:12:53.413Z" }, + { url = "https://files.pythonhosted.org/packages/dc/66/0ae8c092e60b14c79d24f8e0b7f0aea5bfbffdcab00b5483d13404d3c3a5/regex-2026.5.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39617fb0cde9c0e6306dc70e3bfc096f3da793219879f7ae7aa341a69fbdcf6d", size = 852100, upload-time = "2026-05-09T23:12:55.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/8dfde60fc1b21c946a893ba273403b72617edb261370cb1087099a83f088/regex-2026.5.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fd03c4f0e33280d15cae17159b899245d6b7c53d21def19b263b39655061f5ce", size = 789479, upload-time = "2026-05-09T23:12:57.573Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1c/bdcc98f9a4af4fdd166c74941174619ccff4726d3ce32faa8e9a2ecd38dd/regex-2026.5.9-cp312-cp312-win32.whl", hash = "sha256:164eba9b755ea6f244b0d881196fbc1fac09714e9782c9e2732b813142033c8e", size = 266699, upload-time = "2026-05-09T23:12:59.14Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/240d36864f9e48ace85f72e79ced97ceb7f27ce87739a947dcb834b4e6bc/regex-2026.5.9-cp312-cp312-win_amd64.whl", hash = "sha256:86f40a5d6444db30a125c9c9177e6b25dad981cbc37451fd838f145e6edac92e", size = 277783, upload-time = "2026-05-09T23:13:00.789Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b5/7b30f312b0669dff5beebe5b0989dc2d1a312b1a44fab852199c387a5b96/regex-2026.5.9-cp312-cp312-win_arm64.whl", hash = "sha256:96f5f58b54a063d7ea9dca08e1cf57bfe10499c4d579ee672da284f57f5f0070", size = 270513, upload-time = "2026-05-09T23:13:02.426Z" }, + { url = "https://files.pythonhosted.org/packages/aa/da/797e91ecec6f84135da778ddce78c20e0af5d2a15c26f87a81bc3eadb6db/regex-2026.5.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d626b84406444b165fc0ba981604edea39f0588ff1f92baa23fe50799ea9afdb", size = 490303, upload-time = "2026-05-09T23:13:04.382Z" }, + { url = "https://files.pythonhosted.org/packages/44/da/bf30abaaa737b58f4a4b8c4a03659e02fd92092c822e0197ed9e0daab917/regex-2026.5.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d7bdc0ab8f3dd7e1b4f9ab88634e13374669db86bb3c72e8292f07ae313f539f", size = 292019, upload-time = "2026-05-09T23:13:06.022Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e7/d0eaf5713828417b9e5648cf81fa9bacd4961f6ab98c380c2034f8716e35/regex-2026.5.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a8820737949116ffff55fe18f9fc644530063ba6ebfcb8314239416e78f1347c", size = 289468, upload-time = "2026-05-09T23:13:08.214Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9b/b3fdd62b003baa1a9b593cd8c8699c9651c2e80cc21a5c715707983c42d7/regex-2026.5.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0fbdbac82cb3e4450d0ccde7d7a35607f4cb2dd9fba4b8b69bfaf8c9fa6aed", size = 796749, upload-time = "2026-05-09T23:13:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/d4/30/66ab84588765f5b4b271a9ca09ef7ce2b87caa95176ec3d2ad65d7bc4902/regex-2026.5.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57e8915c7986aa33d25e4d3629cef711cd2863f2961b10409f0c04cb8b7d9020", size = 865445, upload-time = "2026-05-09T23:13:12.523Z" }, + { url = "https://files.pythonhosted.org/packages/1a/89/f05169e8588aac365f35ffc7f3bc3184f095ef4cfded7cfaa3c7fd5dbd89/regex-2026.5.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:508f56a89ba9cb26e4168cbc37dbd60a28d82430a9e18ad1d25fe0883c314ca2", size = 912322, upload-time = "2026-05-09T23:13:14.281Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/c93444052cf41581f3c884ab3fb5823daf0992f11cd4388d4275ca610558/regex-2026.5.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6d189041f15691cfa2b6c4290448ec221244d225b3f5fe9e7771b34ffcdf6e2", size = 801269, upload-time = "2026-05-09T23:13:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/50/fe/0cf96b882f540e62e8b9956599798203d599c44cf4c77917ca27400ff69b/regex-2026.5.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e82db382b44d0111b22601c509c89f64434816c9e0eef9d1989cda8cc6ff1c04", size = 777085, upload-time = "2026-05-09T23:13:18.675Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/d78d4924e7fc875557b9e9b768423925fdfaac5549d06da7810019a9bd26/regex-2026.5.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2acfb48634f64996b57f90f39afa692ff362162722581921fe92239a59960f3c", size = 785153, upload-time = "2026-05-09T23:13:20.525Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e0/5214774090e7b4524dcea3e3c4aa74141d43043f8beb49c1599db1c8b53a/regex-2026.5.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d29eebfc9525db68cad3c97eedd7f754fa265aa5cd0cf4f863b2421e1b48fc9f", size = 860164, upload-time = "2026-05-09T23:13:22.263Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e1/4a57a83350319b1271f0d7a249b8672513ed928b237a741631270de6caea/regex-2026.5.9-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:debb893095e944091c16e641a6e33c1b0f4cb61ab945ec5afbf53ce7068834d8", size = 765731, upload-time = "2026-05-09T23:13:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/12/f4/499e74a20c156fc75836ee04a72a38d1a063978f600937f9760467beb1b0/regex-2026.5.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d659eee77986549c9ea45b861c7567e44d6287c3dc9a4565478853f7b9fe2ff6", size = 852062, upload-time = "2026-05-09T23:13:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/5b/92/7eebc0d0a01e78629695f342ba17e0deaff8fb45e79cc0d7b98287da6e3e/regex-2026.5.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2efa205e6d98b24d1f3ab395c11aa15cdf10935bca283d0285e0499c284fba21", size = 789577, upload-time = "2026-05-09T23:13:27.814Z" }, + { url = "https://files.pythonhosted.org/packages/05/a4/018e71f7d2ad48c1ebe6d3ae0026f9b7cb4802fd15c7cc02fdf724355102/regex-2026.5.9-cp313-cp313-win32.whl", hash = "sha256:f3844f134e834076677dd369976e9f5068679fcb8e50102fdf6b7ac96a3ec127", size = 266691, upload-time = "2026-05-09T23:13:29.549Z" }, + { url = "https://files.pythonhosted.org/packages/e6/1d/861a93719fb9ee7dbfc3761b3797b7a3e112a5d42c6129459d2d741be9b5/regex-2026.5.9-cp313-cp313-win_amd64.whl", hash = "sha256:3527bb4942d2c14552155406cdedd906567456821848aed1cb4933a391bf5eca", size = 277747, upload-time = "2026-05-09T23:13:31.859Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c6/0a2436ae4da1ba76e51cb98943c6838a9a721faa40ebe2dce07694ae34e3/regex-2026.5.9-cp313-cp313-win_arm64.whl", hash = "sha256:56a33f191f17d8c417f99945ebdc1e691d3af9605d86ec68c7e54a57e3e17af6", size = 270500, upload-time = "2026-05-09T23:13:33.525Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e9/d21346f7b60ed58789371358ed66b09d00f832e1bd7c06e55d9da5679882/regex-2026.5.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:01f28d868834624c934b8d2e0aa1c8341337e37831f4a012f18a5afcba4cbaf3", size = 494172, upload-time = "2026-05-09T23:13:35.935Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/fd1177a2032037c681baecdb3422ee4e1424aec4e4f470ef47793d325274/regex-2026.5.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:48036f6374aaa79eb3b754ec29c61d1c6b1606749d705a13f8854fa2539671f6", size = 293952, upload-time = "2026-05-09T23:13:38.307Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/9fbf919768368d3f8a4f6c692cf2aa61e482b2b81ec6a298ace4cbf02480/regex-2026.5.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b96350aa424e79d4fd6b567b344dcbe2b2d6bfc48dfe7717587e1fa6d43da6ff", size = 292314, upload-time = "2026-05-09T23:13:40.353Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6c/e41bfeecb589716843e7c4df09ba46ff2a42961457afece19059d85caeef/regex-2026.5.9-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3af7a4903c5c04a11a196a5aa75cdd7dd3f8508132f9fb3259d9f5908e3b88", size = 811681, upload-time = "2026-05-09T23:13:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/83/a5c1c525fba0aa656e88ad0face0b1829788ef4c2fb6b26df58aa1151b84/regex-2026.5.9-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7e87577720152d2caae19fe2baaf1f8d5ca12091e9e229f03915c37d1e4b9178", size = 871135, upload-time = "2026-05-09T23:13:44.326Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/80882e799e440dd878b0979cbebf8fa4d54624a332c83037c7a701649e3f/regex-2026.5.9-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c8b9b9d294cfea3cd19c718ade7cc93492b2c4991abd9a68d0b3477ae6d8e100", size = 917265, upload-time = "2026-05-09T23:13:47.295Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ff/8db60211e2286e396aad7dc7725356c502bff0901ea05bd6cdc2e1a042b9/regex-2026.5.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:728d8bfd28a8845c8b6bc5dc7ce010453d206396786c0765c2740cb65f37791e", size = 816311, upload-time = "2026-05-09T23:13:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/4c/47/742ef579c61730f8d268e5cf1f9ce0e37e2ea041ad0f5644724f2378e463/regex-2026.5.9-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7e30b874d341fac767d7df5a0870540541c2c054b80cfaac116e8d367a8a7ff2", size = 785498, upload-time = "2026-05-09T23:13:52.25Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ab/cb0999802dcb0fb95b1ab005e8d4163d8afdd67efc2cb6b6630ac13f8cb1/regex-2026.5.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fd190e88a895a8901325fad284a3f74ea52b1da8525b76cc811fa9b1edf0ce2b", size = 801348, upload-time = "2026-05-09T23:13:54.127Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/8ca59a24c55bc34d166eefaf3717bd77772f329fdbf984d86581e0a3571c/regex-2026.5.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8e76e8161ad00694cfce6767d5dea860c6391ac5b83e5c3a39661e696f11fc7e", size = 866493, upload-time = "2026-05-09T23:13:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3d/30f2ae62cef3278bb5bb821f467277a55fb73f01032cf85997e15e8289a8/regex-2026.5.9-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ddda5340e6c01a293027dd46232fa79eaff1b48058ce7a98f572b6445b088041", size = 772811, upload-time = "2026-05-09T23:13:57.867Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ae/7d2089bcd78ad0c0161bc684339df50032acb438a7bd3305e7ddb1193cec/regex-2026.5.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:205109e96b3cf5adf8f4cd62bedde9487feb282b9497a3535451e5a24cd706a0", size = 856584, upload-time = "2026-05-09T23:13:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/a9/29/92ff47f75990131ea4f24ba17819e5a9d141e10819807e09addd73409af6/regex-2026.5.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dfbe4579b9f08036aa7d101d1835437a20783574ac66327e6b29b4018a138081", size = 803453, upload-time = "2026-05-09T23:14:01.978Z" }, + { url = "https://files.pythonhosted.org/packages/04/99/eff29f1037dcab36702c9ee5d6858cf1ce2336ea8ea2987f64245b99ea5e/regex-2026.5.9-cp313-cp313t-win32.whl", hash = "sha256:ed2c9e8068b614c574d8d30e543d617cf5379b0535d46f97ef00e904745a08b5", size = 269951, upload-time = "2026-05-09T23:14:03.661Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/8870b8981d27b22cda77bb26a5ac7ebfa9c7d9e0dea195a834a82380e748/regex-2026.5.9-cp313-cp313t-win_amd64.whl", hash = "sha256:b46b0f094dc1d3b90356c85a0bd2c9bafc4a6a190b9d6f8ddd5a033b6e088ed4", size = 281240, upload-time = "2026-05-09T23:14:05.56Z" }, + { url = "https://files.pythonhosted.org/packages/72/b1/3379415e8f135c13ac551353397cc4fe97b4978f3cac73c5fcbcded548b8/regex-2026.5.9-cp313-cp313t-win_arm64.whl", hash = "sha256:872acc074bd29ffc9913ecdfedf6ea77502312ca44a4aa0d3779089c6069d8de", size = 272383, upload-time = "2026-05-09T23:14:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/13/3e/9c3cd292d8808b3645a2ce517e200179b6d0e903f176300bd8b542e14de5/regex-2026.5.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:1bd7587a2948b4085195d5a3374eaf4a425dc3e55784c038175355ecf3bbbf8a", size = 490376, upload-time = "2026-05-09T23:14:09.64Z" }, + { url = "https://files.pythonhosted.org/packages/60/70/d43ee8a2ca0a8b68d167f21658b85520ac0574617c7f320367c5047f7556/regex-2026.5.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dea2e88e1cce4522496cce630e11e67b98b7076620bc4336c3f674bc21a375f4", size = 291964, upload-time = "2026-05-09T23:14:11.424Z" }, + { url = "https://files.pythonhosted.org/packages/21/91/9d50b433828d8e74196904e168a43abf1e6e88b2a15d47ed742456720c37/regex-2026.5.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2099f7e7ff7b6aa3192312650a56e91cc091e49d50b04e4f6f8b6e28b3b27f1c", size = 289682, upload-time = "2026-05-09T23:14:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/b835e3cafbb9d977736912436259ff551d60919f7d7b3d37d46659c63564/regex-2026.5.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecd353045824e4477562a2ac718c25799cdaaa41f7aa925a806a8a3e6848a5b9", size = 796996, upload-time = "2026-05-09T23:14:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a6/9f992d00019166b9de01c546dd4549bc679f2a68df11b877740b0760b7c2/regex-2026.5.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65c8c8c37377794bd5b2f3ebe51919042bf17aec802e23c833d89782ed0c78af", size = 866089, upload-time = "2026-05-09T23:14:17.757Z" }, + { url = "https://files.pythonhosted.org/packages/e0/08/4d32af657e049b19cb62b02e46e38fe1518797bfb2203ee93a510b21b0dc/regex-2026.5.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b73ab8afcf66c622db143d1c6fda4e58e4d537ee4f125229ad47b1ab80f34c0", size = 911530, upload-time = "2026-05-09T23:14:20.353Z" }, + { url = "https://files.pythonhosted.org/packages/d9/27/2af43dd1dc201d1fecefda64a45f4ad0995855b92724f795a777b402ee69/regex-2026.5.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0de5cf193997384ed2ca6f1cd4f78055b255d93d82d5a8cd6ba0d11c10b167e4", size = 800643, upload-time = "2026-05-09T23:14:22.265Z" }, + { url = "https://files.pythonhosted.org/packages/a4/dd/23a249047013b5321d4a60c4d2437462086f601b061776a525e5fba2a59f/regex-2026.5.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d641a8c9a61618047796d572a39a79b26167b0411d2c3031937b2fe2d081e2cf", size = 777223, upload-time = "2026-05-09T23:14:24.179Z" }, + { url = "https://files.pythonhosted.org/packages/94/6a/e85ed9538cd19586d0465076a4578a12e093ce776d15f3f8ce92733a8dd6/regex-2026.5.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24b2355ef5cc9aa5b8f07d17704face1c166fdcc2290fa7bd6e6c925655a8346", size = 785760, upload-time = "2026-05-09T23:14:26.065Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c4/f25473209438638e947c55f9156fd8f236f74169229028cc99116380868e/regex-2026.5.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a24852d3c29ad9e47593593d8a247c44ccc3d0548ef12c822d6ed0810affe676", size = 860891, upload-time = "2026-05-09T23:14:28.17Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f7/f4f86e3c74419c37370e91f150ae0c2ef7d34b2e0e4cdd5da046a02e4022/regex-2026.5.9-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:916714069da19329ef7de197dcbc77bb3104145c7c2c864dbfbe318f46b88b14", size = 765891, upload-time = "2026-05-09T23:14:30.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/70/704d8e13765939146b1cd0ef4e2feb71d7929727d2290f026eed10095955/regex-2026.5.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:fa411799ca8da32a8d38d020a88faa5b6f91657d284761352940ecf9f7c3bbdd", size = 851380, upload-time = "2026-05-09T23:14:32.123Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/1a13582a8460038edc38e49f64ceb0dd7c60f5caba77571f4bf6601965d9/regex-2026.5.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e6da47d679b7010ef27556b6e0f99771b744936db1792a10ceac6547ae1503e", size = 789350, upload-time = "2026-05-09T23:14:34.799Z" }, + { url = "https://files.pythonhosted.org/packages/73/56/3dcafe34fc72e271d62ad9a291801e88a1457bb251c132f15fcc2e5aad1a/regex-2026.5.9-cp314-cp314-win32.whl", hash = "sha256:98bd73080e8756255137e1bd3f3f00295bbc5aa383c0e0f973920e9134d7c4ad", size = 272130, upload-time = "2026-05-09T23:14:36.729Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/02eebf0be95efe416c664db7fb8b6b05b7a0b06a7544f2884f2558b0526f/regex-2026.5.9-cp314-cp314-win_amd64.whl", hash = "sha256:ff8d372ac2acdc048d1c19916f27ee61bc5722728458ba6ca5052f2c72d51763", size = 280999, upload-time = "2026-05-09T23:14:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/70/5a/1dd1abee76cb7a846a0bcf42fdc87e5720c3c33c24f3e37814310a513d9f/regex-2026.5.9-cp314-cp314-win_arm64.whl", hash = "sha256:e1d93bf647916292e8edcec150c07ddf3dc50179ccaf770c04a7f9e452155372", size = 273500, upload-time = "2026-05-09T23:14:41.059Z" }, + { url = "https://files.pythonhosted.org/packages/86/c1/c5f619b0057a7965cb78ec559c1d7a45ce8c99a35bea95483d64959a93d9/regex-2026.5.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:83d0ee4a57d1c87cb549e195ec300b8f0ec3a82eba66d835e4e2ed8634fe4499", size = 494269, upload-time = "2026-05-09T23:14:42.869Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/5d01f1aee33de4bbe60c8452945bfc8477ca7c5ae4450f6bfe711036cb36/regex-2026.5.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d3d7eb5c9a7f6df82ed3cfac9beb93882a5cbcb5b8b157b56cb2b3b276574ac1", size = 293954, upload-time = "2026-05-09T23:14:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/e8988b2ae2108c6ef71bd4aa8d87fbe257976dd0810e826cd75f701c68b6/regex-2026.5.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:075160bf16658e16d35233300b8453aac25de4cbea808d22348b6979668e924d", size = 292405, upload-time = "2026-05-09T23:14:47.211Z" }, + { url = "https://files.pythonhosted.org/packages/79/34/d2b0937faa7859263f7f0a3c6b103a1296306be6952dc173d0154e9a2f49/regex-2026.5.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45375819235558a4ff1c4971dc32881f022613abdb180128f5cb4768c1765a1c", size = 811855, upload-time = "2026-05-09T23:14:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/80/fe/daf53a47457a8486db66c66c01ceb9c2303eecee3f87197f1e77eb1a736d/regex-2026.5.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ead4b163ac30a29574510cd4b3e2e985ac5290c05fc7095557d6a5f403fc31b5", size = 871189, upload-time = "2026-05-09T23:14:51.555Z" }, + { url = "https://files.pythonhosted.org/packages/1c/75/058fc4470cbfbf57d800aff1a0022b929a3f9fa553ee10a0cdf2070eb31f/regex-2026.5.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8c6e4218fbdfbcd4f6c19efca40930d24a621bf4b48cb76bc6640543bd28ef20", size = 917485, upload-time = "2026-05-09T23:14:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/88/e7/179cfda3a28bc843b5c6cfe7f79f23489c791ed95f151083803660878432/regex-2026.5.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6351571c8a42b505eb555c0dc47d740d0fb66977dc142919eea6f4325b7c56a0", size = 816369, upload-time = "2026-05-09T23:14:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/41/90/6f0cc422071688266d344fca8462d787cba0a2c144acb25721f9a61ec265/regex-2026.5.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:002205cafd2a9e78c6290c7d1df277bf3277b3b7a30e0b4bb0dac2e2e3f7cb2d", size = 785869, upload-time = "2026-05-09T23:14:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/02/67/a31f1760f09c27b251ef39e9beb541f462cf977381d067faa764c2c0e393/regex-2026.5.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8abd33fef90b2a9efac5557d6033ca82d1195ed3a15fea5af15ba7b463c6a63b", size = 801427, upload-time = "2026-05-09T23:15:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c4/1a80654597b6bc1e1ea0494824c31200e8a956abe290afae9b19a166a148/regex-2026.5.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:31037c82eccb44b7ea2e9e221d7c01429430e989a1f4b91ea5a855f6017b509a", size = 866482, upload-time = "2026-05-09T23:15:03.384Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/960724e06482c08466ff5611e242e86f80062949cdf6b4b9cc317b9dd93d/regex-2026.5.9-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5604dfd046dc37eca90250fc3be938b076c8059fa772ac0ed6f499b0f0fb0415", size = 773022, upload-time = "2026-05-09T23:15:05.625Z" }, + { url = "https://files.pythonhosted.org/packages/50/a8/a9979c3e7918280e93159ebcab5ef1a65116dd4f3bd6091be0eae4a126e8/regex-2026.5.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e1b1b4e496afbb24f4a62aba855ee4f88f25578927697b340702e48c9ee6bc2", size = 856642, upload-time = "2026-05-09T23:15:07.966Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/a9b732f2f0072c0ab12227483abb24fffcb9f73f8a2b203df0a6d0434735/regex-2026.5.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:be3372b9df6ddecff6486d37e19095a7b4973137caf5512407a89f4455361f41", size = 803552, upload-time = "2026-05-09T23:15:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fe/1b3113817447a1d4155e4ac76d2e072f42c0bcba2f43fa8a0e756ea2cd91/regex-2026.5.9-cp314-cp314t-win32.whl", hash = "sha256:3ddd90103f9e5c471c49c7852ecc1fe27c7e45eb99e977aefe7caa4e779f4f58", size = 275746, upload-time = "2026-05-09T23:15:12.609Z" }, + { url = "https://files.pythonhosted.org/packages/92/73/93d42045302636c91f2e5ef588b65b84b01428f28ec77de256b1dfdfbe5c/regex-2026.5.9-cp314-cp314t-win_amd64.whl", hash = "sha256:ca518ed29c46eecba6010b15f1b9a479314d2de409536e71b6a13aa04e3b8a77", size = 285685, upload-time = "2026-05-09T23:15:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/da/80/35b4c33c804a165a7f55289afda3ea9e3eb6d15800341a2d66455c0f1f30/regex-2026.5.9-cp314-cp314t-win_arm64.whl", hash = "sha256:5e41809d2683fcde7d5a8c87a6567ba1fb1ce0de9f31bff578de00a4b2d76daa", size = 275713, upload-time = "2026-05-09T23:15:16.98Z" }, +] + +[[package]] +name = "requests" +version = "2.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/b8/7a707d60fea4c49094e40262cc0e2ca6c768cca21587e34d3f705afec47e/requests-2.34.0.tar.gz", hash = "sha256:7d62fe92f50eb82c529b0916bb445afa1531a566fc8f35ffdc64446e771b856a", size = 142436, upload-time = "2026-05-11T19:29:51.717Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e6/e300fce5fe83c30520607a015dabd985df3251e188d234bfe9492e17a389/requests-2.34.0-py3-none-any.whl", hash = "sha256:917520a21b767485ce7c588f4ebb917c436b24a31231b44228715eaeb5a52c60", size = 73021, upload-time = "2026-05-11T19:29:49.923Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[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, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[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, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.49" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" }, + { url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" }, + { url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" }, + { url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" }, + { url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" }, + { url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" }, + { url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" }, + { url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" }, + { url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" }, + { url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/8c/f9290339ef6d79badbc010f067cd769d6601ec11a57d78569c683fb4dd87/sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1", size = 32427, upload-time = "2026-03-29T09:00:23.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/7f/3de5402f39890ac5660b86bcf5c03f9d855dad5c4ed764866d7b592b46fd/sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1", size = 14330, upload-time = "2026-03-29T09:00:21.846Z" }, +] + +[[package]] +name = "starlette" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, +] + +[[package]] +name = "structlog" +version = "25.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/52/9ba0f43b686e7f3ddfeaa78ac3af750292662284b3661e91ad5494f21dbc/structlog-25.5.0.tar.gz", hash = "sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98", size = 1460830, upload-time = "2025-10-27T08:28:23.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "toolbox-core" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "deprecated" }, + { name = "google-auth" }, + { name = "pydantic" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/5c/599a13fb527bda261b276f3b6d60a10c24eac433f0ac1c6100f596b9f925/toolbox_core-1.0.0.tar.gz", hash = "sha256:4da19c19797b15ffddfd2526be191ec084549348abef01c91738e6da174b0825", size = 52775, upload-time = "2026-03-25T12:50:18.35Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/86/6685bcce748cb27dc8a0cf5e5abe207081fadc0c31d81de31fe81dfc52db/toolbox_core-1.0.0-py3-none-any.whl", hash = "sha256:f181c33a1b3ac4189794036b9d36b192b2f0c405f947df09fa1aed83e4e1dc67", size = 51011, upload-time = "2026-03-25T12:50:17.108Z" }, +] + +[[package]] +name = "toolbox-langchain" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "deprecated" }, + { name = "langchain-core" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "toolbox-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/b7/e0cabb3e0dc849e2a6974584d8e3eabf7f596440a276956bc5d5bb31afa2/toolbox_langchain-1.0.0.tar.gz", hash = "sha256:06c0174a3e9b15a191c29bdfbf1c4b0a11dcc697786c5d47cca8091196eec1b3", size = 18839, upload-time = "2026-03-25T16:02:33.943Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/96/24f40ddec99e0c4cc97829b79e362ed82f7a48aeecf137fe67384aa43080/toolbox_langchain-1.0.0-py3-none-any.whl", hash = "sha256:4ac07aeff3faee92ab2601c309058fed71fab1c1ff5ec27a31403c151e276de2", size = 12088, upload-time = "2026-03-25T16:02:32.269Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + +[[package]] +name = "ty" +version = "0.0.35" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/53/440e7b1212c4b0abbd4adb7aed93f4971aa1f8dca386ac5515930afa9172/ty-0.0.35.tar.gz", hash = "sha256:8375c240ab38138a19db07996c9808fb7a92047c1492e1ce587c2ef5112ad3a9", size = 5629237, upload-time = "2026-05-10T18:25:17.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/84/19662ee881675815b7fafff940a365be1985730465afd9b75cb2edd5f8b3/ty-0.0.35-py3-none-linux_armv6l.whl", hash = "sha256:85ae1e59b9fb0b40e9d84fe61b29653c5f2f5e78b487ece371a7a38c20c781cf", size = 11198741, upload-time = "2026-05-10T18:24:49.378Z" }, + { url = "https://files.pythonhosted.org/packages/62/df/7e5b6f83d85b4d2e5b72b5dceb388f440acc10679417bd46f829b9200fab/ty-0.0.35-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:709dbb7af4fcadb1196863c00b8791bbbbcc9dacbe15a0ff17f0af82b35d415b", size = 10948304, upload-time = "2026-05-10T18:24:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/59/94/72d7263aca055cde427f0ebcf08d6a74e5a5fee1d1e7fdd553696089cecb/ty-0.0.35-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2cb0877419ab0c8708b6925cb0c2800b263842bd3c425113f200538772f3a0cc", size = 10407413, upload-time = "2026-05-10T18:24:37.422Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/fda6fae8a81ce0cb5f24cdfe63260e110c7af8844e31fa07d1e6e8ef0232/ty-0.0.35-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7afbcfc61904b7e82e7fe1a1db832a40d8f01e69dee1775f6594e552980536c", size = 10932614, upload-time = "2026-05-10T18:24:47.401Z" }, + { url = "https://files.pythonhosted.org/packages/72/3d/b98d8d4aa1a5ed6daaf15864e838f605ca7b1e8b93b7e17b96ed4bc4dfed/ty-0.0.35-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b61498cc3e4178031c079951257fbdb209a891b4feb10ad6c40f615a51846f41", size = 10962982, upload-time = "2026-05-10T18:24:44.88Z" }, + { url = "https://files.pythonhosted.org/packages/18/c4/2881aad71bf6fb2f8df17fc8e4bc89e904e54490a3ee747b5ef73f98ac85/ty-0.0.35-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:573b1eacda349fc8dba0d767b41631c3a6f66412363127c5bf2b1b40a1d898d2", size = 11476274, upload-time = "2026-05-10T18:24:42.4Z" }, + { url = "https://files.pythonhosted.org/packages/34/0f/7717650adaeaddd23eea70470e2c26d3f0b9b18fdc7f26ec9552d6001f17/ty-0.0.35-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a7209746158d6393c1040aa64b3ca29622e212ea7d8bae22ba50dbcbb4f96f0a", size = 12012027, upload-time = "2026-05-10T18:25:00.752Z" }, + { url = "https://files.pythonhosted.org/packages/22/c9/1a16cb4aab6f4707d8f550772e91abc26d1c8870f19b5e2453ad10bb8209/ty-0.0.35-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4466a1470aa4418d49a9aa45d9da7de42033addd0a2837c5b2b0eb71d3c2bcd3", size = 11648894, upload-time = "2026-05-10T18:25:12.44Z" }, + { url = "https://files.pythonhosted.org/packages/18/a1/a977c0e07e9f88db9c67f90c6342a4dc4422c8091fa07bf26521870687c5/ty-0.0.35-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb44bb742d52c309dcaa6598bcf4d82eb4bf1241b9e4940461e522e30093fe8b", size = 11560482, upload-time = "2026-05-10T18:25:05.172Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c1/a5fb11227d5cc4ac3f29a115d8c8bc817578e8ef6907d1e4c914ddbf45ee/ty-0.0.35-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:34b219250736c989b2670a03782c61315f523f3a2be37f1f90b1207e2212c188", size = 11718495, upload-time = "2026-05-10T18:24:54.12Z" }, + { url = "https://files.pythonhosted.org/packages/3c/cb/e92e4317388b6d1fd821a46941b448a8a1ff0bf13e22147c5167d8fa1b00/ty-0.0.35-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:88e2ac497decc0940ef1a07571dee8a746112a93a09cdc7f8bca0099752e2e05", size = 10900815, upload-time = "2026-05-10T18:25:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/e9/4f/03bd87388a92567f262f35ac64e10d2be047d258f2dfcf1405f500fa2b90/ty-0.0.35-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:02cae51b53e6ec17d5d827ff1a3a76fd119705b56a92156e04399eda6e911596", size = 10998051, upload-time = "2026-05-10T18:25:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/b4/60/6edbc375ee6073973200096168f644e1081e5e55a7d42596826465b275de/ty-0.0.35-py3-none-musllinux_1_2_i686.whl", hash = "sha256:11871d730c9400d899ac0b9f3d660ed2e7e433377c8725549f8250a36a7f2620", size = 11148910, upload-time = "2026-05-10T18:24:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b1/a845d2066ed521c477450f436d4bd353d107e7c02dd6536a485944aaf892/ty-0.0.35-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ad0a2f0530d0933dcc99ad36ac556c63e384ea72ab9a18d23ad2e2c9fd61c73", size = 11671005, upload-time = "2026-05-10T18:24:56.223Z" }, + { url = "https://files.pythonhosted.org/packages/73/81/1d5912a54fb66b2f95ac828ae61d422ef5afeae1263e4d231e40796c229f/ty-0.0.35-py3-none-win32.whl", hash = "sha256:0e25d63ec4ab116e7f6757e44d16ca9216bca679d19ecc36d119cf80faada61a", size = 10481096, upload-time = "2026-05-10T18:24:39.976Z" }, + { url = "https://files.pythonhosted.org/packages/3b/36/1c7f8632bfec1c321f01581d4c940a3617b24bd3e8b37c8a7363d33fbfc4/ty-0.0.35-py3-none-win_amd64.whl", hash = "sha256:6a0a6d259f6f2f8f2f954c6f013d4e0b5eba68af6b353bf19a47d59ec254a3d5", size = 11555691, upload-time = "2026-05-10T18:25:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fb/59325221bce52f6e833d6865ce8360ef7d5e1e21151b38df6dc77c4327a7/ty-0.0.35-py3-none-win_arm64.whl", hash = "sha256:619c52c0fb2aa21961a848a1995135ad3b6d0a9aa54da0194e60f679cc200e13", size = 10925457, upload-time = "2026-05-10T18:25:10.352Z" }, +] + +[[package]] +name = "types-aiofiles" +version = "25.1.0.20260508" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/d9/60e8b26ad7e57eb3b58f3370b35f0d740dd0909079417e784f4e6c0f92f6/types_aiofiles-25.1.0.20260508.tar.gz", hash = "sha256:d26b07bb28f36c154c77d33982e506ee462044932d42c4eea6e78f69d1de5b84", size = 14851, upload-time = "2026-05-08T04:49:48.446Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/88/31d8fb1a3d0a96e1e41cfc58c67f48634e840e2598336887fa5be1dd9c82/types_aiofiles-25.1.0.20260508-py3-none-any.whl", hash = "sha256:c35d2be25a7e4b881da7f62ff3823db3770b2f704f31ac69681c227569e808cc", size = 14365, upload-time = "2026-05-08T04:49:47.302Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "uom-orchestrator" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "aiofiles" }, + { name = "daytona" }, + { name = "deepagents" }, + { name = "deepdiff", extra = ["optimize"] }, + { name = "fastapi" }, + { name = "httpx" }, + { name = "langchain" }, + { name = "langchain-community" }, + { name = "langchain-mcp-adapters" }, + { name = "langchain-ollama" }, + { name = "langchain-openai" }, + { name = "langchain-redis" }, + { name = "langchain-tavily" }, + { name = "langfuse" }, + { name = "langgraph" }, + { name = "logfire", extra = ["httpx", "requests"] }, + { name = "ollama" }, + { name = "orjson" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "toolbox-langchain" }, + { name = "types-aiofiles" }, + { name = "uvicorn" }, +] + +[package.optional-dependencies] +dev = [ + { name = "ruff" }, + { name = "ty" }, +] + +[package.dev-dependencies] +dev = [ + { name = "anyio" }, + { name = "debugpy" }, + { name = "langgraph-cli", extra = ["inmem"] }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-recording" }, + { name = "ty" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiofiles", specifier = ">=24.1.0" }, + { name = "daytona", specifier = ">=0.174.0" }, + { name = "deepagents", specifier = ">=0.5.6" }, + { name = "deepdiff", extras = ["optimize"], specifier = ">=9.0.0" }, + { name = "fastapi", specifier = ">=0.136.1" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "langchain", specifier = ">=1.3.0" }, + { name = "langchain-community", specifier = ">=0.4.1" }, + { name = "langchain-mcp-adapters", specifier = ">=0.2.2" }, + { name = "langchain-ollama", specifier = ">=1.1.0" }, + { name = "langchain-openai", specifier = ">=1.2.1" }, + { name = "langchain-redis", specifier = ">=0.2.5" }, + { name = "langchain-tavily", specifier = ">=0.2.18" }, + { name = "langfuse", specifier = ">=4.6.1" }, + { name = "langgraph", specifier = ">=1.2.0" }, + { name = "logfire", extras = ["httpx", "requests"], specifier = ">=4.32.1" }, + { name = "ollama", specifier = ">=0.6.2" }, + { name = "orjson", specifier = ">=3.11.8" }, + { name = "pydantic", specifier = ">=2.13.3" }, + { name = "python-dotenv", specifier = ">=1.2.2" }, + { name = "python-json-logger", specifier = ">=4.1.0" }, + { name = "redis", specifier = ">=7.4.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6.1" }, + { name = "toolbox-langchain", specifier = ">=1.0.0" }, + { name = "ty", marker = "extra == 'dev'", specifier = ">=0.0.23" }, + { name = "types-aiofiles", specifier = ">=24.1.0" }, + { name = "uvicorn", specifier = ">=0.46.0" }, +] +provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [ + { name = "anyio", specifier = ">=4.13.0" }, + { name = "debugpy", specifier = ">=1.8.20" }, + { name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.24" }, + { name = "pytest", specifier = ">=9.0.3" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "pytest-recording", specifier = ">=0.13.4" }, + { name = "ty", specifier = ">=0.0.34" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/f6/1856dc5935a947a062fb8fefd8a26e0f9f6694320e7203c7e85bd291dc93/uuid_utils-0.15.0.tar.gz", hash = "sha256:f182733e3d88edd2ceeca292627e2b1d5fa8693abe00b160de5517616ed399ea", size = 42182, upload-time = "2026-05-11T12:07:01.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/1d/5869a54e85753078a532958d7fc27dbccb48f10f428498f5a77ae700be28/uuid_utils-0.15.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2e68c9d2927ab3b79892f6f9d857cffdb2043be33044854b05a84634ffdad88b", size = 559609, upload-time = "2026-05-11T12:08:38.493Z" }, + { url = "https://files.pythonhosted.org/packages/f6/83/142a2ea23cca01609587b878c4a471ccec82dfab40e70fc1f463d98a618b/uuid_utils-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bceb8aefc5c26ed896f93a36344ff476085f340d051a73074603426ef7588e4d", size = 288304, upload-time = "2026-05-11T12:07:47.94Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/8c75511cf355e749f9fb71c0a8e228e82b47efd9db1214daecb69db8bd07/uuid_utils-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bfaab7ec64936ceae273ec195673acbee247d69525a2186159360d46d54819a0", size = 324652, upload-time = "2026-05-11T12:06:24.798Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5b/16c17ebc6af1d1ecf737b14da538d53383969ab805207819383e66ef6a9c/uuid_utils-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a30412da63cc484bc7e132f4362b4b44ea7dc1ec19ca33378c9bf9f64c5e294d", size = 331281, upload-time = "2026-05-11T12:07:10.91Z" }, + { url = "https://files.pythonhosted.org/packages/80/b5/25e0dd967398bc57fca9265acfa44be8daa8e82f1a7e7bbf7de54ea35ada/uuid_utils-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98b74c6b46e0082c3b8ec2fbe1eb65376d8caf9ed2c903a457350d56260764c3", size = 444048, upload-time = "2026-05-11T12:06:29.722Z" }, + { url = "https://files.pythonhosted.org/packages/8b/32/a383438d884f1e991b9b76e8da7e72a046ecacdd9f6d59695cd049467fbf/uuid_utils-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4b2f5b10f61ce498736b75c4f9fdb16b564ee92649f2ec41505e2584d86ff3", size = 324658, upload-time = "2026-05-11T12:07:18.763Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4e/72b460c19c036db1d78fd7b2b8e95b98a5c57f2f872ac5abfd1b3766999f/uuid_utils-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4dbafbb3ee8828d3ef50414e4691e38b1202ce5f80c96a017f12a0821b8c791d", size = 348304, upload-time = "2026-05-11T12:08:42.086Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d1/d0057b927502dcb65cf29b1f374d9da6aa9acc3b2fb06cb061c50cbf8891/uuid_utils-0.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97221ee09f9c97e9e32a5a468afa8b5d1440b65e7a57d4a0c2c9fe0546fc529b", size = 501057, upload-time = "2026-05-11T12:06:31.225Z" }, + { url = "https://files.pythonhosted.org/packages/cb/88/d99699f62030093768a387ebd0414c6918a35d85b54513d795dbf8344a5a/uuid_utils-0.15.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:704c709d1054079a756e7baf0be2e76cb766d3fd2b3b6c71b1b758258c1d24e0", size = 606248, upload-time = "2026-05-11T12:08:14.536Z" }, + { url = "https://files.pythonhosted.org/packages/65/fa/89798bae188dd33e059fa32f33acb2e6188fe27ea24bc95cdfc8454c525f/uuid_utils-0.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bc9cf4c4a7058f06b67b8cf81f228ccd80ba1ef506e875eed33d05ff19e9a32b", size = 564794, upload-time = "2026-05-11T12:08:44.496Z" }, + { url = "https://files.pythonhosted.org/packages/db/2b/c91039a0651a37fbf009f156b9df3aa0d65a6b53aae44192874a341181e0/uuid_utils-0.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e0c1d03e7d245f03d968f1da709e396f37f56495e231a22bd47f94ab6ae8827", size = 529717, upload-time = "2026-05-11T12:07:27.839Z" }, + { url = "https://files.pythonhosted.org/packages/68/af/fc4ce13a3c25efb3ad7a50b97e1fef84d544cdd9119f30c116d2318905e3/uuid_utils-0.15.0-cp312-cp312-win32.whl", hash = "sha256:65fff497efacde5edf8627d59663a498f12f38e7eae51a7723dd881b5cf15ec7", size = 168200, upload-time = "2026-05-11T12:07:03.842Z" }, + { url = "https://files.pythonhosted.org/packages/88/74/d1c1ea655d4cd45d351fb216ba80fe3ac12ef8d5a512c2f843449bedfa78/uuid_utils-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:19f73783b7ab5a560368702f245bd550cd88e3b64ef33e689aebc67b51d782b3", size = 173974, upload-time = "2026-05-11T12:07:59.863Z" }, + { url = "https://files.pythonhosted.org/packages/6c/41/994a2812629b889116dfcc14d5edb72ca188dfbd7c977042ae718fd121f5/uuid_utils-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:151dcf8aafd93d3747e6cac3d2de8173b4e8880b57db815fd51d945cb434afac", size = 172236, upload-time = "2026-05-11T12:06:44.451Z" }, + { url = "https://files.pythonhosted.org/packages/50/a5/27c31c42a66fb11c2cee1b0be77e6bda3363b6920f6e6105c2402596ac09/uuid_utils-0.15.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3334a5fdb5d5241c4f764382f01eeac6f56fc8fddf49924cd78a47e5c86ed329", size = 560586, upload-time = "2026-05-11T12:07:53.856Z" }, + { url = "https://files.pythonhosted.org/packages/3e/89/a6a79248bdb7f46a9edfa1e1d1777bd4ad57e5b278cbb4daaf602f125cc9/uuid_utils-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7ea97b77218b431c4854f2ccd502819d78d1109188fccabaa005cff61c2ccc81", size = 288804, upload-time = "2026-05-11T12:07:46.957Z" }, + { url = "https://files.pythonhosted.org/packages/02/79/3ddb82178963627693a836f81ab0cdfb2371d73f795a4be4937456e15df9/uuid_utils-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fb8636100cd521325ac90a9c3ad6d4e6cc39ee39ce78bf757c014aaab79b780", size = 324895, upload-time = "2026-05-11T12:07:51.407Z" }, + { url = "https://files.pythonhosted.org/packages/ad/78/1b8aedb556a20b268ffacf20bea115ce163c5019c3c66768c3a44141317d/uuid_utils-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:80a23d5728d82666e788810d67f2dd57b209d4e95929d61d978e02d1d7ab27bc", size = 331448, upload-time = "2026-05-11T12:07:43.949Z" }, + { url = "https://files.pythonhosted.org/packages/3d/09/f3b25d35246df2f2c69cc3fce244b77022d02a26f389419a02d214fdc635/uuid_utils-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b96ffa58744f62dd6dd9c5ed33f8e6232a90e710aeb46758f3776d904352f755", size = 444839, upload-time = "2026-05-11T12:08:25.646Z" }, + { url = "https://files.pythonhosted.org/packages/6d/8d/618c28414bf95c2e555b7ecd7b7fadcd139b191c64213ea8044624ede6b2/uuid_utils-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04dafe5b74f9b9c27587001f39a256e981619626ddda20d7701d6b0a6c3cad51", size = 323820, upload-time = "2026-05-11T12:08:37.357Z" }, + { url = "https://files.pythonhosted.org/packages/76/e4/9762df18f91e33afcc869058dba0ea4c013c64c08f3866160a827b4daa05/uuid_utils-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:257335769b12ebd8c1ae809f8d22e5a4b829bdb9c796ce4f5a5f55d8bb76db86", size = 348568, upload-time = "2026-05-11T12:08:01.19Z" }, + { url = "https://files.pythonhosted.org/packages/86/3e/c99202e8aba95b30aaed419d3508da4f9f5c0a19fa3d01c76fab6a8aed34/uuid_utils-0.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:deee61ce9447f63e6ec765484b40f77dadac9672fb5c49d5f5586d93df38ae85", size = 501135, upload-time = "2026-05-11T12:07:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/f5/bc/740663747449cc0df8dd0e5523dc0e34d566692902edc7a1665a3327ee6e/uuid_utils-0.15.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:616a3e8f178c69f58d54d015bbb1666c6401ce3d41cc0473e67dfa278b96c8e5", size = 606513, upload-time = "2026-05-11T12:08:30.886Z" }, + { url = "https://files.pythonhosted.org/packages/22/14/6e4b523a90014fab0b55b13ea792d5529abf70f0f8c97fd5b90a5200bbcf/uuid_utils-0.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:98bc52c15cf1baf602c965ecc2ed5d798cc8908084098ab6478b53a99b479fa8", size = 565139, upload-time = "2026-05-11T12:06:54.408Z" }, + { url = "https://files.pythonhosted.org/packages/27/d9/ee0c8ce35cc8b0425adc822feec41fdf477d15e3259fb721a711018bb7db/uuid_utils-0.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7ed64c69f815cf434384681d64ee5aa574160a8e2d2a9a63088d388cb8ae7", size = 529000, upload-time = "2026-05-11T12:07:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/24ecbbcef49c0b209aea0d8dfbc15855cf8c3d80829f5e9c0513b4c1e499/uuid_utils-0.15.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:50cc685517e6b99be99b127e7f1817fbb65000d8816537852e603a2e3b60ac88", size = 97671, upload-time = "2026-05-11T12:07:31.232Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4d/9ebcbe90c2be622a9aed56f7606ae1ddc4800ecbe8b1cc6b7fbca2cadead/uuid_utils-0.15.0-cp313-cp313-win32.whl", hash = "sha256:805c52f49bdb90a83727c80b97c98769ef68cc16f2a12ef6c41c4533633e8a95", size = 168345, upload-time = "2026-05-11T12:07:08.968Z" }, + { url = "https://files.pythonhosted.org/packages/82/1a/10ce5709825de275b0a4f5c44f1cd0e13474b5a5430ea64567bdbd8dcd5f/uuid_utils-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:e1e2f4a8ca70ff617916719eadb1f148cc6eb65a4b2b89f35422bf9d595461aa", size = 174290, upload-time = "2026-05-11T12:07:19.852Z" }, + { url = "https://files.pythonhosted.org/packages/85/b3/a120d672b7c84bcd45210a67a368333179c821dd4d76c73da69aaad5414a/uuid_utils-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5929aa92bf4ccb5456bd40646e3c45219cc8f1d751675af75f681674e7bd0029", size = 172579, upload-time = "2026-05-11T12:07:12.915Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9f/67a1a323db03b872c78cc36ddc3249f756d523ee409a6abdfb6c643c0a59/uuid_utils-0.15.0-cp313-cp313t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:395ea1e40d6cd22bf6cfd00a3b25764571df783741d7a501f8b7a2d578f1148d", size = 561609, upload-time = "2026-05-11T12:07:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a5/cc6ed878f6323209a7d497ad345e6eea4c9186af4904f9cd60e5bc9d72e6/uuid_utils-0.15.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:e25b270f98dcf395a434bec704cb503516a71519198634bc827ba87a584387f7", size = 288953, upload-time = "2026-05-11T12:07:17.658Z" }, + { url = "https://files.pythonhosted.org/packages/1f/28/ca25f2e88ff84f4beb3e5310a45508651de389af80c61f172170bde81e19/uuid_utils-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3d38354ce3943fd721109c508b27a54147531ae656e675155301dfe25e8367", size = 324198, upload-time = "2026-05-11T12:06:47.487Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9f/0c9e22ccc4cd3e7cccb6d92cf3ccab3c259d04ff4d34a4d22bc6a8f5f9da/uuid_utils-0.15.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92076407ddb8b752df055378671b8c8bd3c6ffdb3064982190765b1fa685e624", size = 331096, upload-time = "2026-05-11T12:07:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/29/fd/cf820e6af8d4a8bb71a1dd1ea89a895d4186c41ffcd519eddf0b8cd3a126/uuid_utils-0.15.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b7c12e9ac48372781e6d409877621d1505955d8b37a505dbadb864f7098e85", size = 444743, upload-time = "2026-05-11T12:06:36.172Z" }, + { url = "https://files.pythonhosted.org/packages/57/1f/c6d31b0cefaa79c42529dde10b8638b541032b2b61e3ca2d77acaa64857f/uuid_utils-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b001172dede7e0681c6e288ac7febf36efa3efcbe92a964ddcef4acdd9f7b", size = 325096, upload-time = "2026-05-11T12:06:37.601Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9a/8354234e8f6b7a128bb10457bfa00b641b4e79fcf48a03958584ab753fd3/uuid_utils-0.15.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6326c3aff73342b50a39af0301972b671f1da68e6f2d88aaf5b959489b0c0a1", size = 349441, upload-time = "2026-05-11T12:07:07.568Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/7abda94d184e0e05f2aced8720f004581502f7072d60642b227c5861980f/uuid_utils-0.15.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f51f8f74f65b1a8f0cecccd2ab8d04c28df82e813e83cd29248c6a0a9cb96b71", size = 500226, upload-time = "2026-05-11T12:08:11.084Z" }, + { url = "https://files.pythonhosted.org/packages/23/44/efbb84e88d2a3adfc883bcfa97e50259ac39f5ba8858e68438bbd8cb1993/uuid_utils-0.15.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:06a76000bd4917526549fedb63c417e1ea8e745388aedc9906d7af079f969668", size = 606411, upload-time = "2026-05-11T12:08:07.212Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b1/43c1121329467590e99a1aa3a81845d0c908ce7319e870cb68334c5803bd/uuid_utils-0.15.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:5d721605af5478415b311b9d2bd7f3cc71d19dc071c7b891dc92221a845150d1", size = 566029, upload-time = "2026-05-11T12:07:29.176Z" }, + { url = "https://files.pythonhosted.org/packages/80/fa/1f105833249b8259e3afec9ef7874da7c8cd80c534a2eb59726aa6b6945f/uuid_utils-0.15.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fc9a85207269b436255b08f504e3ea185f6f1e4813ffa43c0e658a63af99e7e6", size = 529679, upload-time = "2026-05-11T12:06:57.549Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1d/841ffad2cf8b6050c66de2c9657549cd54b7cbe4e7a807a95dad863ce9bc/uuid_utils-0.15.0-cp313-cp313t-win32.whl", hash = "sha256:be62c176390690b9c28b2cfd5ae8fb1f1d469c76ff85348912904f000d6576fd", size = 167999, upload-time = "2026-05-11T12:06:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/9b/1eaa4016c5b2c614d07e4b58a201dfa89e3cf58d8905ba8e4c2b83e4ccba/uuid_utils-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:061a5d6f58e447ff41f13b07da83e0876cb4d9bcd5a83bf547db315abb886c0a", size = 174534, upload-time = "2026-05-11T12:07:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/7b/49/e18fb7681f0d09fc64d2210a5142b5836507e64999dd68971ad8dacd228c/uuid_utils-0.15.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1b48d6ca94783f5d3907717cea6a636e9451d3169d9398b287c81b18857c91b9", size = 561884, upload-time = "2026-05-11T12:06:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/03/08/dd93d490d06e125a45c322175bd161087e4fff2c9f3d2b7b9b91f8d2d349/uuid_utils-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8b44795c09928ba55b15d94c4a2d29e942983eaf77f1bfa008ae596b5f1c72dd", size = 288932, upload-time = "2026-05-11T12:07:23.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/12/df5c29e5acb1bc3122e7ecca15bef68de6287663c0a2a381822008d4cbf5/uuid_utils-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f76f5654441960425726e377e3ecfaa9e14cde3cc9b2e9f673bbb11daa38e1c3", size = 324611, upload-time = "2026-05-11T12:06:34.691Z" }, + { url = "https://files.pythonhosted.org/packages/62/b7/7c20949ebe7a4e19bf13805ab2f71e667e549e3149502f01e41f695190c6/uuid_utils-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d4f797414c036c7b7c862d6401da8bcbfd19086eabb41035c468e0ad564d339e", size = 331380, upload-time = "2026-05-11T12:07:16.641Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ed/7d32f0ffa31cc4023e5f2919acb9abb103330c3a338a27c85a2f877a4475/uuid_utils-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:670f174a447fa478605c48254f1b8f1fd309f1861be9fd469e5639230bc80ab7", size = 443350, upload-time = "2026-05-11T12:07:38.157Z" }, + { url = "https://files.pythonhosted.org/packages/1a/10/76b4da4086bd70924b562de487a2ef647a0fbee1ed7d5e8777664cc4a986/uuid_utils-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4835b0907466a535b255a27df6cf0d37ea4ab4b69edde53cc350563e8b55442", size = 323637, upload-time = "2026-05-11T12:08:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ab/3d31222f7536e1f2113ad0719cc76f4c78007ebcd752fc9170f1eebb448f/uuid_utils-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3070ba33b609299202e7e2ecfcfeb40451591874bcd4a6b268028d0f026bec49", size = 348390, upload-time = "2026-05-11T12:08:34.604Z" }, + { url = "https://files.pythonhosted.org/packages/b1/67/822fc66ac27ecd086f6bdb6eb1d8e0ddc47b353ed60945038e74c67bfc1d/uuid_utils-0.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:771f9db3cb3e5e3167beb7892ddcaf5d0440c5eff631f3b61476b607d7e59dab", size = 501144, upload-time = "2026-05-11T12:06:42.473Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f5/5d9758e655cbbe9a1d5b72e17f10fd42afc39b88d1cdd21d6e2532dbfbdf/uuid_utils-0.15.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c40cb6a68b95787a55d401394178213003dfce1e6e62d1097756a5fb70aae9da", size = 606407, upload-time = "2026-05-11T12:07:41.328Z" }, + { url = "https://files.pythonhosted.org/packages/8f/cc/16c91835db9cb6870b00529db64c3e0f23dc6e39002b86b80d958358e6b2/uuid_utils-0.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:031baf2ce4136e98f68845d040683b83a64aac4f52c01830e066bbbe2a9113fc", size = 564984, upload-time = "2026-05-11T12:07:00.738Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5a/84c356b33f13fbc6fccc065f4dd51095526bee3bb939e89a64bc959502a4/uuid_utils-0.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ba5bc9191c5636bf2bc33d81166c0b27a71ff1b19ab881a8c80bd70f86578a3d", size = 528947, upload-time = "2026-05-11T12:06:59.716Z" }, + { url = "https://files.pythonhosted.org/packages/40/63/88ee651f506298a08afc32c7a33adc27839fcdce331ae438a50617bcf70c/uuid_utils-0.15.0-cp314-cp314-win32.whl", hash = "sha256:5050efb42112cd2dc37f8eb4efa65188b722dc60ae6e28a52845b5d27f35a85d", size = 168620, upload-time = "2026-05-11T12:06:45.434Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e2/f37cb4a220aab39a627e83d6b9f76705862c5b0db62140f24d38847ab4a5/uuid_utils-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:743fe546f8910edfd6a650cc4eb9995eb0d9dcfee11d948f5b326702851cb246", size = 173867, upload-time = "2026-05-11T12:08:36.358Z" }, + { url = "https://files.pythonhosted.org/packages/ca/60/c1423514345690162c37c4cc33f6052b81bfa6886f5569ba92bee9fa3302/uuid_utils-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:ebacba63d31afbea72e5bf12205413a5f53a2654c9f6302abf8de7cc6697a4d8", size = 172153, upload-time = "2026-05-11T12:08:18.045Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6a/65d401e3ff1f9e79faac5bbc769cab06ca6c454fa492fb8f07fd5c7b2230/uuid_utils-0.15.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5c29e29e8d5e9302cd84e4e5fdac38409448893048f42bd73d5e9b64d6eda2e4", size = 562240, upload-time = "2026-05-11T12:06:52.088Z" }, + { url = "https://files.pythonhosted.org/packages/2d/67/974e71d000b99440717b2864eb53f42d4589edcb6267e46100ccdf1a22fc/uuid_utils-0.15.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a8ab927c4bec80e4b784c5c9af7ce1c74f22b80abc6db2895fe18268255a0060", size = 289149, upload-time = "2026-05-11T12:07:34.581Z" }, + { url = "https://files.pythonhosted.org/packages/51/d4/52a7d5f9f2a4e6f871309e68080921a90f03ccf46b64b9d7dac29ece2bdb/uuid_utils-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7169dd734319ea95e51894b61ad17e76b7edcf6927669ad3b963818e35e06086", size = 324661, upload-time = "2026-05-11T12:07:06.526Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/389c0c5d0d8a04999bbe2a677d3b4bf09d3f3e3298801f27fdd14894d58d/uuid_utils-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c78462302d81e1d7f7fb0ee14ff7c521e47a27c4d7222a4933c01a431d2a6efc", size = 331568, upload-time = "2026-05-11T12:07:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/20/08/1f1e10d0182afa865c623ed272ddbd7750781b81425f05f4e8cab6be5a78/uuid_utils-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270d7f11cfe821d68433103f63058d724c9165c2d1d443559f66cd67352748af", size = 444798, upload-time = "2026-05-11T12:07:39.282Z" }, + { url = "https://files.pythonhosted.org/packages/13/81/1cc1b3b266b7e601571bac85e565a420a0cd47682aaf224aa4a825860283/uuid_utils-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ea58b9332ce8c04b8eec2c655b8bbd34ae31c06a5baf53f9a9b2324fc7d55a1", size = 324919, upload-time = "2026-05-11T12:08:22.951Z" }, + { url = "https://files.pythonhosted.org/packages/14/59/8a8be072f42618cbfe736c382a75456134771a0eb56101668fbb658be883/uuid_utils-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a11e885489a12b8fcf71fcfe7e1ae078515574e9a102f0819f189a4d62db301e", size = 349480, upload-time = "2026-05-11T12:08:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/66a96cb1d74b402248ba4d24e2eba8ecb4618f88dcfe7d82f1a7c13da297/uuid_utils-0.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ad134557819143c37ebd0eecf058accba94664ff4d50ef8bf619a255bdafdcea", size = 500791, upload-time = "2026-05-11T12:07:36.9Z" }, + { url = "https://files.pythonhosted.org/packages/e6/12/09171a3e2f03e18f6b6c86b5a089fc984891293ac8cccb6727a8c6b1bbb2/uuid_utils-0.15.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:60d5d7ef85592cdd555b01be4bc32b30a15854c3de99c5613e2e47299762b044", size = 606626, upload-time = "2026-05-11T12:08:40.874Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/8057a4f38b7e93fe51264d7bda3cbb1c1d9c61654368aa71ffec0057c17f/uuid_utils-0.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c0960c0475033bab0dddb13919e627c062d83d17900f22206c59b2942fe03703", size = 566218, upload-time = "2026-05-11T12:08:04.616Z" }, + { url = "https://files.pythonhosted.org/packages/14/f8/65f1273a82fa84c529caaa737bfdd512bbc2c1028d35e342d0aba88a89b2/uuid_utils-0.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cb9cc99885b676d0f5ce8e0996b57ba2a53fe3a3f0163c7c9e06151e0232982f", size = 529658, upload-time = "2026-05-11T12:08:21.796Z" }, + { url = "https://files.pythonhosted.org/packages/27/8b/2eea5e55d8d2185527cc37e481a363b77ac893534bdda4b9e277cdd71aa1/uuid_utils-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:30e7340f8b55f552a78d90eab2b2be6f68520c380215ddb7fb70a6d234ce154d", size = 168093, upload-time = "2026-05-11T12:06:33.548Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e5/7524e94c316fc0194c3da1a91e51cce69722520e5fc499c4ece53007a967/uuid_utils-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef6edbb10a4956755614e116aee4b558d75284b52dbedcf5f7505c518eb1011", size = 174063, upload-time = "2026-05-11T12:08:03.414Z" }, + { url = "https://files.pythonhosted.org/packages/d8/64/8be140712e3fa9d8406f0cb61876ce6d02f72067d4f9d31d1bf73e127c01/uuid_utils-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b3f0e567b5e992b28a50f50e0aeba546a2e2d3e463590eb5543204cb5d0f40b3", size = 171358, upload-time = "2026-05-11T12:07:30.282Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "vcrpy" +version = "8.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/07/bcfd5ebd7cb308026ab78a353e091bd699593358be49197d39d004e5ad83/vcrpy-8.1.1.tar.gz", hash = "sha256:58e3053e33b423f3594031cb758c3f4d1df931307f1e67928e30cf352df7709f", size = 85770, upload-time = "2026-01-04T19:22:03.886Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/d7/f79b05a5d728f8786876a7d75dfb0c5cae27e428081b2d60152fb52f155f/vcrpy-8.1.1-py3-none-any.whl", hash = "sha256:2d16f31ad56493efb6165182dd99767207031b0da3f68b18f975545ede8ac4b9", size = 42445, upload-time = "2026-01-04T19:22:02.532Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d5/f039e7e3c639d9b1d09b07ea412a6806d38123f0508e5f9b48a87b0a76cc/watchfiles-1.1.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:8c89f9f2f740a6b7dcc753140dd5e1ab9215966f7a3530d0c0705c83b401bd7d", size = 404745, upload-time = "2025-10-14T15:04:46.731Z" }, + { url = "https://files.pythonhosted.org/packages/a5/96/a881a13aa1349827490dab2d363c8039527060cfcc2c92cc6d13d1b1049e/watchfiles-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd404be08018c37350f0d6e34676bd1e2889990117a2b90070b3007f172d0610", size = 391769, upload-time = "2025-10-14T15:04:48.003Z" }, + { url = "https://files.pythonhosted.org/packages/4b/5b/d3b460364aeb8da471c1989238ea0e56bec24b6042a68046adf3d9ddb01c/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8526e8f916bb5b9a0a777c8317c23ce65de259422bba5b31325a6fa6029d33af", size = 449374, upload-time = "2025-10-14T15:04:49.179Z" }, + { url = "https://files.pythonhosted.org/packages/b9/44/5769cb62d4ed055cb17417c0a109a92f007114a4e07f30812a73a4efdb11/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2edc3553362b1c38d9f06242416a5d8e9fe235c204a4072e988ce2e5bb1f69f6", size = 459485, upload-time = "2025-10-14T15:04:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/19/0c/286b6301ded2eccd4ffd0041a1b726afda999926cf720aab63adb68a1e36/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30f7da3fb3f2844259cba4720c3fc7138eb0f7b659c38f3bfa65084c7fc7abce", size = 488813, upload-time = "2025-10-14T15:04:51.059Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2b/8530ed41112dd4a22f4dcfdb5ccf6a1baad1ff6eed8dc5a5f09e7e8c41c7/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8979280bdafff686ba5e4d8f97840f929a87ed9cdf133cbbd42f7766774d2aa", size = 594816, upload-time = "2025-10-14T15:04:52.031Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/f5f9fb49489f184f18470d4f99f4e862a4b3e9ac2865688eb2099e3d837a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dcc5c24523771db3a294c77d94771abcfcb82a0e0ee8efd910c37c59ec1b31bb", size = 475186, upload-time = "2025-10-14T15:04:53.064Z" }, + { url = "https://files.pythonhosted.org/packages/cf/68/5707da262a119fb06fbe214d82dd1fe4a6f4af32d2d14de368d0349eb52a/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db5d7ae38ff20153d542460752ff397fcf5c96090c1230803713cf3147a6803", size = 456812, upload-time = "2025-10-14T15:04:55.174Z" }, + { url = "https://files.pythonhosted.org/packages/66/ab/3cbb8756323e8f9b6f9acb9ef4ec26d42b2109bce830cc1f3468df20511d/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:28475ddbde92df1874b6c5c8aaeb24ad5be47a11f87cde5a28ef3835932e3e94", size = 630196, upload-time = "2025-10-14T15:04:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/78/46/7152ec29b8335f80167928944a94955015a345440f524d2dfe63fc2f437b/watchfiles-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:36193ed342f5b9842edd3532729a2ad55c4160ffcfa3700e0d54be496b70dd43", size = 622657, upload-time = "2025-10-14T15:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/95895e78dd75efe9a7f31733607f384b42eb5feb54bd2eb6ed57cc2e94f4/watchfiles-1.1.1-cp312-cp312-win32.whl", hash = "sha256:859e43a1951717cc8de7f4c77674a6d389b106361585951d9e69572823f311d9", size = 272042, upload-time = "2025-10-14T15:04:59.046Z" }, + { url = "https://files.pythonhosted.org/packages/87/0a/90eb755f568de2688cb220171c4191df932232c20946966c27a59c400850/watchfiles-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:91d4c9a823a8c987cce8fa2690923b069966dabb196dd8d137ea2cede885fde9", size = 288410, upload-time = "2025-10-14T15:05:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/f322701530586922fbd6723c4f91ace21364924822a8772c549483abed13/watchfiles-1.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:a625815d4a2bdca61953dbba5a39d60164451ef34c88d751f6c368c3ea73d404", size = 278209, upload-time = "2025-10-14T15:05:01.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://files.pythonhosted.org/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://files.pythonhosted.org/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://files.pythonhosted.org/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://files.pythonhosted.org/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://files.pythonhosted.org/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://files.pythonhosted.org/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://files.pythonhosted.org/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://files.pythonhosted.org/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://files.pythonhosted.org/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://files.pythonhosted.org/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://files.pythonhosted.org/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://files.pythonhosted.org/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://files.pythonhosted.org/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://files.pythonhosted.org/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://files.pythonhosted.org/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://files.pythonhosted.org/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "wcmatch" +version = "10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bracex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "wsproto" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, +] + +[[package]] +name = "xxhash" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/2f/e183a1b407002f5af81822bee18b61cdb94b8670208ef34734d8d2b8ebe9/xxhash-3.7.0.tar.gz", hash = "sha256:6cc4eefbb542a5d6ffd6d70ea9c502957c925e800f998c5630ecc809d6702bae", size = 82022, upload-time = "2026-04-25T11:10:32.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/8a/51a14cdef4728c6c2337db8a7d8704422cc65676d9199d77215464c880af/xxhash-3.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:082c87bfdd2b9f457606c7a4a53457f4c4b48b0cdc48de0277f4349d79bb3d7a", size = 33357, upload-time = "2026-04-25T11:06:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/0c2c933809421ffd9bf42b59315552c143c755db5d9a816b2f1ae273e884/xxhash-3.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5e7ce913b61f35b0c1c839a49ac9c8e75dd8d860150688aed353b0ce1bf409d8", size = 30869, upload-time = "2026-04-25T11:06:21.989Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/89d5fdd6ee12d70ba99451de46dd0e8010167468dcd913ec855653f4dd50/xxhash-3.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3beb1de3b1e9694fcdd853e570ee64c631c7062435d2f8c69c1adf809bc086f0", size = 194100, upload-time = "2026-04-25T11:06:23.586Z" }, + { url = "https://files.pythonhosted.org/packages/87/ee/2f9f2ed993e77206d1e66991290a1ebe22e843351ca3ebec8e49e01ba186/xxhash-3.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3e7b689c3bce16699efcf736066f5c6cc4472c3840fe4b22bd8279daf4abdac", size = 212977, upload-time = "2026-04-25T11:06:25.019Z" }, + { url = "https://files.pythonhosted.org/packages/de/60/5a91644615a9e9d4e42c2e9925f1908e3a24e4e691d9de7340d565bea024/xxhash-3.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a6545e6b409e3d5cbafc850fb84c55a1ca26ed15a6b11e3bf07a0e0cd84517c8", size = 236373, upload-time = "2026-04-25T11:06:26.482Z" }, + { url = "https://files.pythonhosted.org/packages/22/c0/f3a9384eaaed9d14d4d062a5d953aa0da489bfe9747877aa994caa87cd0b/xxhash-3.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:31ab1461c77a11461d703c88eb949e132a1c6515933cf675d97ec680f4bd18de", size = 212229, upload-time = "2026-04-25T11:06:28.065Z" }, + { url = "https://files.pythonhosted.org/packages/2e/67/02f07a9fd79726804190f2172c4894c3ed9a4ebccaca05653c84beb58025/xxhash-3.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c4d596b7676f811172687ec567cbafb9e4dea2f9be1bbb4f622410cb7f40f40", size = 445462, upload-time = "2026-04-25T11:06:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/558f5a90c0672fc9b4402dc25d87ac5b7406616e8969430c9ca4e52ee74d/xxhash-3.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13805f0461cba0a857924e70ff91ae6d52d2598f79a884e788db80532614a4a1", size = 193932, upload-time = "2026-04-25T11:06:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/aaa09cd58661d32044dbbad7df55bbe22a623032b810e7ed3b8c569a2a6f/xxhash-3.7.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d398f372496152f1c6933a33566373f8d1b37b98b8c9d608fa6edc0976f23b2", size = 284807, upload-time = "2026-04-25T11:06:33.697Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f3/53df3719ab127a02c174f0c1c74924fcd110866e89c966bc7909cfa8fa84/xxhash-3.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d610aa62cdb7d4d497740741772a24a794903bf3e79eaa51d2e800082abe11e5", size = 210445, upload-time = "2026-04-25T11:06:35.488Z" }, + { url = "https://files.pythonhosted.org/packages/72/33/d219975c0e8b6fa2eb9ccd486fe47e21bf1847985b878dd2fbc3126e0d5c/xxhash-3.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:073c23900a9fbf3d26616c17c830db28af9803677cd5b33aea3224d824111514", size = 241273, upload-time = "2026-04-25T11:06:37.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/50/49b1afe610eb3964cedcb90a4d4c3d46a261ee8669cbd4f060652619ae3c/xxhash-3.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:418a463c3e6a590c0cdc890f8be19adb44a8c8acd175ca5b2a6de77e61d0b386", size = 197950, upload-time = "2026-04-25T11:06:39.148Z" }, + { url = "https://files.pythonhosted.org/packages/c6/75/5f42a1a4c78717d906a4b6a140c6dbf837ab1f547a54d23c4e2903310936/xxhash-3.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:03f8ff4474ee61c845758ce00711d7087a770d77efb36f7e74a6e867301000b8", size = 210709, upload-time = "2026-04-25T11:06:40.958Z" }, + { url = "https://files.pythonhosted.org/packages/8a/85/237e446c25abced71e9c53d269f2cef5bab8a82b3f88a12e00c5368e7368/xxhash-3.7.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:44fba4a5f1d179b7ddc7b3dc40f56f9209046421679b57025d4d8821b376fd8d", size = 275345, upload-time = "2026-04-25T11:06:42.525Z" }, + { url = "https://files.pythonhosted.org/packages/62/34/c2c26c0a6a9cc739bc2a5f0ae03ba8b87deb12b8bce35f7ac495e790dc6d/xxhash-3.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31e3516a0f829d06ded4a2c0f3c7c5561993256bfa1c493975fb9dc7bfa828a1", size = 414056, upload-time = "2026-04-25T11:06:44.343Z" }, + { url = "https://files.pythonhosted.org/packages/a0/aa/5c58e9bc8071b8afd8dcf297ff362f723c4892168faba149f19904132bf4/xxhash-3.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b59ee2ac81de57771a09ecad09191e840a1d2fae1ef684208320591055768f83", size = 191485, upload-time = "2026-04-25T11:06:46.262Z" }, + { url = "https://files.pythonhosted.org/packages/d4/69/a929cf9d1e2e65a48b818cdce72cb6b69eab2e6877f21436d0a1942aff43/xxhash-3.7.0-cp312-cp312-win32.whl", hash = "sha256:74bbd92f8c7fcc397ba0a11bfdc106bc72ad7f11e3a60277753f87e7532b4d81", size = 30671, upload-time = "2026-04-25T11:06:48.039Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/104b41a8947f4e1d4a66ce1e628eea752f37d1890bfd7453559ca7a3d950/xxhash-3.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:7bd7bc82dd4f185f28f35193c2e968ef46131628e3cac62f639dadf321cba4d1", size = 31514, upload-time = "2026-04-25T11:06:49.279Z" }, + { url = "https://files.pythonhosted.org/packages/98/a0/1fd0ea1f1b886d9e7c73f0397571e22333a7d79e31da6d7127c2a4a71d75/xxhash-3.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:7d7148180ec99ba36585b42c8c5de25e9b40191613bc4be68909b4d25a77a852", size = 27761, upload-time = "2026-04-25T11:06:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/d5174b4c36d10f64d4ca7050563138c5a599efb01a765858ddefc9c1202a/xxhash-3.7.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:4b6d6b33f141158692bd4eafbb96edbc5aa0dabdb593a962db01a91983d4f8fa", size = 36813, upload-time = "2026-04-25T11:06:51.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/d0/abc6c9d347ba1f1e1e1d98125d0881a0452c7f9a76a9dd03a7b5d2197f23/xxhash-3.7.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:845d347df254d6c619f616afa921331bada8614b8d373d58725c663ba97c3605", size = 35121, upload-time = "2026-04-25T11:06:53.048Z" }, + { url = "https://files.pythonhosted.org/packages/bf/11/4cc834eb3d79f2f2b3a6ef7324195208bcdfbdcf7534d2b17267aa5f3a8f/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:fddbbb69a6fff4f421e7a0d1fa28f894b20112e9e3fab306af451e2dfd0e459b", size = 29624, upload-time = "2026-04-25T11:06:54.311Z" }, + { url = "https://files.pythonhosted.org/packages/23/83/e97d3e7b635fe73a1dfb1e91f805324dd6d930bb42041cbf18f183bc0b6d/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:54876a4e45101cec2bf8f31a973cda073a23e2e108538dad224ba07f85f22487", size = 30638, upload-time = "2026-04-25T11:06:55.864Z" }, + { url = "https://files.pythonhosted.org/packages/f4/40/d84951d80c35db1f4c40a29a64a8520eea5d56e764c603906b4fe763580f/xxhash-3.7.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:0c72fe9c7e3d6dfd7f1e21e224a877917fa09c465694ba4e06464b9511b65544", size = 33323, upload-time = "2026-04-25T11:06:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/c7dc6558d97e9ab023f663d69ab28b340ed9bf4d2d94f2c259cf896bb354/xxhash-3.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a6d73a830b17ef49bc04e00182bd839164c1b3c59c127cd7c54fcb10c7ed8ee8", size = 33362, upload-time = "2026-04-25T11:06:58.656Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6e/46b84017b1301d54091430353d4ad5901654a3e0871649877a416f7f1644/xxhash-3.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c3b07cf3362086d8f126c6aecd8e5e9396ad8b2f2219ea7e49a8250c318acd", size = 30874, upload-time = "2026-04-25T11:06:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/df/5e/8f9158e3ab906ad3fec51e09b5ea0093e769f12207bfa42a368ca204e7ab/xxhash-3.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50e879ebbac351c81565ca108db766d7832f5b8b6a5b14b8c0151f7190028e3d", size = 194185, upload-time = "2026-04-25T11:07:01.658Z" }, + { url = "https://files.pythonhosted.org/packages/f3/29/a804ded9f5d3d3758292678d23e7528b08fda7b7e750688d08b052322475/xxhash-3.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:921c14e93817842dd0dd9f372890a0f0c72e534650b6ab13c5be5cd0db11d47e", size = 213033, upload-time = "2026-04-25T11:07:03.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/91/1ce5a7d2fdc975267320e2c78fc1cecfe7ab735ccbcf6993ec5dd541cb2c/xxhash-3.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e64a7c9d7dfca3e0fafcbc5e455519090706a3e36e95d655cec3e04e79f95aaa", size = 236140, upload-time = "2026-04-25T11:07:05.396Z" }, + { url = "https://files.pythonhosted.org/packages/34/04/fd595a4fd8617b05fa27bd9b684ecb4985bfed27917848eea85d54036d06/xxhash-3.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2220af08163baf5fa36c2b8af079dc2cbe6e66ae061385267f9472362dfd53c6", size = 212291, upload-time = "2026-04-25T11:07:06.966Z" }, + { url = "https://files.pythonhosted.org/packages/03/fb/f1a379cbc372ae5b9f4ab36154c48a849ca6ebe3ac477067a57865bf3bc6/xxhash-3.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f14bb8b22a4a91325813e3d553b8963c10cf8c756cff65ee50c194431296c655", size = 445532, upload-time = "2026-04-25T11:07:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/65/59/172424b79f8cfd4b6d8a122b2193e6b8ad4b11f7159bb3b6f9b3191329bb/xxhash-3.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:496736f86a9bedaf64b0dc70e3539d0766df01c71ea22032698e88f3f04a1ce9", size = 193990, upload-time = "2026-04-25T11:07:10.315Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/aeac22161d953f139f07ba5586cb4a17c5b7b6dff985122803bb12933500/xxhash-3.7.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0ff71596bd79816975b3de7130ab1ff4541410285a3c084584eeb1c8239996fd", size = 284876, upload-time = "2026-04-25T11:07:12.15Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/4fd0b59e7a02242953da05ff679fbb961b0a4368eac97a217e11dae110c1/xxhash-3.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1ad86695c19b1d46fe106925db3c7a37f16be37669dcf58dcc70a9dd6e324676", size = 210495, upload-time = "2026-04-25T11:07:13.952Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fb/976a3165c728c7faf74aa1b5ab3cf6a85e6d731612894741840524c7d28c/xxhash-3.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:970f9f8c50961d639cbd0d988c96f80ddf66006de93641719282c4fe7a87c5e6", size = 241331, upload-time = "2026-04-25T11:07:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2c/6763d5901d53ac9e6ba296e5717ae599025c9d268396e8faa8b4b0a8e0ac/xxhash-3.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5886ad85e9e347911783760a1d16cb6b393e8f9e3b52c982568226cb56927bdc", size = 198037, upload-time = "2026-04-25T11:07:17.563Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/876e722d533833f5f9a83473e6ba993e48745701096944e77bbecf29b2c3/xxhash-3.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6e934bbae1e0ec74e27d5f0d7f37ef547ce5ff9f0a7e63fb39e559fc99526734", size = 210744, upload-time = "2026-04-25T11:07:19.055Z" }, + { url = "https://files.pythonhosted.org/packages/21/e6/d7e7baef7ce24166b4668d3c48557bb35a23b92ecadcac7e7718d099ab69/xxhash-3.7.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3b6b3d28228af044ebcded71c4a3dd86e1dbd7e2f4645bf40f7b5da65bb5fb5a", size = 275406, upload-time = "2026-04-25T11:07:20.908Z" }, + { url = "https://files.pythonhosted.org/packages/92/fe/198b3763b2e01ca908f2154969a2352ec99bda892b574a11a9a151c5ede4/xxhash-3.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:6be4d70d9ab76c9f324ead9c01af6ff52c324745ea0c3731682a0cf99720f1fe", size = 414125, upload-time = "2026-04-25T11:07:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6d/019a11affd5a5499137cacca53808659964785439855b5aa40dfd3412916/xxhash-3.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:151d7520838d4465461a0b7f4ae488b3b00de16183dd3214c1a6b14bf89d7fb6", size = 191555, upload-time = "2026-04-25T11:07:24.991Z" }, + { url = "https://files.pythonhosted.org/packages/76/21/b96d58568df2d01533244c3e0e5cbdd0c8b2b25c4bec4d72f19259a292d7/xxhash-3.7.0-cp313-cp313-win32.whl", hash = "sha256:d798c1e291bffb8e37b5bbe0dda77fc767cd19e89cadaf66e6ed5d0ff88c9fe6", size = 30668, upload-time = "2026-04-25T11:07:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/99/57/d849a8d3afa1f8f4bc6a831cd89f49f9706fbbad94d2975d6140a171988c/xxhash-3.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:875811ba23c543b1a1c3143c926e43996eb27ebb8f52d3500744aa608c275aed", size = 31524, upload-time = "2026-04-25T11:07:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/81/52/bacc753e92dee78b058af8dcef0a50815f5f860986c664a92d75f965b6a5/xxhash-3.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:54a675cb300dda83d71daae2a599389d22db8021a0f8db0dd659e14626eb3ecc", size = 27768, upload-time = "2026-04-25T11:07:29.113Z" }, + { url = "https://files.pythonhosted.org/packages/1c/47/ddbd683b7fc7e592c1a8d9d65f73ce9ab513f082b3967eee2baf549b8fc6/xxhash-3.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a3b19a42111c4057c1547a4a1396a53961dca576a0f6b82bfa88a2d1561764b2", size = 33576, upload-time = "2026-04-25T11:07:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/07/f2/36d3310161db7f72efb4562aadde0ed429f1d0531782dd6345b12d2da527/xxhash-3.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8f4608a06e4d61b7a3425665a46d00e0579122e1a2fae97a0c52953a3aad9aa3", size = 31123, upload-time = "2026-04-25T11:07:31.989Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3f/75937a5c69556ed213021e43cbedd84c8e0279d0d74e7d41a255d84ba4b1/xxhash-3.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad37c7792479e49cf96c1ab25517d7003fe0d93687a772ba19a097d235bbe41e", size = 196491, upload-time = "2026-04-25T11:07:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/f10d7ff8c7a733d4403a43b9de18c8fabc005f98cec054644f04418659ee/xxhash-3.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc026e3b89d98e30a8288c95cb696e77d150b3f0fb7a51f73dcd49ee6b5577fa", size = 215793, upload-time = "2026-04-25T11:07:34.919Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fd/778f60aa295f58907938f030a8b514611f391405614a525cccd2ffc00eb5/xxhash-3.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9b31ab1f28b078a6a1ac1a54eb35e7d5390deddd56870d0be3a0a733d1c321c", size = 237993, upload-time = "2026-04-25T11:07:36.638Z" }, + { url = "https://files.pythonhosted.org/packages/70/f5/736db5de387b4a540e37a05b84b40dc58a1ce974bfd2b4e5754ce29b68c3/xxhash-3.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3bb5fd680c038fd5229e44e9c493782f90df9bef632fd0499d442374688ff70b", size = 214887, upload-time = "2026-04-25T11:07:38.564Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/09a095f22fdb9a27fbb716841fbff52119721f9ca4261952d07a912f7839/xxhash-3.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:030c0fd688fce3569fbb49a2feefd4110cbb0b650186fb4610759ecfac677548", size = 448407, upload-time = "2026-04-25T11:07:40.552Z" }, + { url = "https://files.pythonhosted.org/packages/74/8a/b745efeeca9e34a91c26fdc97ad8514c43d5a81ac78565cba80a1353870a/xxhash-3.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b1bde10324f4c31812ae0d0502e92d916ae8917cad7209353f122b8b8f610c3", size = 196119, upload-time = "2026-04-25T11:07:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5c/0cfceb024af90c191f665c7933b1f318ee234f4797858383bebd1881d52f/xxhash-3.7.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:503722d52a615f2604f5e7611de7d43878df010dc0053094ef91cb9a9ac3d987", size = 286751, upload-time = "2026-04-25T11:07:43.568Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0a/0793e405dc3cf8f4ebe2c1acec1e4e4608cd9e7e50ea691dabbc2a95ccbb/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c72500a3b6d6c30ebfc135035bcace9eb5884f2dc220804efcaaba43e9f611dd", size = 212961, upload-time = "2026-04-25T11:07:45.388Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/721118ffc63bfff94aa565bcf2555a820f9f4bdb0f001e0d609bdfad70de/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:43475925a766d01ca8cd9a857fd87f3d50406983c8506a4c07c4df12adcc867f", size = 243703, upload-time = "2026-04-25T11:07:47.053Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/16f6267160488b8276fd3d449d425712512add292ba545c1b6946bfdb7dd/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8d09dfd2ab135b985daf868b594315ebe11ad86cd9fea46e6c69f19b28f7d25a", size = 200894, upload-time = "2026-04-25T11:07:48.657Z" }, + { url = "https://files.pythonhosted.org/packages/2d/94/80ba841287fd97e3e9cac1d228788c8ef623746f570404961eec748ecb5c/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c50269d0055ac1faecfd559886d2cbe4b730de236585aba0e873f9d9dadbe585", size = 213357, upload-time = "2026-04-25T11:07:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/106d4067130c59f1e18a55ffadcd876d8c68534883a1e02685b29d3d8153/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1910df4756a5ab58cfad8744fc2d0f23926e3efcc346ee76e87b974abab922f4", size = 277600, upload-time = "2026-04-25T11:07:51.745Z" }, + { url = "https://files.pythonhosted.org/packages/c5/86/a081dd30da71d720b2612a792bfd55e45fa9a07ac76a0507f60487473c25/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d006faf3b491957efcb433489be3c149efe4787b7063d5cddb8ddaefdc60e0c1", size = 416980, upload-time = "2026-04-25T11:07:53.504Z" }, + { url = "https://files.pythonhosted.org/packages/35/29/1a95221a029a3c1293773869e1ab47b07cbbdd82444a42809e8c60156626/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:abb65b4e947e958f7b3b0d71db3ce447d1bc5f37f5eab871ce7223bda8768a04", size = 193840, upload-time = "2026-04-25T11:07:55.103Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/db909dd0823285de2286f67e10ee4d81e96ad35d7d8e964ecb07fccd8af9/xxhash-3.7.0-cp313-cp313t-win32.whl", hash = "sha256:178959906cb1716a1ce08e0d69c82886c70a15a6f2790fc084fdd146ca30cd49", size = 30966, upload-time = "2026-04-25T11:07:56.524Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ff/d705b15b22f21ee106adce239cb65d35067a158c630b240270f09b17c2e6/xxhash-3.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2524a1e20d4c231d13b50f7cf39e44265b055669a64a7a4b9a2a44faa03f19b6", size = 31784, upload-time = "2026-04-25T11:07:57.758Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1f/b2cf83c3638fd0588e0b17f22e5a9400bdfb1a3e3755324ac0aee2250b88/xxhash-3.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:37d994d0ffe81ef087bb330d392caa809bb5853c77e22ea3f71db024a0543dba", size = 27932, upload-time = "2026-04-25T11:07:59.109Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cc/431db584f6fbb9312e40a173af027644e5580d39df1f73603cbb9dca4d6b/xxhash-3.7.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:8c5fcfd806c335bfa2adf1cd0b3110a44fc7b6995c3a648c27489bae85801465", size = 36644, upload-time = "2026-04-25T11:08:00.658Z" }, + { url = "https://files.pythonhosted.org/packages/bc/01/255ec513e0a705d1f9a61413e78dfce4e3235203f0ed525a24c2b4b56345/xxhash-3.7.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:506a0b488f190f0a06769575e30caf71615c898ed93ab18b0dbcb6dec5c3713c", size = 35003, upload-time = "2026-04-25T11:08:02.338Z" }, + { url = "https://files.pythonhosted.org/packages/68/70/c55fc33c93445b44d8fc5a17b41ed99e3cebe92bcf8396809e63fc9a1165/xxhash-3.7.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ec68dbba21532c0173a9872298e65c89749f7c9d21538c3a78b5bb6105871568", size = 29655, upload-time = "2026-04-25T11:08:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/c2/72/ff8de73df000d74467d12a59ce6d6e2b2a368b978d41ab7b1fba5ed442be/xxhash-3.7.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:fa77e7ec1450d415d20129961814787c9abd9a07f98872f070b1fe96c5084611", size = 30664, upload-time = "2026-04-25T11:08:05.011Z" }, + { url = "https://files.pythonhosted.org/packages/b6/91/08416d9bd9bc3bf39d831abe8a5631ac2db5141dfd6fe81c3fe59a1f9264/xxhash-3.7.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:fe32736295ea38e43e7d9424053c8c47c9f64fecfc7c895fb3da9b30b131c9ee", size = 33317, upload-time = "2026-04-25T11:08:06.413Z" }, + { url = "https://files.pythonhosted.org/packages/0e/3b/86b1caa4dee10a99f4bf9521e623359341c5e50d05158fa10c275b2bd079/xxhash-3.7.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ab9dd2c83c4bbd63e422181a76f13502d049d3ddcac9a1bdc29196263d692bb8", size = 33457, upload-time = "2026-04-25T11:08:08.099Z" }, + { url = "https://files.pythonhosted.org/packages/ed/38/98ea14ad1517e1461292a65906951458d520689782bfbae111050145bdba/xxhash-3.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3afec3a336a2286601a437cb07562ab0227685e6fbb9ec17e8c18457ff348ecf", size = 30894, upload-time = "2026-04-25T11:08:09.429Z" }, + { url = "https://files.pythonhosted.org/packages/61/a2/074654d0b893606541199993c7db70067d9fc63b748e0d60020a52a1bd36/xxhash-3.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:565df64437a9390f84465dcca33e7377114c7ede8d05cd2cf20081f831ea788e", size = 194409, upload-time = "2026-04-25T11:08:10.91Z" }, + { url = "https://files.pythonhosted.org/packages/e2/26/6d2a1afc468189f77ca28c32e1c83e1b9da1178231e05641dbc1b350e332/xxhash-3.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12eca820a5d558633d423bf8bb78ce72a55394823f64089247f788a7e0ae691e", size = 213135, upload-time = "2026-04-25T11:08:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0e/d8aecf95e09c42547453137be74d2f7b8b14e08f5177fa2fab6144a19061/xxhash-3.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f262b8f7599516567e070abf607b9af649052b2c4bd6f9be02b0cb41b7024805", size = 236379, upload-time = "2026-04-25T11:08:14.206Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/8140e8210536b3dd0cc816c4faaeb5ba6e63e8125ab25af4bcddd6a037b3/xxhash-3.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1598916cb197681e03e601901e4ab96a9a963de398c59d0964f8a6f44a2b361", size = 212447, upload-time = "2026-04-25T11:08:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/462001d2903b4bee5a5689598a0a55e5e7cd1ac7f4247a5545cff10d3ebb/xxhash-3.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:322b2f0622230f526aeb1738149948a7ae357a9e2ceb1383c6fd1fdaecdafa16", size = 445660, upload-time = "2026-04-25T11:08:17.441Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/2bd1ed7f8689b20e51727952cac8329d50c694dc32b2eba06ba5bc742b37/xxhash-3.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cc22070880cc57b830a65cde4e65fa884c6d9b28ae4803b5ee05911e7bafba", size = 194076, upload-time = "2026-04-25T11:08:19.134Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6e/692302cd0a5f4ac4e6289f37fa888dc2e1e07750b68fe3e4bfe939b8cea3/xxhash-3.7.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb5a888a968b2434abf9ecda357b5d43f10d7b5a6da6fdbbe036208473aff0e2", size = 284990, upload-time = "2026-04-25T11:08:20.618Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/e54b159b3d9df7999d2a7c676ce7b323d1b5588a64f8f51ed8172567bd87/xxhash-3.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a999771ff97bec27d18341be4f3a36b163bb1ac41ec17bef6d2dabd84acd33c7", size = 210590, upload-time = "2026-04-25T11:08:22.24Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/0e0df1a3a196ced4ca71de76d65ead25d8e87bbfb87b64306ea47a40c00d/xxhash-3.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ed4a6efe2dee1655adb73e7ad40c6aa955a6892422b1e3b95de6a34de56e3cbb", size = 241442, upload-time = "2026-04-25T11:08:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a9/d917a7a814e90b218f8a0d37967105eea91bf752c3303683c99a1f7bfc1f/xxhash-3.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fd17f14ac0faa12126c2f9ca774a8cf342957265ec3c8669c144e5e6cdb478c", size = 198356, upload-time = "2026-04-25T11:08:25.99Z" }, + { url = "https://files.pythonhosted.org/packages/89/5e/f2ba1877c39469abbefc72991d6ebdcbd4c0880db01ae8cb1f553b0c537d/xxhash-3.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:05fd1254268c59b5cb2a029dfc204275e9fc52de2913f1e53aa8d01442c96b4d", size = 210898, upload-time = "2026-04-25T11:08:27.608Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/be56b58e73de531f39a10de1355bb77ceb663900dc4bf2d6d3002a9c3f9e/xxhash-3.7.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a2eae53197c6276d5b317f75a1be226bbf440c20b58bf525f36b5d0e1f657ca6", size = 275519, upload-time = "2026-04-25T11:08:29.301Z" }, + { url = "https://files.pythonhosted.org/packages/92/e2/17ddc85d5765b9c709f192009ed8f5a1fc876f4eb35bba7c307b5b1169f9/xxhash-3.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bfe6f92e3522dcbe8c4281efd74fa7542a336cb00b0e3272c4ec0edabeaeaf67", size = 414191, upload-time = "2026-04-25T11:08:31.16Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/85f5b79f4bf1ec7ba052491164adfd4f4e9519f5dc7246de4fbd64a1bd56/xxhash-3.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7ab9a49c410d8c6c786ab99e79c529938d894c01433130353dd0fe999111077a", size = 191604, upload-time = "2026-04-25T11:08:32.862Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/6127b623aa4cca18d8b7743592b048d689fd6c6e37ff26a22cddf6cd9d7f/xxhash-3.7.0-cp314-cp314-win32.whl", hash = "sha256:040ea63668f9185b92bc74942df09c7e65703deed71431333678fc6e739a9955", size = 31271, upload-time = "2026-04-25T11:08:34.651Z" }, + { url = "https://files.pythonhosted.org/packages/64/4f/44fc4788568004c43921701cbc127f48218a1eede2c9aea231115323564d/xxhash-3.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2a61e2a3fb23c892496d587b470dee7fa1b58b248a187719c65ea8e94ec13257", size = 32284, upload-time = "2026-04-25T11:08:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/6d/77/18bb895eb60a49453d16e17d67990e5caff557c78eafc90ad4e2eabf4570/xxhash-3.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:c7741c7524961d8c0cb4d4c21b28957ff731a3fd5b5cd8b856dc80a40e9e5acc", size = 28701, upload-time = "2026-04-25T11:08:37.767Z" }, + { url = "https://files.pythonhosted.org/packages/45/a0/46f72244570c550fbbb7db1ef554183dd5ebe9136385f30e032b781ae8f6/xxhash-3.7.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc84bf7aa7592f31ec63a3e7b11d624f468a3f19f5238cec7282a42e838ab1d7", size = 33646, upload-time = "2026-04-25T11:08:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3a/453846a7eceea11e75def361eed01ec6a0205b9822c19927ed364ccae7cc/xxhash-3.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9f1563fdc8abfc389748e6932c7e4e99c89a53e4ec37d4563c24fc06f5e5644b", size = 31125, upload-time = "2026-04-25T11:08:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3e/49434aba738885d512f9e486db1bdd19db28dfa40372b56da26ef7a4e738/xxhash-3.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d415f18becf6f153046ab6adc97da77e3643a0ee205dae61c4012604113a020", size = 196633, upload-time = "2026-04-25T11:08:41.943Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e9/006cb6127baeb9f8abe6d15e62faa01349f09b34e2bfd65175b2422d026b/xxhash-3.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb16aa13ed175bc9be5c2491ba031b85a9b51c4ed90e0b3d4ebe63cf3fb54f8e", size = 215899, upload-time = "2026-04-25T11:08:43.645Z" }, + { url = "https://files.pythonhosted.org/packages/27/e4/cc57d72e66df0ae29b914335f1c6dcf61e8f3746ddf0ae3c471aa4f15e00/xxhash-3.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f9fd595f1e5941b3d7863e4774e4b30caa6731fc34b9277da032295aa5656ee5", size = 238116, upload-time = "2026-04-25T11:08:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/af/78/3531d4a3fd8a0038cc6be1f265a69c1b3587f557a10b677dd736de2202c1/xxhash-3.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1295325c5a98d552333fa53dc2b026b0ef0ec9c8e73ca3a952990b4c7d65d459", size = 215012, upload-time = "2026-04-25T11:08:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f6/259fb1eaaec921f59b17203b0daee69829761226d3b980d5191d7723dd83/xxhash-3.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3573a651d146912da9daa9e29e5fbc45994420daaa9ef1e2fa5823e1dc485513", size = 448534, upload-time = "2026-04-25T11:08:49.149Z" }, + { url = "https://files.pythonhosted.org/packages/7b/16/a66d0eaf6a7e68532c07714361ddc904c663ec940f3b028c1ae4a21a7b9d/xxhash-3.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ec1e080a3d02d94ea9335bfab0e3374b877e25411422c18f51a943fa4b46381", size = 196217, upload-time = "2026-04-25T11:08:50.805Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ef/d2efc7fc51756dc52509109d1a25cefc859d74bc4b19a167b12dbd8c2786/xxhash-3.7.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84415265192072d8638a3afc3c1bc5995e310570cd9acb54dc46d3939e364fe0", size = 286906, upload-time = "2026-04-25T11:08:52.418Z" }, + { url = "https://files.pythonhosted.org/packages/fc/67/25decd1d4a4018582ec4db2a868a2b7e40640f4adb20dfeb19ac923aa825/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d4dea659b57443989ef32f4295104fd6912c73d0bf26d1d148bb88a9f159b02", size = 213057, upload-time = "2026-04-25T11:08:54.105Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5d/17651eb29d06786cdc40c60ae3d27d645aa5d61d2eca6237a7ba0b94789b/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:05ece0fe4d9c9c2728912d1981ae1566cfc83a011571b24732cbf76e1fb70dca", size = 243886, upload-time = "2026-04-25T11:08:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d4/174d9cf7502243d586e6a9ae842b1ae23026620995114f85f1380e588bc9/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fd880353cf1ffaf321bc18dd663e111976dbd0d3bbd8a66d58d2b470dfa7f396", size = 201015, upload-time = "2026-04-25T11:08:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/91/8c/2254e2d06c3ac5e6fe22eaf3da791b87ea823ae9f2c17b4af66755c5752d/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4e15cc9e2817f6481160f930c62842b3ff419e20e13072bcbab12230943092bc", size = 213457, upload-time = "2026-04-25T11:08:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/79/a2/e3daa762545921173e3360f3b4ff7fc63c2d27359f7230ec1a7a74e117f6/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:90b9d1a8bd37d768ffc92a1f651ec69afc532a96fa1ac2ea7abbed5d630b3237", size = 277738, upload-time = "2026-04-25T11:09:01.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4c/e186da2c46b87f5204640e008d42730bf3c1ee9f0efb71ae1ebcdfeac681/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:157c49475b34ecea8809e51123d9769a534e139d1247942f7a4bc67710bb2533", size = 417127, upload-time = "2026-04-25T11:09:03.592Z" }, + { url = "https://files.pythonhosted.org/packages/17/28/3798e15007a3712d0da3d3fe70f8e11916569858b5cc371053bc26270832/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5a6ddec83325685e729ca119d1f5c518ec39294212ecd770e60693cdc5f7eb79", size = 193962, upload-time = "2026-04-25T11:09:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/ad/95/a26baa93b5241fd7630998816a4ec47a5a0bad193b3f8fc8f3593e1a4a67/xxhash-3.7.0-cp314-cp314t-win32.whl", hash = "sha256:a04a6cab47e2166435aaf5b9e5ee41d1532cc8300efdef87f2a4d0acb7db19ed", size = 31643, upload-time = "2026-04-25T11:09:08.153Z" }, + { url = "https://files.pythonhosted.org/packages/44/36/5454f13c447e395f9b06a3e91274c59f503d31fad84e1836efe3bdb71f6a/xxhash-3.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8653dd7c2eda020545bb2c71c7f7039b53fe7434d0fc1a0a9deb79ab3f1a4fc1", size = 32522, upload-time = "2026-04-25T11:09:09.534Z" }, + { url = "https://files.pythonhosted.org/packages/74/35/698e7e3ff38e22992ea24870a511d8762474fb6783627a2910ff22a185c2/xxhash-3.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:468f0fc114faaa4b36699f8e328bbc3bb11dc418ba94ac52c26dd736d4b6c637", size = 28807, upload-time = "2026-04-25T11:09:11.234Z" }, +] + +[[package]] +name = "yarl" +version = "1.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4b/a0a6e5d0ee8a2f3a373ddef8a4097d74ac901ac363eea1440464ccbe0898/yarl-1.23.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c6994ac35c3e74fb0ae93323bf8b9c2a9088d55946109489667c510a7d010e", size = 123796, upload-time = "2026-03-01T22:05:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/8925d68af039b835ae876db5838e82e76ec87b9782ecc97e192b809c4831/yarl-1.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4a42e651629dafb64fd5b0286a3580613702b5809ad3f24934ea87595804f2c5", size = 86547, upload-time = "2026-03-01T22:05:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b", size = 85854, upload-time = "2026-03-01T22:05:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035", size = 98351, upload-time = "2026-03-01T22:05:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/86/fc/4118c5671ea948208bdb1492d8b76bdf1453d3e73df051f939f563e7dcc5/yarl-1.23.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9d9a4d06d3481eab79803beb4d9bd6f6a8e781ec078ac70d7ef2dcc29d1bea5", size = 92711, upload-time = "2026-03-01T22:05:48.316Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/1ed91d42bd9e73c13dc9e7eb0dd92298d75e7ac4dd7f046ad0c472e231cd/yarl-1.23.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f514f6474e04179d3d33175ed3f3e31434d3130d42ec153540d5b157deefd735", size = 106014, upload-time = "2026-03-01T22:05:50.028Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c9/74e44e056a23fbc33aca71779ef450ca648a5bc472bdad7a82339918f818/yarl-1.23.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fda207c815b253e34f7e1909840fd14299567b1c0eb4908f8c2ce01a41265401", size = 105557, upload-time = "2026-03-01T22:05:51.416Z" }, + { url = "https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4", size = 101559, upload-time = "2026-03-01T22:05:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/72/59/c5b8d94b14e3d3c2a9c20cb100119fd534ab5a14b93673ab4cc4a4141ea5/yarl-1.23.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d7504f2b476d21653e4d143f44a175f7f751cd41233525312696c76aa3dbb23f", size = 100502, upload-time = "2026-03-01T22:05:54.954Z" }, + { url = "https://files.pythonhosted.org/packages/77/4f/96976cb54cbfc5c9fd73ed4c51804f92f209481d1fb190981c0f8a07a1d7/yarl-1.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:578110dd426f0d209d1509244e6d4a3f1a3e9077655d98c5f22583d63252a08a", size = 98027, upload-time = "2026-03-01T22:05:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/63/6e/904c4f476471afdbad6b7e5b70362fb5810e35cd7466529a97322b6f5556/yarl-1.23.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:609d3614d78d74ebe35f54953c5bbd2ac647a7ddb9c30a5d877580f5e86b22f2", size = 95369, upload-time = "2026-03-01T22:05:58.141Z" }, + { url = "https://files.pythonhosted.org/packages/9d/40/acfcdb3b5f9d68ef499e39e04d25e141fe90661f9d54114556cf83be8353/yarl-1.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4966242ec68afc74c122f8459abd597afd7d8a60dc93d695c1334c5fd25f762f", size = 105565, upload-time = "2026-03-01T22:06:00.286Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c6/31e28f3a6ba2869c43d124f37ea5260cac9c9281df803c354b31f4dd1f3c/yarl-1.23.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e0fd068364a6759bc794459f0a735ab151d11304346332489c7972bacbe9e72b", size = 99813, upload-time = "2026-03-01T22:06:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/08/1f/6f65f59e72d54aa467119b63fc0b0b1762eff0232db1f4720cd89e2f4a17/yarl-1.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:39004f0ad156da43e86aa71f44e033de68a44e5a31fc53507b36dd253970054a", size = 105632, upload-time = "2026-03-01T22:06:03.188Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c4/18b178a69935f9e7a338127d5b77d868fdc0f0e49becd286d51b3a18c61d/yarl-1.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5723c01a56c5028c807c701aa66722916d2747ad737a046853f6c46f4875543", size = 101895, upload-time = "2026-03-01T22:06:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/8f/54/f5b870b5505663911dba950a8e4776a0dbd51c9c54c0ae88e823e4b874a0/yarl-1.23.0-cp313-cp313-win32.whl", hash = "sha256:1b6b572edd95b4fa8df75de10b04bc81acc87c1c7d16bcdd2035b09d30acc957", size = 82356, upload-time = "2026-03-01T22:06:06.04Z" }, + { url = "https://files.pythonhosted.org/packages/7a/84/266e8da36879c6edcd37b02b547e2d9ecdfea776be49598e75696e3316e1/yarl-1.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:baaf55442359053c7d62f6f8413a62adba3205119bcb6f49594894d8be47e5e3", size = 87515, upload-time = "2026-03-01T22:06:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/00/fd/7e1c66efad35e1649114fa13f17485f62881ad58edeeb7f49f8c5e748bf9/yarl-1.23.0-cp313-cp313-win_arm64.whl", hash = "sha256:fb4948814a2a98e3912505f09c9e7493b1506226afb1f881825368d6fb776ee3", size = 81785, upload-time = "2026-03-01T22:06:10.181Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fc/119dd07004f17ea43bb91e3ece6587759edd7519d6b086d16bfbd3319982/yarl-1.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aecfed0b41aa72b7881712c65cf764e39ce2ec352324f5e0837c7048d9e6daaa", size = 130719, upload-time = "2026-03-01T22:06:11.708Z" }, + { url = "https://files.pythonhosted.org/packages/e6/0d/9f2348502fbb3af409e8f47730282cd6bc80dec6630c1e06374d882d6eb2/yarl-1.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a41bcf68efd19073376eb8cf948b8d9be0af26256403e512bb18f3966f1f9120", size = 89690, upload-time = "2026-03-01T22:06:13.429Z" }, + { url = "https://files.pythonhosted.org/packages/50/93/e88f3c80971b42cfc83f50a51b9d165a1dbf154b97005f2994a79f212a07/yarl-1.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cde9a2ecd91668bcb7f077c4966d8ceddb60af01b52e6e3e2680e4cf00ad1a59", size = 89851, upload-time = "2026-03-01T22:06:15.53Z" }, + { url = "https://files.pythonhosted.org/packages/1c/07/61c9dd8ba8f86473263b4036f70fb594c09e99c0d9737a799dfd8bc85651/yarl-1.23.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5023346c4ee7992febc0068e7593de5fa2bf611848c08404b35ebbb76b1b0512", size = 95874, upload-time = "2026-03-01T22:06:17.553Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/f9ff8ceefba599eac6abddcfb0b3bee9b9e636e96dbf54342a8577252379/yarl-1.23.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1009abedb49ae95b136a8904a3f71b342f849ffeced2d3747bf29caeda218c4", size = 88710, upload-time = "2026-03-01T22:06:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/0231bfcc5d4c8eec220bc2f9ef82cb4566192ea867a7c5b4148f44f6cbcd/yarl-1.23.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a8d00f29b42f534cc8aa3931cfe773b13b23e561e10d2b26f27a8d309b0e82a1", size = 101033, upload-time = "2026-03-01T22:06:21.203Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/30ea5239a61786f18fd25797151a17fbb3be176977187a48d541b5447dd4/yarl-1.23.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:95451e6ce06c3e104556d73b559f5da6c34a069b6b62946d3ad66afcd51642ea", size = 100817, upload-time = "2026-03-01T22:06:22.738Z" }, + { url = "https://files.pythonhosted.org/packages/62/e2/a4980481071791bc83bce2b7a1a1f7adcabfa366007518b4b845e92eeee3/yarl-1.23.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531ef597132086b6cf96faa7c6c1dcd0361dd5f1694e5cc30375907b9b7d3ea9", size = 97482, upload-time = "2026-03-01T22:06:24.21Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1e/304a00cf5f6100414c4b5a01fc7ff9ee724b62158a08df2f8170dfc72a2d/yarl-1.23.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88f9fb0116fbfcefcab70f85cf4b74a2b6ce5d199c41345296f49d974ddb4123", size = 95949, upload-time = "2026-03-01T22:06:25.697Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/093f4055ed4cae649ac53bca3d180bd37102e9e11d048588e9ab0c0108d0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e7b0460976dc75cb87ad9cc1f9899a4b97751e7d4e77ab840fc9b6d377b8fd24", size = 95839, upload-time = "2026-03-01T22:06:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/b9/28/4c75ebb108f322aa8f917ae10a8ffa4f07cae10a8a627b64e578617df6a0/yarl-1.23.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:115136c4a426f9da976187d238e84139ff6b51a20839aa6e3720cd1026d768de", size = 90696, upload-time = "2026-03-01T22:06:29.048Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/42c2e2dd91c1a570402f51bdf066bfdb1241c2240ba001967bad778e77b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ead11956716a940c1abc816b7df3fa2b84d06eaed8832ca32f5c5e058c65506b", size = 100865, upload-time = "2026-03-01T22:06:30.525Z" }, + { url = "https://files.pythonhosted.org/packages/74/05/1bcd60a8a0a914d462c305137246b6f9d167628d73568505fce3f1cb2e65/yarl-1.23.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:fe8f8f5e70e6dbdfca9882cd9deaac058729bcf323cf7a58660901e55c9c94f6", size = 96234, upload-time = "2026-03-01T22:06:32.692Z" }, + { url = "https://files.pythonhosted.org/packages/90/b2/f52381aac396d6778ce516b7bc149c79e65bfc068b5de2857ab69eeea3b7/yarl-1.23.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a0e317df055958a0c1e79e5d2aa5a5eaa4a6d05a20d4b0c9c3f48918139c9fc6", size = 100295, upload-time = "2026-03-01T22:06:34.268Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/638bae5bbf1113a659b2435d8895474598afe38b4a837103764f603aba56/yarl-1.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f0fd84de0c957b2d280143522c4f91a73aada1923caee763e24a2b3fda9f8a5", size = 97784, upload-time = "2026-03-01T22:06:35.864Z" }, + { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +] diff --git a/ty.toml b/ty.toml new file mode 100644 index 0000000..efddb86 --- /dev/null +++ b/ty.toml @@ -0,0 +1,15 @@ +[environment] +root = ["./services/orchestrator/src"] + +[src] +include = ["services/orchestrator/src", "services/orchestrator/tests"] + +[rules] +unused-type-ignore-comment = "ignore" + +[[overrides]] +include = ["services/orchestrator/tests"] + +[overrides.rules] +missing-typed-dict-key = "ignore" +invalid-key = "ignore"