From 8cbc8fdb689d6c644ae8ef0326975b784056862d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 06:29:49 +0000 Subject: [PATCH] Module mod - Update dependency dagger/dagger to v0.18.12 --- content/en/docs/04/solution/ci/sdk/LICENSE | 191 + content/en/docs/04/solution/ci/sdk/README.md | 137 + .../04/solution/ci/sdk/codegen/pyproject.toml | 12 + .../ci/sdk/codegen/src/codegen/__init__.py | 0 .../ci/sdk/codegen/src/codegen/__main__.py | 5 + .../ci/sdk/codegen/src/codegen/ast.py | 65 + .../ci/sdk/codegen/src/codegen/cli.py | 56 + .../ci/sdk/codegen/src/codegen/generator.py | 838 ++ .../en/docs/04/solution/ci/sdk/pyproject.toml | 100 + .../04/solution/ci/sdk/runtime/.gitattributes | 6 + .../04/solution/ci/sdk/runtime/.gitignore | 5 + .../04/solution/ci/sdk/runtime/Dockerfile | 3 + .../04/solution/ci/sdk/runtime/dagger.json | 7 + .../04/solution/ci/sdk/runtime/discovery.go | 486 + .../04/solution/ci/sdk/runtime/extension.go | 122 + .../en/docs/04/solution/ci/sdk/runtime/go.mod | 64 + .../en/docs/04/solution/ci/sdk/runtime/go.sum | 117 + .../docs/04/solution/ci/sdk/runtime/image.go | 131 + .../docs/04/solution/ci/sdk/runtime/main.go | 497 + .../docs/04/solution/ci/sdk/runtime/python.go | 72 + .../ci/sdk/runtime/template/__init__.py | 16 + .../solution/ci/sdk/runtime/template/main.py | 22 + .../ci/sdk/runtime/template/pyproject.toml | 9 + .../ci/sdk/runtime/template/runtime.py | 8 + .../04/solution/ci/sdk/src/dagger/__init__.py | 32 + .../solution/ci/sdk/src/dagger/_exceptions.py | 177 + .../solution/ci/sdk/src/dagger/_managers.py | 44 + .../ci/sdk/src/dagger/client/__init__.py | 0 .../ci/sdk/src/dagger/client/_config.py | 40 + .../ci/sdk/src/dagger/client/_connection.py | 5 + .../ci/sdk/src/dagger/client/_core.py | 312 + .../ci/sdk/src/dagger/client/_guards.py | 45 + .../ci/sdk/src/dagger/client/_session.py | 252 + .../solution/ci/sdk/src/dagger/client/base.py | 103 + .../solution/ci/sdk/src/dagger/client/gen.py | 9368 +++++++++++++++++ .../docs/04/solution/ci/sdk/src/dagger/log.py | 36 + .../ci/sdk/src/dagger/mod/__init__.py | 35 + .../ci/sdk/src/dagger/mod/_arguments.py | 153 + .../ci/sdk/src/dagger/mod/_converter.py | 225 + .../ci/sdk/src/dagger/mod/_exceptions.py | 88 + .../solution/ci/sdk/src/dagger/mod/_module.py | 712 ++ .../ci/sdk/src/dagger/mod/_resolver.py | 246 + .../solution/ci/sdk/src/dagger/mod/_types.py | 51 + .../solution/ci/sdk/src/dagger/mod/_utils.py | 316 + .../04/solution/ci/sdk/src/dagger/mod/cli.py | 95 + .../04/solution/ci/sdk/src/dagger/py.typed | 0 .../solution/ci/sdk/src/dagger/telemetry.py | 213 + content/en/docs/04/solution/ci/sdk/uv.lock | 1498 +++ mod/.gitignore | 1 + mod/dagger.json | 2 +- mod/go.mod | 51 +- mod/go.sum | 92 +- 52 files changed, 17091 insertions(+), 70 deletions(-) create mode 100644 content/en/docs/04/solution/ci/sdk/LICENSE create mode 100644 content/en/docs/04/solution/ci/sdk/README.md create mode 100644 content/en/docs/04/solution/ci/sdk/codegen/pyproject.toml create mode 100644 content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__init__.py create mode 100644 content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__main__.py create mode 100644 content/en/docs/04/solution/ci/sdk/codegen/src/codegen/ast.py create mode 100644 content/en/docs/04/solution/ci/sdk/codegen/src/codegen/cli.py create mode 100644 content/en/docs/04/solution/ci/sdk/codegen/src/codegen/generator.py create mode 100644 content/en/docs/04/solution/ci/sdk/pyproject.toml create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/.gitattributes create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/.gitignore create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/Dockerfile create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/dagger.json create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/discovery.go create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/extension.go create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/go.mod create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/go.sum create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/image.go create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/main.go create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/python.go create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/template/__init__.py create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/template/main.py create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/template/pyproject.toml create mode 100644 content/en/docs/04/solution/ci/sdk/runtime/template/runtime.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/__init__.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/_exceptions.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/_managers.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/__init__.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/_config.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/_connection.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/_core.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/_guards.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/_session.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/base.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/client/gen.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/log.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/__init__.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_arguments.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_converter.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_exceptions.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_module.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_resolver.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_types.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/_utils.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/mod/cli.py create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/py.typed create mode 100644 content/en/docs/04/solution/ci/sdk/src/dagger/telemetry.py create mode 100644 content/en/docs/04/solution/ci/sdk/uv.lock diff --git a/content/en/docs/04/solution/ci/sdk/LICENSE b/content/en/docs/04/solution/ci/sdk/LICENSE new file mode 100644 index 0000000..17b2ba2 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2022 Dagger, Inc. + + Licensed 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. diff --git a/content/en/docs/04/solution/ci/sdk/README.md b/content/en/docs/04/solution/ci/sdk/README.md new file mode 100644 index 0000000..06b276c --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/README.md @@ -0,0 +1,137 @@ +# Dagger Python SDK + +[![PyPI Version](https://img.shields.io/pypi/v/dagger-io)](https://pypi.org/project/dagger-io/) +[![Conda Version](https://img.shields.io/conda/vn/conda-forge/dagger-io.svg)](https://anaconda.org/conda-forge/dagger-io) +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/dagger-io.svg)](https://pypi.org/project/dagger-io/) +[![License](https://img.shields.io/pypi/l/dagger-io.svg)](https://pypi.python.org/pypi/dagger-io) +[![Code style](https://img.shields.io/badge/code%20style-black-black.svg)](https://github.com/psf/black) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff) + +A client package for running [Dagger](https://dagger.io/) pipelines. + +## What is the Dagger Python SDK? + +The Dagger Python SDK contains everything you need to develop CI/CD pipelines in Python, and run them on any OCI-compatible container runtime. + +## Requirements + +- Python 3.10 or later +- [Docker](https://docs.docker.com/engine/install/), or another OCI-compatible container runtime + +A compatible version of the [Dagger CLI](https://docs.dagger.io/cli) is automatically downloaded and run by the SDK for you, although it’s possible to manage it manually. + +## Installation + +From [PyPI](https://pypi.org/project/dagger-io/), using `pip`: + +```shell +pip install dagger-io +``` + +You can also install via [Conda](https://anaconda.org/conda-forge/dagger-io), from the [conda-forge](https://conda-forge.org/docs/user/introduction.html#how-can-i-install-packages-from-conda-forge) channel: + +```shell +conda install dagger-io +``` + +## Example + +Create a `main.py` file: + +```python +import sys + +import anyio +import dagger +from dagger import dag + + +async def main(args: list[str]): + async with dagger.connection(): + # build container with cowsay entrypoint + ctr = ( + dag.container() + .from_("python:alpine") + .with_exec(["pip", "install", "cowsay"]) + ) + + # run cowsay with requested message + result = await ctr.with_exec(["cowsay", *args]).stdout() + + print(result) + + +anyio.run(main, sys.argv[1:]) +``` + +Run with: + +```console +$ python main.py "Simple is better than complex" + _____________________________ +| Simple is better than complex | + ============================= + \ + \ + ^__^ + (oo)\_______ + (__)\ )\/\ + ||----w | + || || +``` + +> **Note** +> It may take a while for it to finish, especially on first run with cold cache. + +If you need to debug, you can stream the logs from the engine with the `log_output` config: + +```python +config = dagger.Config(log_output=sys.stderr) +async with dagger.connection(config): + ... +``` + +## Learn more + +- [Documentation](https://docs.dagger.io/sdk/python) +- [API Reference](https://dagger-io.readthedocs.org) +- [Source code](https://github.com/dagger/dagger/tree/main/sdk/python) + +## Development + +The SDK is managed with a Dagger module in `./dev`. To see which tasks are +available run: + +```shell +dagger call -m dev +``` + +### Common tasks + +Run pytest in supported Python versions: + +```shell +dagger call -m dev test default +``` + +Check for linting violations: +```shell +dagger call -m dev lint +``` + +Re-format code following common styling conventions: +```shell +dagger call -m dev format export --path=. +``` + +Update pinned development dependencies: +```shell +uv lock -U +``` + +Build and preview the reference documentation: +```shell +dagger call -m dev docs preview up +``` + +Add `--help` to any command to check all the available options. diff --git a/content/en/docs/04/solution/ci/sdk/codegen/pyproject.toml b/content/en/docs/04/solution/ci/sdk/codegen/pyproject.toml new file mode 100644 index 0000000..98714fc --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/codegen/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "codegen" +version = "0.0.0" +description = "Codegen for the Python SDK" +requires-python = ">= 3.10" +dependencies = [ + "graphql-core>=3.2.3", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__init__.py b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__main__.py b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__main__.py new file mode 100644 index 0000000..8221d0e --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/__main__.py @@ -0,0 +1,5 @@ +import sys + +from codegen.cli import main + +sys.exit(main()) diff --git a/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/ast.py b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/ast.py new file mode 100644 index 0000000..264072b --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/ast.py @@ -0,0 +1,65 @@ +from typing import Any + +import graphql + + +def insert_stubs(introspection: Any, schema: graphql.GraphQLSchema): + """Insert ast node stubs into the parsed schema.""" + for tp in introspection["types"]: + tp_schema = schema.get_type(tp["name"]) + if isinstance(tp_schema, graphql.GraphQLObjectType): + fields = [] + for field in tp["fields"]: + if field["name"] not in tp_schema.fields: + continue + field_schema = tp_schema.fields[field["name"]] + field_schema.ast_node = graphql.FieldDefinitionNode( + name=graphql.NameNode(value=field["name"]), + description=field["description"], + directives=parse_directives(field["directives"]), + ) + fields.append(field_schema.ast_node) + + tp_schema.ast_node = graphql.ObjectTypeDefinitionNode( + fields=fields, + directives=parse_directives(tp["directives"]), + ) + + elif isinstance(tp_schema, graphql.GraphQLEnumType): + if values := tp.get("enumValues"): + value_defs = [] + for value in values: + schema_value = tp_schema.values[value["name"]] + schema_value.ast_node = graphql.EnumValueDefinitionNode( + name=graphql.NameNode(value=value["name"]), + description=value["description"], + directives=parse_directives(value["directives"]), + ) + value_defs.append(schema_value.ast_node) + + tp_schema.ast_node = graphql.EnumTypeDefinitionNode( + values=value_defs, + directives=parse_directives(tp["directives"]), + ) + + # TODO: add support for other graphql declarations + + +def parse_directives( + directives: list[dict[str, Any]], +) -> tuple[graphql.ConstDirectiveNode, ...]: + """Parse directives from our dagger non-standard graphql directive application.""" + result = [] + for directive in directives: + node = graphql.ConstDirectiveNode( + name=graphql.NameNode(value=directive["name"]), + arguments=tuple( + graphql.ConstArgumentNode( + name=graphql.NameNode(value=arg["name"]), + value=graphql.parse_const_value(arg["value"]), + ) + for arg in directive["args"] + ), + ) + result.append(node) + return tuple(result) diff --git a/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/cli.py b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/cli.py new file mode 100644 index 0000000..c8e9c77 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/cli.py @@ -0,0 +1,56 @@ +import argparse +import json +import pathlib +import sys + +import graphql + +from codegen import ast, generator + +parser = argparse.ArgumentParser( + prog="python -m codegen", description="Dagger Python SDK" +) + + +def main(): + subparsers = parser.add_subparsers( + title="additional commands", + required=True, + ) + gen_parser = subparsers.add_parser( + "generate", + help="generate a Python client for the API", + ) + gen_parser.add_argument( + "-i", + "--introspection", + type=pathlib.Path, + required=True, + help="path to a .json file holding the introspection result", + ) + gen_parser.add_argument( + "-o", + "--output", + type=pathlib.Path, + help=( + "path to save the generated python module " + "(defaults to printing it to stdout)" + ), + ) + args = parser.parse_args() + + # TODO: Add argument for module init. + codegen(args.introspection, args.output) + + +def codegen(introspection: pathlib.Path, output: pathlib.Path | None): + result = json.loads(introspection.read_text()) + schema = graphql.build_client_schema(result) + ast.insert_stubs(result["__schema"], schema) + code = generator.generate(schema) + + if output: + output.write_text(code) + sys.stdout.write(f"Client generated successfully to {output}\n") + else: + sys.stdout.write(f"{code}\n") diff --git a/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/generator.py b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/generator.py new file mode 100644 index 0000000..269dd1d --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/codegen/src/codegen/generator.py @@ -0,0 +1,838 @@ +import enum +import functools +import itertools +import logging +import re +import textwrap +from abc import ABC, abstractmethod +from collections.abc import Callable, Container, Iterable, Iterator +from dataclasses import dataclass, field +from datetime import date, datetime, time +from decimal import Decimal +from functools import partial +from itertools import chain, groupby +from keyword import iskeyword +from operator import itemgetter +from typing import ( + ClassVar, + Generic, + ParamSpec, + Protocol, + TypeAlias, + TypeGuard, + TypeVar, + cast, +) + +import graphql +from graphql import ( + GraphQLArgument, + GraphQLEnumType, + GraphQLField, + GraphQLFieldMap, + GraphQLInputField, + GraphQLInputFieldMap, + GraphQLInputObjectType, + GraphQLInputType, + GraphQLLeafType, + GraphQLList, + GraphQLNamedType, + GraphQLNonNull, + GraphQLObjectType, + GraphQLOutputType, + GraphQLScalarType, + GraphQLSchema, + GraphQLType, + GraphQLWrappingType, + Undefined, + get_named_type, + is_leaf_type, +) +from graphql.pyutils import camel_to_snake +from graphql.type.schema import TypeMap + +ACRONYM_RE = re.compile(r"([A-Z\d]+)(?=[A-Z\d]|$)") +"""Pattern for grouping initialisms.""" + +DEPRECATION_RE = re.compile(r"`([a-zA-Z\d_]+)`") +"""Pattern for extracting replaced references in deprecations.""" + +logger = logging.getLogger(__name__) + +indent = partial(textwrap.indent, prefix=" " * 4) +wrap = textwrap.wrap +wrap_indent = partial(wrap, initial_indent=" " * 4, subsequent_indent=" " * 4) + + +T_ParamSpec = ParamSpec("T_ParamSpec") + +# These alias types are used to make the code more self-documenting. +IDName: TypeAlias = str +TypeName: TypeAlias = str +FieldName: TypeAlias = str +PythonName: TypeAlias = str +OutputTypeFormat: TypeAlias = str + +IDSet: TypeAlias = frozenset[IDName] + + +def joiner(func: Callable[T_ParamSpec, Iterable[str]]) -> Callable[T_ParamSpec, str]: + """Join elements with a new line from an iterator.""" + + @functools.wraps(func) + def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> str: + return "\n".join(func(*args, **kwargs)) + + return wrapper + + +class Scalars(enum.Enum): + ID = str + Int = int + String = str # noqa: PIE796 + Float = float + Boolean = bool + Date = date + DateTime = datetime + Time = time + Decimal = Decimal + + @classmethod + def from_type(cls, t: GraphQLScalarType) -> str: + try: + return cls[t.name].value.__name__ + except KeyError: + return t.name + + +@dataclass +class Context: + """Shared state during execution.""" + + schema: GraphQLSchema = field(default_factory=GraphQLSchema) + """GraphQL schema.""" + + ids: frozenset[IDName] = field(default_factory=frozenset) + """Set of ID scalar names.""" + + defined: set[str] = field(default_factory=set) + """Types that have already been defined.""" + + remaining: set[str] = field(default_factory=set) + """Remaining type names that haven't been defined yet.""" + + def process_type(self, name: str): + # This is only needed to keep track of remaining types because + # of forward references. + self.remaining.remove(name) + self.defined.add(name) + + def render_types(self, s: str) -> str: + """Render type names as forward references if they haven't been defined yet.""" + if not self.remaining: + return s + + # Add quotes to names that haven't been defined yet (forward references). + # Need to fix optionals because `"File" | None` is not a valid annotation. + # The whole annotation needs to be quoted (`"File | None"`). + s = re.sub(rf"\b({'|'.join(self.remaining)})\b", r'"\1"', s).replace( + '" | None', + ' | None"', + ) + return re.sub( + rf'list\["({"|".join(self.remaining)})"\] \| None', r'"list[\1] | None"', s + ) + + +_H = TypeVar("_H", bound=GraphQLNamedType) +"""Handler generic type""" + + +Predicate: TypeAlias = Callable[..., bool] + + +@dataclass +class Handler(ABC, Generic[_H]): + ctx: Context + """Generation execution context.""" + + predicate: ClassVar[Predicate] = staticmethod(lambda _: False) + """Does this handler render the given type?""" + + def supertype_name(self, t: _H) -> str: + return self.__class__.__name__ + + def type_name(self, t: _H) -> str: + return t.name + + @joiner + def render(self, t: _H) -> Iterator[str]: + yield "" + yield self.render_head(t) + yield indent(self.render_body(t)) + yield "" + + def render_head(self, t: _H) -> str: + return f"class {self.type_name(t)}({self.supertype_name(t)}):" + + @joiner + def render_body(self, t: _H) -> Iterator[str]: + if t.description: + yield from wrap(doc(t.description)) + + +@joiner +def generate(schema: GraphQLSchema) -> Iterator[str]: + """Code generation main function.""" + yield textwrap.dedent( + """\ + # Code generated by dagger. DO NOT EDIT. + + import warnings # noqa: F401 + from collections.abc import Callable + from dataclasses import dataclass + + from typing_extensions import Self + + from dagger.client._core import Arg + from dagger.client._guards import typecheck + from dagger.client.base import Enum, Input, Root, Scalar, Type + """, + ) + + # Pre-create handy maps to make handler code simpler. + ids = frozenset(n for n, t in schema.type_map.items() if is_id_type(t)) + + # shared state between all handler instances + ctx = Context(ids=ids, schema=schema) + + handlers: tuple[Handler, ...] = ( + Scalar(ctx), + Enum(ctx), + Input(ctx), + Object(ctx), + ) + + # Split into two iterators to update ctx.remaining. + types_n, types_g = itertools.tee(get_grouped_types(handlers, schema.type_map)) + + # Track types that haven't been defined yet, to format as a forward reference. + ctx.remaining.update(name for _, name, _ in types_n) + + for handler, type_name, named_type in types_g: + yield handler.render(named_type) + ctx.process_type(type_name) + + yield "" + yield "dag = Client()" + yield '"""The global client instance."""' + ctx.defined.add("dag") + + yield "" + yield "__all__ = [" + yield from (indent(f"{quote(name)},") for name in sorted(ctx.defined)) + yield "]" + + +def get_grouped_types(handlers: tuple[Handler, ...], type_map: TypeMap): + """Group types by handler and sorted by their name.""" + + def _filtered(): + for n, t in type_map.items(): + if n.startswith("_") or is_builtin_scalar_type(t): + continue + for i, handler in enumerate(handlers): + if handler.predicate(t): + yield i, n + + for _, items in groupby(sorted(_filtered()), itemgetter(0)): + for index, name in items: + named_type = type_map[name] + handler = handlers[index] + formatted_name = handler.type_name(named_type) + yield handler, formatted_name, named_type + + +# TODO: these typeguards should be contributed upstream +# https://github.com/graphql-python/graphql-core/issues/183 + + +def is_required_type(t: GraphQLType) -> TypeGuard[GraphQLNonNull]: + return isinstance(t, GraphQLNonNull) + + +def is_list_type(t: GraphQLType) -> TypeGuard[GraphQLList]: + if is_required_type(t): + t = t.of_type + return isinstance(t, GraphQLList) + + +def is_list_of_objects_type( + t: GraphQLType, +) -> TypeGuard[GraphQLList[GraphQLObjectType]]: + return is_list_type(t) and is_object_type(get_named_type(t)) + + +def is_wrapping_type(t: GraphQLType) -> TypeGuard[GraphQLWrappingType]: + return isinstance(t, GraphQLWrappingType) + + +def is_scalar_type(t: GraphQLType) -> TypeGuard[GraphQLScalarType]: + return isinstance(t, GraphQLScalarType) + + +def is_input_object_type(t: GraphQLType) -> TypeGuard[GraphQLInputObjectType]: + return isinstance(t, GraphQLInputObjectType) + + +def is_object_type(t: GraphQLType) -> TypeGuard[GraphQLObjectType]: + return isinstance(t, GraphQLObjectType) + + +def is_output_leaf_type(t: GraphQLOutputType) -> TypeGuard[GraphQLLeafType]: + return is_leaf_type(get_named_type(t)) + + +def is_custom_scalar_type(t: GraphQLType) -> TypeGuard[GraphQLScalarType]: + t = get_named_type(t) + return is_scalar_type(t) and t.name not in Scalars.__members__ + + +def is_builtin_scalar_type(t: GraphQLNamedType) -> TypeGuard[GraphQLScalarType]: + return is_scalar_type(t) and not is_custom_scalar_type(t) + + +def is_enum_type(t: GraphQLNamedType) -> TypeGuard[GraphQLEnumType]: + return isinstance(t, GraphQLEnumType) + + +def is_self_chainable(t: GraphQLObjectType) -> bool: + """Checks if an object type has any fields that return that same type.""" + return any( + f + for f in t.fields.values() + # Only consider fields that return a non-null object. + if is_required_type(f.type) + and is_object_type(f.type.of_type) + and f.type.of_type.name == t.name + ) + + +def is_id_type( + t: GraphQLType, + known_ids: Container[IDName] | None = None, +) -> TypeGuard[GraphQLScalarType]: + t = get_named_type(t) + if not is_scalar_type(t): + return False + return t.name in known_ids if known_ids else t.name.endswith("ID") + + +def type_from_id(t: GraphQLType) -> TypeName | None: + """Return the type name for the given id type name.""" + return t.name.removesuffix("ID") if is_id_type(t) else None + + +def id_from_type(t: GraphQLType) -> IDName | None: + """Return the id type name for the given type name.""" + return f"{t.name}ID" if is_id_type(t) else None + + +def id_query_field(t: GraphQLType) -> FieldName | None: + """Get the field name under Query that returns the given id type.""" + type_name = type_from_id(t) + return f"load{type_name}FromID" if type_name else None + + +# Don't shadow builtins that can be used as types in function signatures. +# +# For example, if a method is called "str" and the next one returns the "str" +# type, that method will actually return the method above, not the type. +_reserved_builtins = frozenset( + [ + "str", + "int", + "float", + "bool", + "list", + "type", + ] +) + + +def format_name(s: str) -> str: + """Format a GraphQL field or argument name into Python.""" + # rewrite acronyms, initialisms and abbreviations + s = ACRONYM_RE.sub(lambda m: m.group(0).title(), s) + s = camel_to_snake(s) + if iskeyword(s) or s in _reserved_builtins: + s += "_" + return s + + +def format_input_type(t: GraphQLInputType, convert_id=True) -> str: + """May be used in an input object field or an object field parameter.""" + if is_required_type(t): + t = t.of_type + fmt = "%s" + else: + fmt = "%s | None" + + if is_list_type(t): + return fmt % f"list[{format_input_type(t.of_type, convert_id)}]" + + if convert_id and is_id_type(t): + return fmt % type_from_id(t) + + return fmt % (Scalars.from_type(t) if is_scalar_type(t) else get_named_type(t).name) + + +def format_output_type(t: GraphQLOutputType) -> str: + """May be used as the output type of an object field.""" + # When returning objects we're in query building mode, so don't return + # None even if the field's return is optional. + if not is_output_leaf_type(t) and not is_required_type(t): + t = GraphQLNonNull(t) + return format_input_type(t, False) + + +def output_type_description(t: GraphQLOutputType) -> str: + if is_wrapping_type(t): + return output_type_description(t.of_type) + if isinstance(t, GraphQLNamedType) and t.description: + return t.description + return "" + + +def doc(s: str) -> str: + """Wrap string in docstring quotes.""" + if "\n" in s: + s = f"{s}\n" + elif s.endswith('"'): + s += " " + return f'"""{s}"""' + + +def quote(s: str) -> str: + """Wrap string in quotes.""" + return f'"{s}"' + + +class _InputField: + """Input object field or object field argument.""" + + def __init__( + self, + ctx: Context, + name: str, + graphql: GraphQLInputField | GraphQLArgument, + parent: "_ObjectField | None" = None, + ) -> None: + self.ctx = ctx + self.graphql_name = name + self.graphql = graphql + + self.name = format_name(name) + self.named_type = get_named_type(graphql.type) + self.parent_return_type: TypeName | None = ( + get_named_type(parent.graphql.type).name if parent else None + ) + self.parent_object_name: TypeName | None = ( + parent.parent_name if parent else None + ) + + # On object type fields, don't replace ID scalar with object + # only if field name is `id` and the corresponding type is different + # from the output type (e.g., `file(id: FileID) -> File`). + convert_id = not ( + name == "id" and self.parent_return_type == type_from_id(self.named_type) + ) + + self.type = format_input_type(graphql.type, convert_id) + self.is_self = self.type == self.parent_object_name + self.description = graphql.description + self.has_default = graphql.default_value is not Undefined + + default_value = graphql.default_value + self.default_is_mutable = isinstance(default_value, list) + if self.default_is_mutable: + default_value = () + + if not is_required_type(graphql.type) and not self.has_default: + default_value = None + self.has_default = True + + if default_value and is_enum_type(self.named_type): + self.default_value = f"{self.named_type.name}.{default_value}" + else: + # repr uses single quotes for strings, contrary to black + self.default_value = repr(default_value).replace("'", '"') + + @joiner + def __str__(self) -> Iterator[str]: + """Output for an InputObject field.""" + yield "" + yield self.ctx.render_types(self.as_param()) + + if self.description: + yield doc(self.description) + + def as_param(self) -> str: + """As a parameter in a function signature.""" + type_ = "Self" if self.is_self else self.type + out = f"{self.name}: {type_}" + if self.default_is_mutable: + if not out.endswith("| None"): + out = f"{out} | None" + out = f"{out} = None" + elif self.has_default: + out = f"{out} = {self.default_value}" + return out + + @joiner + def as_doc(self) -> Iterator[str]: + """As a part of a docstring.""" + yield f"{self.name}:" + if self.description: + for line in self.description.split("\n"): + yield from wrap_indent(line) + + def as_arg(self) -> str: + """As a Arg object for the query builder.""" + params = [quote(self.graphql_name), self.name] + if self.default_is_mutable: + params[1] = f"{self.default_value} if {self.name} is None else {self.name}" + if self.has_default: + params.append(self.default_value) + return f"Arg({', '.join(params)})," + + +class _ObjectField: + """Field of an object type.""" + + def __init__( + self, + ctx: Context, + name: str, + field: GraphQLField, + parent: GraphQLObjectType, + ) -> None: + self.ctx = ctx + self.graphql_name = name + self.graphql = field + + self.name = format_name(name) + self.named_type = get_named_type(field.type) + self.parent_name = get_named_type(parent).name + + self.required_args = [] + self.default_args = [] + for args in field.args.items(): + arg = _InputField(ctx, *args, parent=self) + (self.default_args if arg.has_default else self.required_args).append(arg) + self.args = self.required_args + self.default_args + self.description = field.description + + self.is_leaf = is_output_leaf_type(field.type) + self.is_list = is_list_of_objects_type(field.type) + self.is_exec = self.is_leaf or self.is_list + self.is_void = self.is_leaf and self.named_type.name == "Void" + self.type = format_output_type(field.type).replace("Query", "Client") + + # Any field in the API that returns an ID for its parent object should + # return the binding for the object instead in the SDK to allow continued + # chaining, except if it's called "id". + # + # For example, the API `Service { start: ServiceID }` should produce + # the following binding signature: + # >>> class Service: + # ... async def start(self) -> Self: ... + # + self.convert_id = False + if name != "id" and is_id_type(field.type) and self.is_leaf: + converted = type_from_id(self.named_type) + if self.parent_name == converted: + self.type = converted + self.convert_id = True + + self.is_sync = self.convert_id and self.name == "sync" + + @joiner + def __str__(self) -> Iterator[str]: + yield from ( + "", + self.func_signature(), + indent(self.func_body()), + ) + + # convenience to await any object that has a sync method + # without having to call it explicitly + if self.is_sync: + yield from ( + "", + "def __await__(self):", + indent("return self.sync().__await__()"), + ) + + def func_signature(self) -> str: + params = ", ".join( + chain( + ("self",), + (a.as_param() for a in self.required_args), + ("*",) if self.default_args else (), + (a.as_param() for a in self.default_args), + ) + ) + # arbitrary heuristic to force trailing comma in long signatures + if len(params) > 40: # noqa: PLR2004 + params = f"{params}," + + ret_type = "Self" if self.type == self.parent_name else self.type + sig = self.ctx.render_types(f"def {self.name}({params}) -> {ret_type}:") + if self.is_exec: + sig = f"async {sig}" + return sig + + @joiner + def func_body(self) -> Iterator[str]: + if docstring := self.func_doc(): + yield doc(docstring) + + if deprecated := self.deprecated(): + msg = f'Method "{self.name}" is deprecated: {deprecated}'.replace( + '"', '\\"' + ) + yield textwrap.dedent( + f"""\ + warnings.warn( + "{msg}", + DeprecationWarning, + stacklevel=4, + )\ + """ + ) + + if self.args: + yield "_args = [" + yield from (indent(arg.as_arg()) for arg in self.args) + yield "]" + else: + yield "_args: list[Arg] = []" + + if self.convert_id: + args = ("self", f'"{self.graphql_name}"', "_args") + yield f"return await self._ctx.execute_sync({', '.join(args)})" + return + + yield f'_ctx = self._select("{self.graphql_name}", _args)' + + if not self.is_exec: + yield f"return {self.type}(_ctx)" + elif self.is_list: + yield f"return await _ctx.execute_object_list({self.named_type.name})" + elif self.is_void: + yield "await _ctx.execute()" + else: + yield f"return await _ctx.execute({self.type})" + + def func_doc(self) -> str: + def _out(): + if self.description: + yield (textwrap.fill(line) for line in self.description.splitlines()) + + if deprecated := self.deprecated(":py:meth:`", "`"): + yield chain( + (".. deprecated::",), + wrap_indent(deprecated), + ) + if experimental := self.experimental(":py:meth:`", "`"): + yield chain( + (".. caution::",), + wrap_indent("Experimental: " + experimental), + ) + + if self.name == "id": + yield ( + "Note", + "----", + "This is lazily evaluated, no operation is actually run.", + ) + + if any(arg.description for arg in self.args): + yield chain( + ( + "Parameters", + "----------", + ), + (arg.as_doc() for arg in self.args), + ) + + if self.is_leaf: + return_doc = output_type_description(self.graphql.type) + if not self.convert_id and return_doc: + yield chain( + ( + "Returns", + "-------", + self.type, + ), + wrap_indent(return_doc), + ) + + yield chain( + ( + "Raises", + "------", + "ExecuteTimeoutError", + ), + wrap_indent( + "If the time to execute the query exceeds the " + "configured timeout." + ), + ( + "QueryError", + indent("If the API returns an error."), + ), + ) + + return "\n\n".join("\n".join(section) for section in _out()) + + def deprecated(self, prefix='"', suffix='"') -> str: + return self._rewrite_notice(self.graphql.deprecation_reason, prefix, suffix) + + def experimental(self, prefix='"', suffix='"') -> str: + reason = "" + if self.graphql.ast_node and ( + directive := self.ctx.schema.get_directive("experimental") + ): + args = graphql.get_directive_values(directive, self.graphql.ast_node) + if args: + reason = args["reason"] + return self._rewrite_notice(reason, prefix, suffix) + + def _rewrite_notice(self, reason, prefix='"', suffix='"') -> str: + def _format_name(m): + name = format_name(m.group().strip("`")) + return f"{prefix}{name}{suffix}" + + return DEPRECATION_RE.sub(_format_name, reason) if reason else "" + + +@dataclass +class Scalar(Handler[GraphQLScalarType]): + predicate: ClassVar[Predicate] = staticmethod(is_custom_scalar_type) + + def render_body(self, t: GraphQLScalarType) -> str: + return super().render_body(t) or "..." + + +@dataclass +class Enum(Handler[GraphQLEnumType]): + predicate: ClassVar[Predicate] = staticmethod(is_enum_type) + + @joiner + def render_body(self, t: GraphQLEnumType) -> Iterable[str]: + if body := super().render_body(t): + yield body + + for name, value in sorted(t.values.items()): + yield "" + + val = None + if value.ast_node and ( + directive := self.ctx.schema.get_directive("enumValue") + ): + args = graphql.get_directive_values(directive, value.ast_node) + if args: + val = args["value"] + if not val: + val = value.value + + # repr uses single quotes for strings, contrary to black + val = repr(val).replace("'", '"') + yield f"{name} = {val}" + + if value.description: + yield doc(value.description) + + +class Field(Protocol): + name: str + graphql_name: str + + def __str__(self) -> str: ... + + +_O = TypeVar("_O", GraphQLInputObjectType, GraphQLObjectType) +"""Object handler generic type""" + +_F: TypeAlias = _InputField | _ObjectField + + +class ObjectHandler(Handler[_O]): + @abstractmethod + def fields(self, t: _O) -> Iterator[_F]: ... + + @joiner + def render_body(self, t: _O) -> Iterator[str]: + if body := super().render_body(t): + yield body + + yield from ( + str(field) + # Sorting by graphql name rather than python name for + # consistency with other SDKs. + for field in sorted( + self.fields(t), + key=lambda f: (getattr(f, "has_default", False), f.graphql_name), + ) + ) + + +class Input(ObjectHandler[GraphQLInputObjectType]): + predicate: ClassVar[Predicate] = staticmethod(is_input_object_type) + + def render_head(self, t: GraphQLInputObjectType) -> str: + return f"@typecheck\n@dataclass(slots=True)\n{super().render_head(t)}" + + def fields(self, t: GraphQLInputObjectType) -> Iterator[_InputField]: + return ( + _InputField(self.ctx, *args) + for args in cast(GraphQLInputFieldMap, t.fields).items() + ) + + +class Object(ObjectHandler[GraphQLObjectType]): + predicate: ClassVar[Predicate] = staticmethod(is_object_type) + + def supertype_name(self, t: GraphQLObjectType) -> str: + return "Root" if t.name == "Query" else "Type" + + def type_name(self, t: GraphQLObjectType) -> str: + return super().type_name(t).replace("Query", "Client") + + def fields(self, t: GraphQLObjectType) -> Iterator[_ObjectField]: + return ( + _ObjectField(self.ctx, *args, t) + for args in cast(GraphQLFieldMap, t.fields).items() + ) + + def render_head(self, t: GraphQLObjectType) -> str: + return f"@typecheck\n{super().render_head(t)}" + + @joiner + def render_body(self, t: GraphQLObjectType) -> Iterator[str]: + yield super().render_body(t) + + if is_self_chainable(t): + self_name = self.type_name(t) + yield textwrap.dedent( + f''' + def with_(self, cb: Callable[["{self_name}"], "{self_name}"]) -> "{self_name}": + """Call the provided callable with current {self_name}. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + ''' # noqa: E501 + ) diff --git a/content/en/docs/04/solution/ci/sdk/pyproject.toml b/content/en/docs/04/solution/ci/sdk/pyproject.toml new file mode 100644 index 0000000..ca4e82b --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/pyproject.toml @@ -0,0 +1,100 @@ +[build-system] +requires = ["hatchling==1.27.0", "hatch-vcs==0.5.0"] +build-backend = "hatchling.build" + +[project] +name = "dagger-io" +dynamic = ["version"] +description = "A client package for running Dagger pipelines in Python." +readme = "README.md" +authors = [{ name = "Dagger", email = "hello@dagger.io" }] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Framework :: AnyIO", + "Framework :: Pytest", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Typing :: Typed", +] +requires-python = ">=3.10" +dependencies = [ + "anyio>=3.6.2", + "cattrs>=25.1.0", + "gql[httpx]>=3.5.0", + "beartype>=0.18.2", + "platformdirs>=2.6.2", + "typing_extensions>=4.13.0", + "rich>=10.11.0", + "opentelemetry-sdk>=1.23.0", + "opentelemetry-exporter-otlp-proto-http>=1.23.0", +] + +[project.urls] +"Homepage" = "https://dagger.io" +"Documentation" = "https://docs.dagger.io/sdk/python" +"Repository" = "https://github.com/dagger/dagger/tree/main/sdk/python" +"Tracker" = "https://github.com/dagger/dagger/issues" +"Release Notes" = "https://github.com/dagger/dagger/releases?q=tag%3Asdk%2Fpython%2Fv0" +"Community" = "https://discord.gg/ufnyBtc8uY" +"Twitter" = "https://twitter.com/dagger_io" + +[tool.uv] +dev-dependencies = [ + "codegen", + # lint + "ruff>=0.3.4", + "mypy>=1.8.0", + # test + "aiohttp>=3.9.3", + "pytest>=8.0.2", + "pytest-httpx>=0.30.0", + "pytest-mock>=3.12.0", + "pytest-subprocess>=1.5.0", + # docs + "sphinx>=7.2.6", + "sphinx-rtd-theme>=2.0.0", +] + +[tool.uv.sources] +codegen = { workspace = true } + +[tool.uv.workspace] +members = ["codegen"] + +[tool.hatch.version] +source = "vcs" +fallback-version = "0.0.0" + +[tool.hatch.build.targets.sdist] +only-include = ["src", "tests", "docs", "CHANGELOG.md", "README.md"] + +[tool.hatch.build.targets.wheel] +packages = ["src/dagger"] + +[tool.pytest.ini_options] +testpaths = ["tests/"] +addopts = ["--import-mode=importlib"] +markers = [ + "slow: mark test as slow (integration)", + "provision: mark provisioning tests", +] + +[tool.mypy] +disallow_untyped_defs = false +follow_imports = "normal" +# ignore_missing_imports = true +install_types = true +non_interactive = true +warn_redundant_casts = true +pretty = true +show_column_numbers = true +warn_no_return = false +warn_unused_ignores = true diff --git a/content/en/docs/04/solution/ci/sdk/runtime/.gitattributes b/content/en/docs/04/solution/ci/sdk/runtime/.gitattributes new file mode 100644 index 0000000..6e0a34d --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/.gitattributes @@ -0,0 +1,6 @@ + +/dagger.gen.go linguist-generated +/internal/** linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/content/en/docs/04/solution/ci/sdk/runtime/.gitignore b/content/en/docs/04/solution/ci/sdk/runtime/.gitignore new file mode 100644 index 0000000..ccf66bf --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/.gitignore @@ -0,0 +1,5 @@ +/dagger.gen.go +/internal +/internal/dagger +/internal/querybuilder +/internal/telemetry diff --git a/content/en/docs/04/solution/ci/sdk/runtime/Dockerfile b/content/en/docs/04/solution/ci/sdk/runtime/Dockerfile new file mode 100644 index 0000000..089ff24 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/Dockerfile @@ -0,0 +1,3 @@ +# Images defined here for Dependabot. +FROM python:3.12-slim@sha256:e55523f127124e5edc03ba201e3dbbc85172a2ec40d8651ac752364b23dfd733 AS base +FROM ghcr.io/astral-sh/uv:0.7.13@sha256:6c1e19020ec221986a210027040044a5df8de762eb36d5240e382bc41d7a9043 AS uv diff --git a/content/en/docs/04/solution/ci/sdk/runtime/dagger.json b/content/en/docs/04/solution/ci/sdk/runtime/dagger.json new file mode 100644 index 0000000..96ab93f --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/dagger.json @@ -0,0 +1,7 @@ +{ + "name": "python-sdk", + "engineVersion": "v0.18.3", + "sdk": { + "source": "go" + } +} diff --git a/content/en/docs/04/solution/ci/sdk/runtime/discovery.go b/content/en/docs/04/solution/ci/sdk/runtime/discovery.go new file mode 100644 index 0000000..572db59 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/discovery.go @@ -0,0 +1,486 @@ +package main + +import ( + "context" + "fmt" + "path" + "python-sdk/internal/dagger" + "strings" + "sync" + + "github.com/pelletier/go-toml/v2" + "golang.org/x/sync/errgroup" +) + +// DirExcludes are directories from the module's source that we always want to exclude. +// +// These directories can affect the build process so we just make sure to remove +// them if found to avoid any conflicts. +var DirExcludes = []string{".venv", "sdk"} + +// FileContents are files from the module's source that we always want the contents of. +// +// This is to enable a small performance optimization for loading multiple +// files concurrently rather than making blocking calls later. +var FileContents = []string{"pyproject.toml", ".python-version"} + +// Uv config bits we'd like to consume. +type UvConfig struct { + Sources struct { + Dagger UvSource `toml:"dagger-io"` + } `toml:"sources"` + + // Index is a list of uv index configurations. + // Ssee [uv v0.4.23](https://github.com/astral-sh/uv/releases/tag/0.4.23) + Index []UvIndexConfig `toml:"index"` +} + +type UvSource struct { + Path string `toml:"path"` + Editable bool `toml:"editable"` +} + +type UvIndexConfig struct { + Name string `toml:"name"` + URL string `toml:"url"` + Default bool `toml:"default"` +} + +// PyProject is the parsed pyproject.toml file. +type PyProject struct { + Project struct { + Name string `toml:"name"` + RequiresPython string `toml:"requires-python"` + Dependencies []string `toml:"dependencies"` + } `toml:"project"` + Tool struct { + Uv UvConfig `toml:"uv"` + Dagger UserConfig `toml:"dagger"` + } `toml:"tool"` +} + +// Discovery is a helper to load information from the target module. +type Discovery struct { + Config PyProject + + // Images is a map of container image names to their addresses. + Images map[string]Image + + // DefaultImages is a map of default container image addresses. + DefaultImages map[string]Image + + // FileSet is a set of file names in the SDK source directory. + SdkFileSet map[string]struct{} + + // FileSet is a set of file names from an initial Entries() call for quick lookups. + FileSet map[string]struct{} + + // Files is a map of file names to their contents. + Files map[string]string + + // EnableCustomConfig is a flag to enable or disable the discovery of custom + // configuration, either from loading pyproject.toml or reacting to the + // the presence of certain files like .python-version. + EnableCustomConfig bool + + // Used to synchronize updates. + mu sync.Mutex +} + +func NewDiscovery(cfg UserConfig) (*Discovery, error) { + proj := PyProject{} + proj.Tool.Dagger = cfg + + // Get image addresses from the Dockerfile + images, err := extractImages() + if err != nil { + return nil, fmt.Errorf("get default container image addresses: %w", err) + } + + return &Discovery{ + Config: proj, + DefaultImages: images, + Images: make(map[string]Image), + SdkFileSet: make(map[string]struct{}), + FileSet: make(map[string]struct{}), + Files: make(map[string]string), + + // Custom config can only be disabled by an extension module. + EnableCustomConfig: true, + }, nil +} + +// UserConfig is the configuration the user can set in pyproject.toml, under +// the "tool.dagger" table. +func (d *Discovery) UserConfig() *UserConfig { + return &d.Config.Tool.Dagger +} + +func (d *Discovery) UvConfig() *UvConfig { + return &d.Config.Tool.Uv +} + +// HasFile returns true if the file exists in the original module's source directory. +func (d *Discovery) HasFile(name string) bool { + _, ok := d.FileSet[name] + return ok +} + +// SdkHasFile returns true if the file exists in the SDK's source directory. +func (d *Discovery) SdkHasFile(name string) bool { + _, ok := d.SdkFileSet[name] + return ok +} + +// AddNewFile adds a new file, with contents, to the module's source. +func (m *PythonSdk) AddNewFile(name, contents string) { + m.ContextDir = m.ContextDir.WithNewFile(path.Join(m.SubPath, name), contents) +} + +// AddFile adds a file to the module's source. +func (m *PythonSdk) AddFile(name string, file *dagger.File) { + m.ContextDir = m.ContextDir.WithFile(path.Join(m.SubPath, name), file) +} + +// GetFile returns a file from the module's source. +func (m *PythonSdk) GetFile(name string) *dagger.File { + return m.ContextDir.File(path.Join(m.SubPath, name)) +} + +// UseUvLock returns true if the runtime should expect a uv.lock file. +func (m *PythonSdk) UseUvLock() bool { + d := m.Discovery + return m.UseUv() && (d.HasFile(UvLock) || !d.HasFile(PipCompileLock) && m.IsInit) +} + +// AddDirectory adds a directory to the module's source. +func (m *PythonSdk) AddDirectory(name string, dir *dagger.Directory) { + m.ContextDir = m.ContextDir.WithDirectory(path.Join(m.SubPath, name), dir) +} + +// We could use modSource.Directory("") but we'll need to use the +// context directory in GeneratedCode later, so rather than trying +// to replace the source directory in the context directory, we'll +// just use the context directory with subpath everywhere. +func (m *PythonSdk) Source() *dagger.Directory { + return m.ContextDir.Directory(m.SubPath) +} + +// getImage returns the container image address for the given name. +func (m *PythonSdk) getImage(name string) Image { + image, exists := m.Discovery.Images[name] + if !exists { + return m.Discovery.DefaultImages[name] + } + return image +} + +// Load reads from the module source files and metadata. +// +// This is intended to make all the necessary API calls as efficiently as possibly +// with concurrency early on, to avoid unnecessary blocking calls later. +func (d *Discovery) Load(ctx context.Context, m *PythonSdk) error { + type loadFunc func(context.Context, *PythonSdk) error + + tasks := []loadFunc{ + d.loadModInfo, + d.loadFiles, + d.loadConfig, + } + + for _, task := range tasks { + if err := task(ctx, m); err != nil { + return err + } + } + + return nil +} + +// loadModInfo loads the module's metadata. +func (d *Discovery) loadModInfo(ctx context.Context, m *PythonSdk) error { + eg, gctx := errgroup.WithContext(ctx) + + doneSubPath := make(chan struct{}) + + eg.Go(func() error { + defer close(doneSubPath) + p, err := m.ModSource.SourceSubpath(gctx) + if err != nil { + return fmt.Errorf("get module source subpath: %w", err) + } + d.mu.Lock() + m.SubPath = p + d.mu.Unlock() + return nil + }) + + eg.Go(func() error { + // m.Source() depends on SubPath + <-doneSubPath + entries, _ := m.Source().Entries(gctx) + d.mu.Lock() + for _, entry := range entries { + d.FileSet[entry] = struct{}{} + } + d.mu.Unlock() + return nil + }) + + eg.Go(func() error { + dig, err := m.ModSource.Digest(gctx) + if err != nil { + return fmt.Errorf("get module source digest: %w", err) + } + d.mu.Lock() + m.ContextDirPath = path.Join(ModSourceDirPath, dig) + d.mu.Unlock() + return nil + }) + + eg.Go(func() error { + modName, err := m.ModSource.ModuleOriginalName(gctx) + if err != nil { + return fmt.Errorf("get module name: %w", err) + } + d.mu.Lock() + m.ModName = modName + m.MainObjectName = NormalizeObjectName(modName) + m.ProjectName = NormalizeProjectNameFromModule(modName) + m.PackageName = NormalizePackageName(m.ProjectName) + d.mu.Unlock() + return nil + }) + + // TODO: Provide runtime modules with a boolean to indicate whether the + // module is new or not. Could be `dagger init --sdk` or `dagger develop --sdk`. + // + // With `dagger init` we can check for the presence of the dagger.json file, + // which is only being created after this code runs, but in `dagger develop`, + // the CLI changes the "sdk" field in dagger.json before loading the module. + // + // The boolean could be provided to the runtime module's constructor, + // the codegen function, or call a new and specific function only when using + // `--sdk` in the CLI, like `Init()`. + + eg.Go(func() error { + // If there's no dagger.json file, it's definitely a new module + // (dagger init). + exists, err := m.ModSource.ConfigExists(gctx) + if err != nil { + return fmt.Errorf("check if config exists: %w", err) + } + if !exists { + d.mu.Lock() + m.IsInit = true + d.mu.Unlock() + } + return nil + }) + + return eg.Wait() +} + +// loadFiles loads the contents of certain module source files. +func (d *Discovery) loadFiles(ctx context.Context, m *PythonSdk) error { + // If there's a dagger.json and no pyproject.toml, it's an init'ed module + // adding sources (`dagger develop --sdk`). + if !m.IsInit && !d.HasFile("pyproject.toml") { + m.IsInit = true + } + + // These paths should be in "exclude" in dagger.json. + // Let's remove them just in case, to avoid conflicts. + for _, exclude := range DirExcludes { + if d.HasFile(exclude) { + m.ContextDir = m.ContextDir.WithoutDirectory( + path.Join(m.SubPath, exclude), + ) + } + } + + eg, gctx := errgroup.WithContext(ctx) + + if d.EnableCustomConfig { + for _, name := range FileContents { + name := name + if d.HasFile(name) { + eg.Go(func() error { + contents, err := m.GetFile(name).Contents(gctx) + if err != nil { + return fmt.Errorf("get file contents of %q: %w", name, err) + } + d.mu.Lock() + d.Files[name] = strings.TrimSpace(contents) + d.mu.Unlock() + return nil + }) + } + } + } + + eg.Go(func() error { + // We'll use a glob pattern in fileSet to check for the existence of + // python files later. The error is normal when the target directory + // on `dagger init` doesn't exist, but just ignore otherwise (best + // effort). + entries, err := m.Source().Glob(gctx, "src/**/*.py|*.py") + if len(entries) > 0 { + d.mu.Lock() + d.FileSet["*.py"] = struct{}{} + d.mu.Unlock() + } else if err == nil && !m.IsInit { + // This can also happen on `dagger develop --sdk` if there's also + // a pyproject.toml present to customize the base container. + return fmt.Errorf("no python files found in module source") + } + return nil + }) + + eg.Go(func() error { + entries, _ := m.SdkSourceDir.Entries(gctx) + d.mu.Lock() + for _, entry := range entries { + d.SdkFileSet[entry] = struct{}{} + } + // quick check to avoid an unnecessary request + hasDist := d.SdkHasFile("dist/") + d.mu.Unlock() + + if hasDist { + entries, _ = m.SdkSourceDir.Glob(gctx, "dist/*") + d.mu.Lock() + for _, entry := range entries { + d.SdkFileSet[entry] = struct{}{} + } + d.mu.Unlock() + } + + return nil + }) + + return eg.Wait() +} + +// loadConfig loads configurations from user files listed in FileContents. +func (d *Discovery) loadConfig(ctx context.Context, m *PythonSdk) error { + // d.Files can be empty if EnableCustomConfig is false, which can be disabled + // on extension modules. Otherwise, `pyproject.toml` can only be empty + // on `dagger init`, in which case it will be created from template. + contents, exists := d.Files["pyproject.toml"] + if !exists { + return nil + } + + if err := toml.Unmarshal([]byte(contents), &d.Config); err != nil { + return err + } + + baseImage, err := d.parseBaseImage(d.DefaultImages[BaseImageName]) + if err != nil { + return err + } + uvImage, err := d.parseUvImage(d.DefaultImages[UvImageName]) + if err != nil { + return err + } + d.Images[BaseImageName] = baseImage + d.Images[UvImageName] = uvImage + + // For an existing pyproject.toml, the project name may divert from the default + if d.Config.Project.Name != "" { + m.ProjectName = d.Config.Project.Name + m.PackageName = NormalizePackageName(m.ProjectName) + } + + // Only look for vendor path when uv.lock is being used + if m.UseUvLock() { + m.VendorPath = d.Config.Tool.Uv.Sources.Dagger.Path + } + + return nil +} + +// findPythonVersion looks for a Python version pin in either `.python-version` +// or `requires-python` in pyproject.toml. +func (d *Discovery) findPythonVersion() string { + if version, ok := d.Files[".python-version"]; ok { + return version + } + // NB: In pyproject.toml, the "requires-python" option refers to a minimum + // version because it's meant for checking if the (already installed) + // Python version in the environment is compatible with what a library + // supports. If it's set, we'll use it as a fallback to decide which + // version to install. + minimum := strings.TrimSpace(d.Config.Project.RequiresPython) + + // With ">=" or a relaxed "==" we don't want to go search for the latest + // version here anyway but we know that as a minimum it'll be supported. + if strings.HasPrefix(minimum, "==") || strings.HasPrefix(minimum, ">=") { + return strings.TrimSpace(minimum[2:]) + } + + return "" +} + +// parseBaseImage parses user configuration to look for an override of the base image. +// +// Base image is constructed on a best effort: +// 1. Override in custom `base-image` setting (in pyproject.toml) +// 2. Check `.python-version` contents +// 3. Check pinned version in requires-python (in pyproject.toml) +// 4. Use the default base image +// +// To completely override the base image in pyproject.toml: +// ```toml +// [tool.dagger] +// base-image = "acme/my-python:3.11" +// ``` +// This can be useful to add customizations to the base image, such as +// additional system dependencies, or just to use a different Python +// version with full image digest. +// +// WARNING: Using an image that deviates from the official slim Python image +// is not supported and may lead to unexpected behavior. Use at own risk. +func (d *Discovery) parseBaseImage(defaultImage Image) (Image, error) { + ref := d.UserConfig().BaseImage + + if ref == "" { + version := d.findPythonVersion() + if version == "" { + return defaultImage, nil + } + + tag := fmt.Sprintf("%s-slim", version) + image, err := defaultImage.WithTag(tag) + + // If the image name and tag is the same as the default, reuse the default + // because of the digest. + if err != nil || image.Equal(defaultImage) { + return defaultImage, err + } + + return image, nil + } + return NewImage(ref) +} + +// parseUvImage parses user configuration to look for an override of the uv image. +// +// To override the uv image in pyproject.toml: +// ```toml +// [tool.dagger] +// uv-version = "0.6.14" +// ``` +// +// Can be useful to get a newer version to fix a bug or get a new feature. +func (d *Discovery) parseUvImage(defaultImage Image) (Image, error) { + version := d.UserConfig().UvVersion + + // Uv's image tag matches the version exactly. + if version != "" && version != defaultImage.Tag() { + return defaultImage.WithTag(version) + } + + return defaultImage, nil +} diff --git a/content/en/docs/04/solution/ci/sdk/runtime/extension.go b/content/en/docs/04/solution/ci/sdk/runtime/extension.go new file mode 100644 index 0000000..6708df1 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/extension.go @@ -0,0 +1,122 @@ +// Helper functions for extension modules. +// +// Exension modules are runtime modules that depend on this one, to be used +// as a custom SDK. +// +// WARNING: Extending this module is considered experimental and may change +// in the future. The public API is the ModuleRuntime and Codegen functions. +package main + +import "python-sdk/internal/dagger" + +// Disable the discovery of custom configuration +// +// If it's not necessary, it's faster without it. +func (m *PythonSdk) WithoutUserConfig() *PythonSdk { + m.Discovery.EnableCustomConfig = false + return m +} + +// Replace the underlying container +// +// Since all steps change this container, it's possible to extract it in one +// step, change it, and then set it with this function. Can be useful, for +// example, to add system packages between the WithBase() and WithSource() +// steps. +func (m *PythonSdk) WithContainer( + // The container to use + ctr *dagger.Container, +) *PythonSdk { + m.Container = ctr + return m +} + +// Image reference for the base container +func (m *PythonSdk) BaseImage() string { + return m.getImage(BaseImageName).String() +} + +// Image reference where uv is fetched from +func (m *PythonSdk) UvImage() string { + return m.getImage(UvImageName).String() +} + +// Override the base container's image +// +// Needs to be called before Load. +func (m *PythonSdk) WithBaseImage( + // The image reference + ref string, +) (*PythonSdk, error) { + m.Discovery.UserConfig().BaseImage = ref + img, err := m.Discovery.parseBaseImage(m.Discovery.DefaultImages[BaseImageName]) + if err != nil { + return nil, err + } + m.Discovery.Images[BaseImageName] = img + return m, nil +} + +// Override the uv version +// +// Needs to be called before Load. Enables uv if not already enabled. +func (m *PythonSdk) WithUvVersion( + // The uv version + version string, +) (*PythonSdk, error) { + m.WithUv().Discovery.UserConfig().UvVersion = version + img, err := m.Discovery.parseUvImage(m.Discovery.DefaultImages[UvImageName]) + if err != nil { + return nil, err + } + m.Discovery.Images[UvImageName] = img + return m, nil +} + +// Check whether to use uv or not +func (m *PythonSdk) UseUv() bool { + return m.Discovery.UserConfig().UseUv +} + +// Enable the use of uv +func (m *PythonSdk) WithUv() *PythonSdk { + m.Discovery.UserConfig().UseUv = true + return m +} + +// Disable the use of uv +func (m *PythonSdk) WithoutUv() *PythonSdk { + m.Discovery.UserConfig().UseUv = false + return m +} + +// Version to use for uv +func (m *PythonSdk) UvVersion() string { + return m.Discovery.UserConfig().UvVersion +} + +// Uv's default index URL setting +func (m *PythonSdk) IndexURL() string { + for _, cfg := range m.Discovery.UvConfig().Index { + if cfg.Name != "" { + continue + } + if cfg.Default { + return cfg.URL + } + } + return "" +} + +// Uv's "extra-index-url" setting +func (m *PythonSdk) ExtraIndexURL() string { + for _, cfg := range m.Discovery.UvConfig().Index { + if cfg.Name != "" { + continue + } + if !cfg.Default { + return cfg.URL + } + } + return "" +} diff --git a/content/en/docs/04/solution/ci/sdk/runtime/go.mod b/content/en/docs/04/solution/ci/sdk/runtime/go.mod new file mode 100644 index 0000000..b4690f4 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/go.mod @@ -0,0 +1,64 @@ +module python-sdk + +go 1.23.0 + +toolchain go1.23.6 + +require ( + github.com/distribution/reference v0.6.0 + github.com/iancoleman/strcase v0.3.0 + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/stretchr/testify v1.10.0 + golang.org/x/sync v0.12.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/99designs/gqlgen v0.17.70 + github.com/Khan/genqlient v0.8.0 + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/sosodev/duration v1.3.1 // indirect + github.com/vektah/gqlparser/v2 v2.5.23 + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 + go.opentelemetry.io/otel/log v0.8.0 + go.opentelemetry.io/otel/metric v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/sdk/log v0.8.0 + go.opentelemetry.io/otel/sdk/metric v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 + go.opentelemetry.io/proto/otlp v1.3.1 + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.71.0 + google.golang.org/protobuf v1.36.6 // indirect +) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 diff --git a/content/en/docs/04/solution/ci/sdk/runtime/go.sum b/content/en/docs/04/solution/ci/sdk/runtime/go.sum new file mode 100644 index 0000000..5c1588d --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/go.sum @@ -0,0 +1,117 @@ +github.com/99designs/gqlgen v0.17.70 h1:xgLIgQuG+Q2L/AE9cW595CT7xCWCe/bpPIFGSfsGSGs= +github.com/99designs/gqlgen v0.17.70/go.mod h1:fvCiqQAu2VLhKXez2xFvLmE47QgAPf/KTPN5XQ4rsHQ= +github.com/Khan/genqlient v0.8.0 h1:Hd1a+E1CQHYbMEKakIkvBH3zW0PWEeiX6Hp1i2kP2WE= +github.com/Khan/genqlient v0.8.0/go.mod h1:hn70SpYjWteRGvxTwo0kfaqg4wxvndECGkfa1fdDdYI= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.23 h1:PurJ9wpgEVB7tty1seRUwkIDa/QH5RzkzraiKIjKLfA= +github.com/vektah/gqlparser/v2 v2.5.23/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/content/en/docs/04/solution/ci/sdk/runtime/image.go b/content/en/docs/04/solution/ci/sdk/runtime/image.go new file mode 100644 index 0000000..1a10003 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/image.go @@ -0,0 +1,131 @@ +package main + +import ( + _ "embed" + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/distribution/reference" +) + +//go:embed Dockerfile +var dockerfile string + +// fromLineRegex should match: FROM AS +var fromLineRegex = regexp.MustCompile(`^FROM\s+([^\s]+)\s+AS\s+([^\s]+)`) + +const ( + BaseImageName = "base" + UvImageName = "uv" +) + +var baseImageNames = []string{BaseImageName, UvImageName} + +// Image represents a parsed docker image reference. +type Image struct { + named reference.Named +} + +// String returns the full reference. +func (i Image) String() string { + if i.named == nil { + return "" + } + return i.named.String() +} + +// Familiar returns the familiar string representation for the given reference. +func (i Image) Familiar() string { + return reference.FamiliarString(i.named) +} + +// Tag returns the tag of the image reference. +func (i Image) Tag() string { + if tagged, ok := i.named.(reference.Tagged); ok { + return tagged.Tag() + } + return "" +} + +// WithTag replaces the tag in the image reference. +func (i Image) WithTag(tag string) (Image, error) { + if i.named == nil { + return Image{}, fmt.Errorf("empty image") + } + tagged, err := reference.WithTag(reference.TrimNamed(i.named), tag) + if err != nil { + return i, err + } + return Image{named: tagged}, nil +} + +// Equal returns true if the given image reference begins with the current one. +// +// Useful to reuse a digest if name and tag are the same. +func (i Image) Equal(full Image) bool { + return strings.HasPrefix(full.Familiar(), i.Familiar()) +} + +func (i Image) MarshalJSON() ([]byte, error) { + return json.Marshal(i.String()) +} + +func (i *Image) UnmarshalJSON(data []byte) error { + var ref string + if err := json.Unmarshal(data, &ref); err != nil { + return err + } + if ref == "" { + return nil + } + img, err := NewImage(ref) + if err != nil { + return err + } + i.named = img.named + return nil +} + +// NewImage parses a string into a named reference transforming a familiar +// name from Docker UI to a fully qualified reference. +func NewImage(ref string) (Image, error) { + named, err := reference.ParseNormalizedNamed(ref) + if err != nil { + return Image{}, err + } + if named == nil { + return Image{}, fmt.Errorf("invalid image ref %q", ref) + } + return Image{named: named}, nil +} + +// extractImages reads from the bundled Dockerfile to extract the default docker +// image references. +func extractImages() (map[string]Image, error) { + lines := strings.Split(dockerfile, "\n") + images := make(map[string]Image) + + for _, line := range lines { + if matches := fromLineRegex.FindStringSubmatch(strings.TrimSpace(line)); matches != nil { + ref := matches[1] + name := matches[2] + + image, err := NewImage(ref) + if err != nil { + return nil, fmt.Errorf("parsing %q image ref: %w", name, err) + } + + images[name] = image + } + } + + for _, name := range baseImageNames { + if _, found := images[name]; !found { + return nil, fmt.Errorf("unable to find %q image ref", name) + } + } + + return images, nil +} diff --git a/content/en/docs/04/solution/ci/sdk/runtime/main.go b/content/en/docs/04/solution/ci/sdk/runtime/main.go new file mode 100644 index 0000000..5a49952 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/main.go @@ -0,0 +1,497 @@ +// Runtime module for the Python SDK + +package main + +import ( + "context" + _ "embed" + "fmt" + "path" + "python-sdk/internal/dagger" + "strings" +) + +const ( + ModSourceDirPath = "/src" + RuntimeExecutablePath = "/runtime" + GenDir = "sdk" + SDKGenPath = "src/dagger/client/gen.py" + UserGenPath = "src/dagger_gen.py" + SchemaPath = "/schema.json" + VenvPath = "/opt/venv" + ProjectCfg = "pyproject.toml" + PipCompileLock = "requirements.lock" + UvLock = "uv.lock" + MainObjectName = "Main" +) + +// UserConfig is the custom user configuration that users can add to their pyproject.toml. +// +// For example: +// ```toml +// [tool.dagger] +// use-uv = false +// ``` +type UserConfig struct { + // BaseImage is the image reference to use for the base container. + BaseImage string `toml:"base-image"` + + // UseUv is for choosing the faster uv tool instead of pip to install packages. + UseUv bool `toml:"use-uv"` + + // UvVersion is the version of the uv tool to use. + // + // By default, it's pinned to a specific version in each dagger version. + UvVersion string `toml:"uv-version"` +} + +func New( + // Directory with the Python SDK source code. + // +defaultPath=".." + // +ignore=["**", "!pyproject.toml", "!uv.lock", "!src/**/*.py", "!src/**/*.typed", "!codegen/pyproject.toml", "!codegen/**/*.py", "!LICENSE", "!README.md", "!dist/*"] + sdkSourceDir *dagger.Directory, +) (*PythonSdk, error) { + // Shouldn't happen due to defaultPath, but just in case. + if sdkSourceDir == nil { + return nil, fmt.Errorf("sdk source directory not provided") + } + d, err := NewDiscovery(UserConfig{ + UseUv: true, + }) + if err != nil { + return nil, err + } + return &PythonSdk{ + Discovery: d, + SdkSourceDir: sdkSourceDir, + Container: dag.Container(), + // TODO: remove the following when we no longer vendor every time + VendorPath: GenDir, + }, nil +} + +//go:embed template/pyproject.toml +var tplToml string + +//go:embed template/__init__.py +var tplInit string + +//go:embed template/main.py +var tplMain string + +// Functions for building the runtime module for the Python SDK. +// +// The server interacts directly with the ModuleRuntime and Codegen functions. +// The others were built to be composable and chainable to facilitate the +// creation of extension modules (custom SDKs that depend on this one). +type PythonSdk struct { + // Directory with the Python SDK source code + SdkSourceDir *dagger.Directory + + // Resulting container after each composing step + Container *dagger.Container + + // The original module's name + ModName string + + // The normalized python distribution package name (in pyproject.toml) + ProjectName string + + // The normalized python import package name (in the filesystem) + PackageName string + + // The normalized main object name in Python + MainObjectName string + + // The source needed to load and run a module + ModSource *dagger.ModuleSource + + // ContextDir is a copy of the context directory from the module source + // + // We add files to this directory, always joining paths with the source's + // subpath. We could use modSource.Directory("") for that if it was read-only, + // but since we have to mount the context directory in the end, rather than + // mounting the context dir and then mounting the forked source dir on top, + // we fork the context dir instead so there's only one mount in the end. + ContextDir *dagger.Directory + + // ContextDirPath is a unique host path for the module being loaded + // + // HACK: this property is computed as a unique value for a ModuleSource to + // provide a unique path on the filesystem. This is because the uv cache + // uses hashes of source paths - so we need to have something unique, or we + // can get very real conflicts in the uv cache. + ContextDirPath string + + // Relative path from the context directory to the source directory + SubPath string + + // Relative path to vendor client library into + VendorPath string + + // True if the module is new and we need to create files from the template + // + // It's assumed that this is the case if there's no pyproject.toml file. + IsInit bool + + // Discovery holds the logic for getting more information from the target module. + // +private + Discovery *Discovery +} + +// Generated code for the Python module +func (m *PythonSdk) Codegen( + ctx context.Context, + modSource *dagger.ModuleSource, + introspectionJSON *dagger.File, +) (*dagger.GeneratedCode, error) { + m, err := m.Common(ctx, modSource, introspectionJSON) + if err != nil { + return nil, err + } + + ignorePaths := []string{".venv", "**/__pycache__"} + genPaths := []string{ + // TODO: uncomment when we start generating client bindings outside the library + // UserGenPath, + } + + if m.VendorPath != "" { + ignorePaths = append(ignorePaths, m.VendorPath) + genPaths = []string{m.VendorPath + "/**"} + } + + return dag.GeneratedCode(m.Container.Directory(m.ContextDirPath)). + WithVCSGeneratedPaths(genPaths). + WithVCSIgnoredPaths(ignorePaths), nil +} + +// Container for executing the Python module runtime +func (m *PythonSdk) ModuleRuntime( + ctx context.Context, + modSource *dagger.ModuleSource, + introspectionJSON *dagger.File, +) (*dagger.Container, error) { + m, err := m.Common(ctx, modSource, introspectionJSON) + if err != nil { + return nil, err + } + return m.WithInstall().Container, nil +} + +// Common steps for the ModuleRuntime and Codegen functions +func (m *PythonSdk) Common( + ctx context.Context, + modSource *dagger.ModuleSource, + // +optional + introspectionJSON *dagger.File, +) (*PythonSdk, error) { + // The following functions were built to be composable in a granular way, + // to allow a custom SDK to depend on this one and hook into before or + // after major steps in the process. For example, you can get the base + // container, add system packages, use the new one with `WithContainer`, + // and then continue with the rest of the steps. Without this, you'd need + // to copy the entire function and modify it. + + // NB: In extension modules, Load is chainable. + _, err := m.Load(ctx, modSource) + if err != nil { + return nil, err + } + _, err = m.WithBase() + if err != nil { + return nil, err + } + return m. + WithSDK(introspectionJSON). + WithTemplate(). + WithSource(). + WithUpdates(), nil +} + +// Get all the needed information from the module's metadata and source files +func (m *PythonSdk) Load(ctx context.Context, modSource *dagger.ModuleSource) (*PythonSdk, error) { + m.ModSource = modSource + m.ContextDir = modSource.ContextDirectory() + + if err := m.Discovery.Load(ctx, m); err != nil { + return nil, fmt.Errorf("runtime module load: %w", err) + } + + return m, nil +} + +// Initialize the base Python container +// +// Workdir is set to the module's source directory. +func (m *PythonSdk) WithBase() (*PythonSdk, error) { + baseAddr := m.getImage(BaseImageName).String() + + // NB: Adding env vars with container images that were pulled allows + // modules to reuse them for performance benefits. + m.Container = dag.Container(). + // Base Python + From(baseAddr). + // This var is informational only, in case it's useful in a module. + WithEnvVariable("DAGGER_BASE_IMAGE", baseAddr). + WithEnvVariable("PYTHONUNBUFFERED", "1"). + WithEnvVariable("PIP_DISABLE_PIP_VERSION_CHECK", "1"). + WithEnvVariable("PIP_ROOT_USER_ACTION", "ignore"). + // Uv + With(m.uv()). + WithEnvVariable("UV_SYSTEM_PYTHON", "1"). + WithEnvVariable("UV_LINK_MODE", "copy"). + WithEnvVariable("UV_NATIVE_TLS", "1"). + WithEnvVariable("UV_PROJECT_ENVIRONMENT", "/opt/venv") + + if !m.UseUv() { + m.Container = m.Container.WithMountedCache("/root/.cache/pip", dag.CacheVolume("modpython-pip")) + } + if m.IndexURL() != "" { + m.Container = m.Container.WithEnvVariable("UV_INDEX_URL", m.IndexURL()) + } + if m.ExtraIndexURL() != "" { + m.Container = m.Container.WithEnvVariable("UV_EXTRA_INDEX_URL", m.ExtraIndexURL()) + } + + return m, nil +} + +func (m *PythonSdk) uv() dagger.WithContainerFunc { + // NB: Always add uvImage to avoid a dynamic base pipeline as much as possible. + // Even if users don't use it, it's useful to create a faster virtual env + // and faster install for the codegen package. + uvImage := m.getImage(UvImageName) + + bins := dag.Container().From(uvImage.String()).Rootfs() + + return func(ctr *dagger.Container) *dagger.Container { + // Use bundled uv binaries if version wasn't overridden. + if m.Discovery.SdkHasFile("dist/uv") && uvImage.Equal(m.Discovery.DefaultImages[UvImageName]) { + bins = m.SdkSourceDir.Directory("dist") + } + + return ctr. + WithDirectory("/usr/local/bin", bins, dagger.ContainerWithDirectoryOpts{ + Include: []string{"uv*"}, // uv and uvx + }). + WithMountedCache("/root/.cache/uv", dag.CacheVolume("modpython-uv")). + // These are informational only, to be leveraged by the target module if needed. + WithEnvVariable("DAGGER_UV_IMAGE", uvImage.String()). + WithEnvVariable("DAGGER_UV_VERSION", uvImage.Tag()) + } +} + +// Add the template files to skaffold a new module +// +// The following files are added: +// - /runtime +// - /pyproject.toml +// - /src//__init__.py +// - /src//main.py +func (m *PythonSdk) WithTemplate() *PythonSdk { + m.Container = m.Container. + WithFile( + RuntimeExecutablePath, + dag.CurrentModule().Source().File("template/runtime.py"), + dagger.ContainerWithFileOpts{Permissions: 0o755}, + ). + WithEntrypoint([]string{RuntimeExecutablePath}) + + d := m.Discovery + + // NB: We can't detect if it's a new module with `dagger develop --sdk` + // if there's also a pyproject.toml file to customize the base container. + // + // The reason for adding sources only on new modules is because it's + // been reported that it's surprising for users to delete the pyhton + // file on the host and not fail on `dagger functions` and `dagger call`, + // if we always recreate from the template. That's because only `init` + // and `develop` export the generated files back to the host, potentially + // creating a discrepancy. + // + // Throwing an error on missing files when not a new module is less + // surprising, which is done during discovery. + + if m.IsInit { + // On `dagger init --sdk`, one can first set a `pyproject.toml` to + // change the base image, but if it's `dagger develop --sdk` the + // existence of this file will set d.IsInit = true, thus skipping + // this entire branch. + if !d.HasFile(ProjectCfg) { + projCfg := strings.ReplaceAll(tplToml, "main", m.ProjectName) + m.AddNewFile(ProjectCfg, VendorConfig(projCfg, m.VendorPath)) + } + if !d.HasFile("*.py") { + m.AddNewFile( + path.Join("src", m.PackageName, "__init__.py"), + strings.ReplaceAll(tplInit, MainObjectName, m.MainObjectName), + ) + m.AddNewFile( + path.Join("src", m.PackageName, "main.py"), + strings.ReplaceAll(tplMain, MainObjectName, m.MainObjectName), + ) + } + } + + return m +} + +// Add the SDK package to the source directory +// +// This includes regenerating the client bindings for the current API schema +// (codegen). +func (m *PythonSdk) WithSDK(introspectionJSON *dagger.File) *PythonSdk { + if m.VendorPath != "" { + src := m.SdkSourceDir + // If not vendoring we don't care to remove this + if m.Discovery.SdkHasFile("dist/") { + src = src.WithoutDirectory("dist") + } + m.AddDirectory(m.VendorPath, src) + } + + // Allow empty introspection to facilitate debugging the container with a + // `dagger call module-runtime terminal` command. + if introspectionJSON != nil { + ctr := m.Container + cmd := []string{"codegen"} + + // When not using the bundled codegen executable we can revert to executing directly + if m.Discovery.SdkHasFile("dist/codegen") { + ctr = ctr. + WithMountedCache("/root/.shiv", dag.CacheVolume("shiv")). + WithMountedFile("/usr/local/bin/codegen", m.SdkSourceDir.File("dist/codegen")) + } else { + ctr = ctr. + WithWorkdir("/sdk"). + WithMountedDirectory("", m.SdkSourceDir) + cmd = []string{ + "uv", "run", "--isolated", "--frozen", "--package", "codegen", + "python", "-m", "codegen", + } + } + + genFile := ctr. + // mounted schema as late as possible because it varies more often + WithMountedFile(SchemaPath, introspectionJSON). + WithExec(append(cmd, "generate", "-i", SchemaPath, "-o", "/gen.py")). + File("/gen.py") + + genPath := UserGenPath + + // For now, patch vendored client library with generated bindings. + // TODO: Always generate outside library, even if vendored. + if m.VendorPath != "" { + genPath = path.Join(m.VendorPath, SDKGenPath) + } + + m.AddFile(genPath, genFile) + } + + return m +} + +// Add the module's source code +func (m *PythonSdk) WithSource() *PythonSdk { + m.Container = m.Container. + WithWorkdir(path.Join(m.ContextDirPath, m.SubPath)). + WithMountedDirectory(m.ContextDirPath, m.ContextDir). + // These are added as late as possible to avoid cache invalidation + // between different modules. It may be used by the runtime entrypoint + // so only needed in ModuleRuntime but added here so that extension + // modules get it for free since they need to reimplement ModuleRuntime. + // It's ok since the previous layer is already dependent on the target + // module's sources. + WithEnvVariable("DAGGER_MODULE", m.ModName). + WithEnvVariable("DAGGER_DEFAULT_PYTHON_PACKAGE", m.PackageName). + WithEnvVariable("DAGGER_MAIN_OBJECT", m.MainObjectName) + return m +} + +// Make any updates to current source +func (m *PythonSdk) WithUpdates() *PythonSdk { + if !m.UseUv() { + return m + } + + ctr := m.Container + d := m.Discovery + + // Update lock file but without upgrading dependencies. + switch { + case m.UseUvLock(): + // Support uv.lock. Takes precedence. + // Always update if uv.lock exists, but only create a new uv.lock + // if init and there's not already a requirements.lock. + ctr = ctr.WithExec([]string{"uv", "lock"}) + + case d.HasFile(PipCompileLock) && !m.IsInit: + // Support requirements.lock (legacy). + args := []string{ + "uv", "pip", "compile", "-q", "--universal", + "-o", PipCompileLock, + ProjectCfg, + } + + if m.VendorPath != "" { + args = append(args, path.Join(m.VendorPath, ProjectCfg)) + } + + ctr = ctr.WithExec(args) + } + + m.Container = ctr + + return m +} + +// Install the module's package and dependencies +func (m *PythonSdk) WithInstall() *PythonSdk { + // NB: Only enable bytecode compilation in `dagger call` + // (not `dagger init/develop`), to avoid having to remove the .pyc files + // before exporting the module back to the host. + ctr := m.Container.WithEnvVariable("UV_COMPILE_BYTECODE", "1") + + // Support uv.lock for simple and fast project management workflow. + if m.UseUvLock() { + // While best practice is to sync dependencies first with only pyproject.toml and + // uv.lock, user projects can have more required files for a minimally successful + // `uv sync --no-install-project --no-dev`. + // Besides, uv is fast enough that's not too bad to skip this optimization. + m.Container = ctr. + WithExec([]string{"uv", "sync", "--no-dev"}). + // Activate virtualenv to avoid having to prepend `uv run` to the entrypoint. + WithEnvVariable("VIRTUAL_ENV", "$UV_PROJECT_ENVIRONMENT", dagger.ContainerWithEnvVariableOpts{ + Expand: true, + }). + WithEnvVariable("PATH", "$VIRTUAL_ENV/bin:$PATH", dagger.ContainerWithEnvVariableOpts{ + Expand: true, + }) + return m + } + + // Fallback to pip-compile workflow (legacy). + install := []string{"pip", "install", "-e", "./sdk", "-e", "."} + check := []string{"pip", "check"} + + // uv has a compatible API with pip + if m.UseUv() { + // Support requirements.lock. + if m.Discovery.HasFile(PipCompileLock) { + // If there's a lock file, we assume that all the dependencies are + // included in it so we can avoid resolving for them to get a faster + // install. + install = append(install, "--no-deps", "-r", PipCompileLock) + } + // pip compiles by default, but not uv + install = append([]string{"uv"}, install...) + check = append([]string{"uv"}, check...) + } + + m.Container = ctr. + WithExec(install). + WithExec(check) + + return m +} diff --git a/content/en/docs/04/solution/ci/sdk/runtime/python.go b/content/en/docs/04/solution/ci/sdk/runtime/python.go new file mode 100644 index 0000000..3caa2ed --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/python.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "regexp" + "strings" + + "github.com/iancoleman/strcase" +) + +var ( + canonicalize = regexp.MustCompile(`[._-]+`) + disallowed = regexp.MustCompile(`[^a-z0-9-]+`) +) + +// NormalizeProjectName normalizes the project name in pyproject.toml +// +// Additionally to PEP 508, non-allowed characters are simply removed +// instead of raising an error. +// +// See https://packaging.python.org/en/latest/specifications/name-normalization/ +func NormalizeProjectName(n string) string { + // project name is case insensitive + n = strings.ToLower(n) + // valid non alphanumeric chars, even if repeated, should be replaced + // with a single "-" + n = canonicalize.ReplaceAllString(n, "-") + // instead of erroring, remove any other disallowed characters + n = disallowed.ReplaceAllString(n, "") + // remove leading and trailing dashes + return strings.Trim(n, "-") +} + +// NormalizeProjectNameFromModule normalizes the project name in `pyproject.toml` +// from the module name in `dagger.json`. +// +// Since the name in `dagger.json` currently allows more than what's valid for +// `pyproject.toml`, we allow `camelCase` and convert spaces to `-` before +// normalizing the name to PEP 508 standard. +func NormalizeProjectNameFromModule(n string) string { + // Since the main object name is the `PascalCase` version of the + // module's name, let's just convert to `kebab-case` from that. + n = NormalizeObjectName(n) + n = strcase.ToKebab(n) + return NormalizeProjectName(n) +} + +// NormalizePackageName normalizes the name of the directory where the +// source files will be imported from +// +// Assumes input is the normalized project name. +func NormalizePackageName(n string) string { + return strings.ReplaceAll(n, "-", "_") +} + +// NormalizeObjectName normalizes the name of the class that is the main +// Dagger object of the module +func NormalizeObjectName(n string) string { + return strcase.ToCamel(n) +} + +// VendorConfig appends to a pyproject.toml the config to vendor the client library +func VendorConfig(cfg, path string) string { + if path == "" { + return cfg + } + return fmt.Sprintf( + "%s\n\n[tool.uv.sources]\ndagger-io = { path = %q, editable = true }\n", + strings.TrimSpace(cfg), + path, + ) +} diff --git a/content/en/docs/04/solution/ci/sdk/runtime/template/__init__.py b/content/en/docs/04/solution/ci/sdk/runtime/template/__init__.py new file mode 100644 index 0000000..ae84e4a --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/template/__init__.py @@ -0,0 +1,16 @@ +"""A generated module for Main functions + +This module has been generated via dagger init and serves as a reference to +basic module structure as you get started with Dagger. + +Two functions have been pre-created. You can modify, delete, or add to them, +as needed. They demonstrate usage of arguments and return types using simple +echo and grep commands. The functions can be called from the dagger CLI or +from one of the SDKs. + +The first line in this comment block is a short description line and the +rest is a long description with more detail on the module's purpose or usage, +if appropriate. All modules should have a short description. +""" + +from .main import Main as Main diff --git a/content/en/docs/04/solution/ci/sdk/runtime/template/main.py b/content/en/docs/04/solution/ci/sdk/runtime/template/main.py new file mode 100644 index 0000000..3fa51ba --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/template/main.py @@ -0,0 +1,22 @@ +import dagger +from dagger import dag, function, object_type + + +@object_type +class Main: + @function + def container_echo(self, string_arg: str) -> dagger.Container: + """Returns a container that echoes whatever string argument is provided""" + return dag.container().from_("alpine:latest").with_exec(["echo", string_arg]) + + @function + async def grep_dir(self, directory_arg: dagger.Directory, pattern: str) -> str: + """Returns lines that match a pattern in the files of the provided Directory""" + return await ( + dag.container() + .from_("alpine:latest") + .with_mounted_directory("/mnt", directory_arg) + .with_workdir("/mnt") + .with_exec(["grep", "-R", pattern, "."]) + .stdout() + ) diff --git a/content/en/docs/04/solution/ci/sdk/runtime/template/pyproject.toml b/content/en/docs/04/solution/ci/sdk/runtime/template/pyproject.toml new file mode 100644 index 0000000..67a1891 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/template/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "main" +version = "0.1.0" +requires-python = ">=3.12" +dependencies = ["dagger-io"] + +[build-system] +requires = ["hatchling==1.25.0"] +build-backend = "hatchling.build" diff --git a/content/en/docs/04/solution/ci/sdk/runtime/template/runtime.py b/content/en/docs/04/solution/ci/sdk/runtime/template/runtime.py new file mode 100644 index 0000000..0fc6d32 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/runtime/template/runtime.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import sys + +from dagger.mod.cli import app + +if __name__ == "__main__": + sys.exit(app()) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/__init__.py b/content/en/docs/04/solution/ci/sdk/src/dagger/__init__.py new file mode 100644 index 0000000..c82edfe --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/__init__.py @@ -0,0 +1,32 @@ +import contextlib + +# Make sure to place exceptions first as they're dependencies of other imports. +from dagger._exceptions import * + +# Engine provisioning (doesn't make sense in modules) +with contextlib.suppress(ModuleNotFoundError): + from dagger.provisioning import * + +# Client bindings +try: + # Custom extended API bindings can be placed in user's src/dagger_gen.py + from dagger_gen import * +except ModuleNotFoundError: + # Only core API bindings + from dagger.client.gen import * + +# Client connection +from dagger.client._config import Retry as Retry +from dagger.client._config import Timeout as Timeout +from dagger.client._connection import connect as connect +from dagger.client._connection import close as close + +# Module support (only makes sense in a module runtime container) +with contextlib.suppress(ModuleNotFoundError): + from dagger.mod import * + +# Re-export imports so they look like they live directly in this package. +for _value in list(locals().values()): + if getattr(_value, "__module__", "").startswith("dagger."): + with contextlib.suppress(AttributeError): + _value.__module__ = __name__ diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/_exceptions.py b/content/en/docs/04/solution/ci/sdk/src/dagger/_exceptions.py new file mode 100644 index 0000000..fa6bfb0 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/_exceptions.py @@ -0,0 +1,177 @@ +import dataclasses +from typing import Any + +import cattrs +import graphql +from gql.transport.exceptions import TransportQueryError + + +class VersionMismatch(Warning): + """Dagger CLI version doesn't match required version.""" + + +class DaggerError(Exception): + """Base exception for all Dagger exceptions.""" + + +class ClientError(DaggerError): + """Base class for client errors.""" + + +class ClientConnectionError(ClientError): + """Error while establishing a client connection to the server.""" + + def __str__(self) -> str: + return ( + "Failed to establish client connection to the Dagger session: " + f"{super().__str__()}" + ) + + +class TransportError(ClientError): + """Error processing request/response during query execution.""" + + +class ExecuteTimeoutError(TransportError): + """Timeout while executing a query.""" + + +class InvalidQueryError(ClientError): + """Misuse of the query builder.""" + + +@dataclasses.dataclass(slots=True) +class QueryErrorLocation: + """Error location returned by the API.""" + + line: int + column: int + + +@dataclasses.dataclass(slots=True) +class QueryErrorValue: + """An error value returned by the API.""" + + message: str + locations: list[QueryErrorLocation] | None = None + path: list[str] | None = None + extensions: dict[str, Any] = dataclasses.field(default_factory=dict) + + def __str__(self) -> str: + return self.message + + +class QueryError(ClientError): + """The server returned an error for a specific query.""" + + _type = None + + def __new__(cls, errors: list[QueryErrorValue], *_): + error_types = { + subclass._type: subclass # noqa: SLF001 + for subclass in cls.__subclasses__() + if subclass._type # noqa: SLF001 + } + try: + new_type = error_types[errors[0].extensions["_type"]] + except (KeyError, IndexError): + return super().__new__(cls) + return super().__new__(new_type) + + def __init__(self, errors: list[QueryErrorValue], query: graphql.DocumentNode): + if not errors: + msg = "Errors list is empty" + raise ValueError(msg) + super().__init__(errors[0]) + self.errors: list[QueryErrorValue] = errors + self.query = query + + def debug_query(self): + """Return GraphQL query for debugging purposes. + + Example:: + + try: + await ctr + except dagger.QueryError as e: + print(e.debug_query()) + """ + lines = graphql.print_ast(self.query).splitlines() + # count number of digits from line count + pad = len(str(len(lines))) + locations = ( + {loc.line: loc.column for loc in self.errors[0].locations} + if self.errors[0].locations + else {} + ) + res = [] + for nr, line in enumerate(lines, start=1): + # prepend line number + res.append(f"{{:{pad}d}}: {{}}".format(nr, line)) + if nr in locations: + # add caret below line, pointing to start of error + res.append(" " * (pad + 1 + locations[nr]) + "^") + return "\n".join(res) + + +def _query_error_from_transport(exc: TransportQueryError, query: graphql.DocumentNode): + """Create instance from a gql exception.""" + try: + errors = cattrs.structure(exc.errors, list[QueryErrorValue]) + except (TypeError, KeyError, ValueError): + return None + return QueryError(errors, query) if errors else None + + +class ExecError(QueryError): + """API error from an exec operation. + + Attributes + ---------- + command: + The command that was executed. + message: + The error message. + exit_code: + The exit code of the command. + stdout: + The stdout of the command. + stderr: + The stderr of the command. + """ + + _type = "EXEC_ERROR" + + command: list[str] + message: str + exit_code: int + stdout: str + stderr: str + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + error: QueryErrorValue = self.args[0] + ext = error.extensions + self.command = ext["cmd"] + self.message = error.message + self.exit_code = ext["exitCode"] + self.stdout = ext["stdout"] + self.stderr = ext["stderr"] + + def __str__(self): + """Prints the original error message.""" + return self.message + + +__all__ = [ + "ClientConnectionError", + "ClientError", + "DaggerError", + "ExecError", + "ExecuteTimeoutError", + "InvalidQueryError", + "QueryError", + "TransportError", + "VersionMismatch", +] diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/_managers.py b/content/en/docs/04/solution/ci/sdk/src/dagger/_managers.py new file mode 100644 index 0000000..3e3a9c6 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/_managers.py @@ -0,0 +1,44 @@ +import contextlib +import typing + +import anyio.to_thread + +asyncify = anyio.to_thread.run_sync + + +class ResourceManager(contextlib.AbstractAsyncContextManager): + def __init__(self): + super().__init__() + self.stack = contextlib.AsyncExitStack() + + @contextlib.asynccontextmanager + async def get_stack(self) -> typing.AsyncIterator[contextlib.AsyncExitStack]: + async with self.stack as stack: + yield stack + self.stack = stack.pop_all() + + async def __aexit__(self, *_) -> None: + await self.close() + + async def close(self) -> None: + await self.stack.aclose() + + # For compatibility with contextlib.aclosing. + async def aclose(self) -> None: + await self.close() + + +T = typing.TypeVar("T") + + +class SyncResource(contextlib.AbstractAsyncContextManager[T], typing.Generic[T]): + """Wrap a blocking sync context manager in a non-blocking async context manager.""" + + def __init__(self, cm: typing.ContextManager[T]): + self.sync_cm = cm + + async def __aenter__(self) -> T: + return await asyncify(self.sync_cm.__enter__) + + async def __aexit__(self, *exc_details) -> None: + await asyncify(self.sync_cm.__exit__, *exc_details) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/__init__.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/_config.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_config.py new file mode 100644 index 0000000..73dceb8 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_config.py @@ -0,0 +1,40 @@ +import dataclasses +from collections.abc import Callable +from typing import Any, TypeVar + +import httpx + +_CallableT = TypeVar("_CallableT", bound=Callable[..., Any]) +_Decorator = Callable[[_CallableT], _CallableT] + + +@dataclasses.dataclass(slots=True, kw_only=True) +class Retry: + """Retry parameters for connecting to the Dagger API server.""" + + connect: bool | _Decorator = True + execute: bool | _Decorator = True + + +class Timeout(httpx.Timeout): + """ + Timeout configuration. + + Examples:: + + Timeout(None) # No timeouts. + Timeout(5.0) # 5s timeout on all operations. + Timeout(None, connect=5.0) # 5s timeout on connect, no other timeouts. + Timeout(5.0, connect=10.0) # 10s timeout on connect. 5s timeout elsewhere. + Timeout(5.0, pool=None) # No timeout on acquiring connection from pool. + """ + + @classmethod + def default(cls) -> "Timeout": + return cls(None, connect=10.0) + + +@dataclasses.dataclass(slots=True, kw_only=True) +class ConnectConfig: + timeout: Timeout | None = dataclasses.field(default_factory=Timeout.default) + retry: Retry | None = dataclasses.field(default_factory=Retry) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/_connection.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_connection.py new file mode 100644 index 0000000..6640389 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_connection.py @@ -0,0 +1,5 @@ +from dagger.client._session import SharedConnection + +_shared = SharedConnection() +connect = _shared.connect +close = _shared.close diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/_core.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_core.py new file mode 100644 index 0000000..d4b4654 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_core.py @@ -0,0 +1,312 @@ +import collections +import dataclasses +import enum +import functools +import logging +import typing +from dataclasses import MISSING +from typing import ( + Any, + TypeVar, + overload, +) + +import anyio +import cattrs +import graphql +import httpx +from beartype.door import TypeHint +from cattrs.preconf.json import make_converter as make_json_converter +from gql.dsl import DSLField, DSLQuery, DSLSchema, DSLSelectable, DSLType, dsl_gql +from gql.transport.exceptions import ( + TransportClosed, + TransportProtocolError, + TransportQueryError, + TransportServerError, +) +from typing_extensions import TypeForm + +from dagger import ( + ExecuteTimeoutError, + InvalidQueryError, + TransportError, +) +from dagger._exceptions import _query_error_from_transport +from dagger.client._session import BaseConnection, SharedConnection +from dagger.client.base import Scalar, Type + +from ._guards import ( + IDType, + is_id_type, + is_id_type_sequence, +) + +logger = logging.getLogger(__name__) + +T = TypeVar("T") +Obj_T = TypeVar("Obj_T", bound=Type) + + +class Arg(typing.NamedTuple): + name: str # GraphQL name + value: Any + default: Any = MISSING + + +@dataclasses.dataclass(slots=True) +class Field: + type_name: str + name: str + args: dict[str, Any] + children: dict[str, "Field"] = dataclasses.field(default_factory=dict) + + def to_dsl(self, schema: DSLSchema) -> DSLField: + type_: DSLType = getattr(schema, self.type_name) + field_ = getattr(type_, self.name)(**self.args) + if self.children: + field_ = field_.select( + **{name: child.to_dsl(schema) for name, child in self.children.items()} + ) + return field_ + + def add_child(self, child: "Field") -> "Field": + return dataclasses.replace(self, children={child.name: child}) + + +@dataclasses.dataclass(slots=True) +class Context: + conn: BaseConnection = dataclasses.field( + default_factory=SharedConnection, + compare=False, + ) + selections: collections.deque[Field] = dataclasses.field( + default_factory=collections.deque + ) + converter: cattrs.Converter = dataclasses.field( + init=False, + compare=False, + ) + + def __post_init__(self): + self.converter = make_converter(self) + + def select( + self, + type_name: str, + field_name: str, + args: typing.Sequence[Arg], + ) -> "Context": + args_ = self.converter.unstructure( + {arg.name: arg.value for arg in args if arg.value is not arg.default} + ) + field_ = Field(type_name, field_name, args_) + selections = self.selections.copy() + selections.append(field_) + return dataclasses.replace(self, selections=selections) + + def select_multiple(self, type_name: str, **fields: str) -> "Context": + selections = self.selections.copy() + parent = selections.pop() + # When selecting multiple fields, set them as children of the last + # selection to make `build` logic simpler. + field_ = dataclasses.replace( + parent, + # Using kwargs for alias names. This way the returned result + # is already formatted with the python name we expect. + children={k: Field(type_name, v, {}) for k, v in fields.items()}, + ) + selections.append(field_) + return dataclasses.replace(self, selections=selections) + + def root_select( + self, + field_name: str, + args: typing.Sequence[Arg], + ) -> "Context": + ctx = dataclasses.replace(self, selections=collections.deque()) + return ctx.select("Query", field_name, args) + + def select_id(self, type_name: str, id_value: str) -> "Context": + return self.root_select( + f"load{type_name}FromID", + [Arg("id", id_value)], + ) + + async def build(self) -> DSLSelectable: + if not self.selections: + msg = "No field has been selected" + raise InvalidQueryError(msg) + + def _collapse(child: Field, field_: Field): + return field_.add_child(child) + + # This transforms the selection set into a single root Field, where + # the `children` attribute is set to the next selection in the set, + # and so on... + root = functools.reduce(_collapse, reversed(self.selections)) + + # `to_dsl` will cascade to all children, until the end. + return root.to_dsl(DSLSchema(await self.conn.session.get_schema())) + + async def query(self) -> graphql.DocumentNode: + return dsl_gql(DSLQuery(await self.build())) + + @overload + async def execute(self, return_type: None = None) -> None: ... + + @overload + async def execute(self, return_type: TypeForm[T] | type[T]) -> T: ... + + async def execute( + self, return_type: TypeForm[T] | type[T] | None = None + ) -> T | None: + await self.resolve_ids() + query = await self.query() + + try: + result = await self.conn.session.execute(query) + except httpx.TimeoutException as e: + msg = ( + "Request timed out. Try setting a higher timeout value in " + "for this connection." + ) + raise ExecuteTimeoutError(msg) from e + + except httpx.RequestError as e: + msg = f"Failed to make request: {e}" + raise TransportError(msg) from e + + except TransportClosed as e: + msg = ( + "Connection to engine has been closed. Make sure you're " + "calling the API within a `dagger.Connection()` context." + ) + raise TransportError(msg) from e + + except (TransportProtocolError, TransportServerError) as e: + msg = f"Unexpected response from engine: {e}" + raise TransportError(msg) from e + + except TransportQueryError as e: + if error := _query_error_from_transport(e, query): + raise error from e + raise + + return self.get_value(result, return_type) if return_type else None + + async def execute_object_list( + self, + element_type: type[Obj_T], + ) -> list[Obj_T]: + @dataclasses.dataclass + class Response: + id: str + + ctx = element_type(self)._select("id", []) # noqa: SLF001 + ids = await ctx.execute(list[Response]) + + return [element_type(ctx.select_id(element_type.__name__, v.id)) for v in ids] + + async def execute_sync( + self, + obj: Obj_T, + field_name: str = "sync", + args: typing.Sequence[Arg] = (), + ) -> Obj_T: + ctx = obj._select(field_name, args) # noqa: SLF001 + id_ = await ctx.execute(Scalar) + cls = obj.__class__ + ctx = self.select_id(cls.__name__, id_) + return cls(ctx) + + @overload + def get_value(self, value: None, return_type: Any) -> None: ... + + @overload + def get_value(self, value: dict[str, Any], return_type: type[T]) -> T: ... + + def get_value(self, value: dict[str, Any] | None, return_type: type[T]) -> T | None: + type_hint = TypeHint(return_type) + + for f in self.selections: + if not isinstance(value, dict): + break + value = value[f.name] + + if value is None and not type_hint.is_bearable(value): + msg = ( + "Required field got a null response. Check if parent fields are valid." + ) + raise InvalidQueryError(msg) + + return self.converter.structure(value, return_type) + + async def resolve_ids(self) -> None: + """Replace Type object instances with their ID implicitly.""" + + # mutating to avoid re-fetching on forked pipeline + async def _resolve_id(pos: int, k: str, v: IDType): + sel = self.selections[pos] + sel.args[k] = await v.id() + + async def _resolve_seq_id(pos: int, idx: int, k: str, v: IDType): + sel = self.selections[pos] + sel.args[k][idx] = await v.id() + + # resolve all ids concurrently + async with anyio.create_task_group() as tg: + for i, sel in enumerate(self.selections): + for k, v in sel.args.items(): + # check if it's a sequence of Type objects + if is_id_type_sequence(v): + # make sure it's a list, to mutate by index + sel.args[k] = list(v) + for seq_i, seq_v in enumerate(sel.args[k]): + if is_id_type(seq_v): + tg.start_soon(_resolve_seq_id, i, seq_i, k, seq_v) + elif is_id_type(v): + tg.start_soon(_resolve_id, i, k, v) + + +def make_converter(ctx: Context): + conv = make_json_converter( + omit_if_default=True, + detailed_validation=False, + ) + + # For types that were returned from a list we need to set + # their private attributes with a custom structuring function. + + def _needs_hook(cls: type) -> bool: + return issubclass(cls, Type) and hasattr(cls, "__slots__") + + def _struct(d: dict[str, Any], cls: type) -> Any: + obj = cls(ctx) + hints = typing.get_type_hints(cls) + for slot in getattr(cls, "__slots__", ()): + t = hints.get(slot) + if t and slot in d: + setattr(obj, slot, conv.structure(d[slot], t)) + return obj + + conv.register_structure_hook_func( + _needs_hook, + _struct, + ) + + configure_converter_enum(conv) + + return conv + + +def configure_converter_enum(conv: cattrs.Converter, cl: typing.Any = enum.Enum): + """Register hooks for structuring and destructuring enums using member names.""" + + def to_enum_name(val: enum.Enum) -> str: + return val.name + + def from_enum_name(name: str, cls: type[enum.Enum]) -> enum.Enum: + return cls[name] + + conv.register_unstructure_hook(cl, to_enum_name) + conv.register_structure_hook(cl, from_enum_name) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/_guards.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_guards.py new file mode 100644 index 0000000..e931117 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_guards.py @@ -0,0 +1,45 @@ +import typing +from collections.abc import Sequence +from typing import Annotated, TypeGuard + +from beartype import BeartypeConf, BeartypeViolationVerbosity, beartype +from beartype.door import TypeHint +from beartype.vale import Is, IsInstance, IsSubclass + +from dagger.client.base import Scalar, Type + +IDScalar = Annotated[Scalar, Is[lambda obj: type(obj).__name__.endswith("ID")]] + + +@typing.runtime_checkable +class HasID(typing.Protocol): + async def id(self) -> IDScalar: ... + + +IDTypeSubclass = Annotated[type[HasID], IsSubclass[Type]] +IDType = Annotated[HasID, IsInstance[Type]] +IDTypeSeq = Annotated[Sequence[IDType], ~IsInstance[str]] + +IDTypeSubclassHint = TypeHint(IDTypeSubclass) +IDTypeHint = TypeHint(IDType) +IDTypeSeqHint = TypeHint(IDTypeSeq) + + +def is_id_type_subclass(v: type) -> TypeGuard[type[Type]]: + return IDTypeSubclassHint.is_bearable(v) + + +def is_id_type(v: object) -> TypeGuard[IDType]: + return IDTypeHint.is_bearable(v) + + +def is_id_type_sequence(v: object) -> TypeGuard[IDTypeSeq]: + return IDTypeSeqHint.is_bearable(v) + + +typecheck = beartype( + conf=BeartypeConf( + violation_param_type=TypeError, + violation_verbosity=BeartypeViolationVerbosity.MINIMAL, + ) +) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/_session.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_session.py new file mode 100644 index 0000000..b64eccd --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/_session.py @@ -0,0 +1,252 @@ +import contextlib +import logging +import os +from dataclasses import dataclass, field +from typing import Any + +import graphql +import httpx +from gql.client import AsyncClientSession +from gql.client import Client as GraphQLClient +from gql.transport.exceptions import ( + TransportProtocolError, + TransportQueryError, + TransportServerError, +) +from gql.transport.httpx import HTTPXAsyncTransport +from opentelemetry import propagate +from typing_extensions import Self + +from dagger import ClientConnectionError, telemetry +from dagger._managers import ResourceManager +from dagger.client._config import ConnectConfig, Retry + +logger = logging.getLogger(__name__) + + +@dataclass(slots=True, kw_only=True) +class ConnectParams: + """Options for making a session connection. For internal use only.""" + + port: int + session_token: str + url: httpx.URL = field(init=False) + + def __post_init__(self): + self.port = int(self.port) + if self.port < 1: + msg = f"Invalid port value: {self.port}" + raise ValueError(msg) + self.url = httpx.URL(f"http://127.0.0.1:{self.port}/query") + + @classmethod + def from_env(cls) -> "ConnectParams | None": + if not (port := os.getenv("DAGGER_SESSION_PORT")): + return None + if not (token := os.getenv("DAGGER_SESSION_TOKEN")): + msg = "DAGGER_SESSION_TOKEN must be set when using DAGGER_SESSION_PORT" + raise ClientConnectionError(msg) + try: + return cls(port=int(port), session_token=token) + except ValueError as e: + # only port is validated + msg = f"Invalid DAGGER_SESSION_PORT: {port}" + raise ClientConnectionError(msg) from e + + +class TelemetryTransport(httpx.AsyncHTTPTransport): + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + # Get traceparent into request headers if present. + propagate.inject(request.headers) + return await super().handle_async_request(request) + + +class ClientSession(ResourceManager): + """Establish a GraphQL client connection to the engine.""" + + def __init__(self, conn: ConnectParams, cfg: ConnectConfig | None = None): + super().__init__() + + if cfg is None: + cfg = ConnectConfig() + + transport = HTTPXAsyncTransport( + conn.url, + transport=TelemetryTransport(), + timeout=cfg.timeout, + auth=(conn.session_token, ""), + ) + + client = GraphQLClient( + transport=transport, + fetch_schema_from_transport=True, + # We're using the timeout from the httpx transport. + execute_timeout=None, + ) + + self.client = retrying_client(client, cfg.retry) if cfg.retry else client + self._session: AsyncClientSession | None = None + + async def __aenter__(self) -> Self: + await self.start() + return self + + async def start(self) -> AsyncClientSession: + if self._session: + return self._session + + async with self.get_stack() as stack: + logger.debug("Establishing client session to GraphQL server") + + try: + session = await stack.enter_async_context(self.client) + except TimeoutError as e: + msg = f"Failed to connect to engine: {e}" + raise ClientConnectionError(msg) from e + except httpx.RequestError as e: + msg = f"Could not make request: {e}" + raise ClientConnectionError(msg) from e + except (TransportProtocolError, TransportServerError) as e: + msg = f"Got unexpected response from engine: {e}" + raise ClientConnectionError(msg) from e + except TransportQueryError as e: + # Only query during connection is the introspection query + # for building the schema. + msg = str(e) + # Extract only the error message. + if e.errors and "message" in e.errors[0]: + msg = e.errors[0]["message"].strip() + msg = f"Failed to build schema from introspection query: {msg}" + raise ClientConnectionError(msg) from e + + self._session = session + return session + + def has_session(self): + return self._session is not None + + async def get_session(self) -> AsyncClientSession: + return await self.start() + + async def get_schema(self) -> graphql.GraphQLSchema: + client = (await self.get_session()).client + if not client.schema: + msg = "No schema in session" + raise ClientConnectionError(msg) + return client.schema + + async def execute(self, query: graphql.DocumentNode) -> Any: + return await (await self.get_session()).execute(query) + + async def close(self) -> None: + logger.debug("Closing client session to GraphQL server") + await super().close() + + +@contextlib.asynccontextmanager +async def retrying_client(client: GraphQLClient, retry: Retry): + try: + yield await client.connect_async( + reconnecting=True, + retry_connect=retry.connect, + retry_execute=retry.execute, + ) + finally: + await client.close_async() + + +class BaseConnection: + session: ClientSession + + async def connect(self) -> Self: + await self.session.start() + return self + + async def close(self) -> None: + await self.session.close() + + async def aclose(self) -> None: + await self.close() + + def __await__(self): + return self.connect().__await__() + + async def __aenter__(self) -> Self: + telemetry.initialize() + return await self.connect() + + async def __aexit__(self, *_) -> None: + await self.close() + + +class SingleConnection(BaseConnection): + """Establish a GraphQL client connection to the Dagger API server.""" + + def __init__(self, conn: ConnectParams, cfg: ConnectConfig | None = None): + self.session = ClientSession(conn, cfg) + + +class SharedConnection(BaseConnection): + """Establish a GraphQL client connection to the Dagger API server. + + Uses a lazy and shared connection. + """ + + _instance: Self | None = None + _session: ClientSession | None = None + _params: ConnectParams | None = None + _cfg: ConnectConfig + + def __new__(cls): + if not cls._instance: + cls._instance = super().__new__(cls) + cls._cfg = ConnectConfig() + return cls._instance + + def __init__(self) -> None: + # This is a singleton class, so we don't want to initialize. + ... + + def with_params(self, params: ConnectParams) -> Self: + """Set the connection params.""" + if self._session: + logger.warning( + "Cannot set connection params after connection already started" + ) + else: + self._params = params + return self + + def with_config(self, cfg: ConnectConfig) -> Self: + """Set the connection config.""" + if self._session: + logger.warning( + "Cannot set connection config after connection already started" + ) + else: + self._cfg = cfg + return self + + @property + def session(self) -> ClientSession: + if not self._session: + logger.debug("Configuring shared connection to GraphQL server") + + # Delay checking the environment until we actually need it. + if not self._params: + self._params = ConnectParams.from_env() + + if not self._params: + msg = "No active engine session to connect to" + raise ClientConnectionError(msg) + + self._session = ClientSession(self._params, self._cfg) + return self._session + + def is_connected(self) -> bool: + return self._session is not None and self._session.has_session() + + async def close(self) -> None: + if self._session: + await super().close() + self._session = None diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/base.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/base.py new file mode 100644 index 0000000..2c6daaa --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/base.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import enum +import typing + +from typing_extensions import override + +if typing.TYPE_CHECKING: + from dagger.client._core import Context + from dagger.client._session import BaseConnection + + +class Scalar(str): + """Custom scalar.""" + + __slots__ = () + + +class Enum(enum.Enum): + """Custom enumeration.""" + + __slots__ = () + + def __str__(self) -> str: + """The string representation of the enum value.""" + return str(self.value) + + +class Object: + """Base for object types.""" + + __slots__ = () + + @classmethod + def _graphql_name(cls) -> str: + return cls.__name__ + + +class Input(Object): + """Input object type.""" + + __slots__ = () + + +class Type(Object): + """Object type.""" + + __slots__ = ("_ctx",) + + def __init__(self, ctx: Context): + self._ctx = ctx + + def __eq__(self, other) -> bool: + return ( + type(self) is type(other) + and self._graphql_name() == other._graphql_name() + and self._ctx == other._ctx + ) + + def __hash__(self) -> int: + return hash((type(self), self._graphql_name(), self._ctx)) + + def _select(self, *args, **kwargs): + return self._ctx.select(self._graphql_name(), *args, **kwargs) + + def _select_multiple(self, **kwargs): + return self._ctx.select_multiple(self._graphql_name(), **kwargs) + + +class Interface(Type): + """Dagger interface type.""" + + __slots__ = (*Type.__slots__, "_declaration") + + _declaration: type + + async def id(self) -> str: + """Get the ID of the underlying implementation.""" + return await self._select("id", []).execute(str) + + +class Root(Type): + """Top level query object type (a.k.a. Query).""" + + @override + def __init__(self, ctx: Context | None = None): + if ctx is None: + from ._core import Context + + ctx = Context() + + super().__init__(ctx) + + @classmethod + def from_connection(cls, conn: BaseConnection): + """Create a new instance of the root type, using the given connection.""" + from ._core import Context + + return cls(Context(conn)) + + @classmethod + def _graphql_name(cls) -> str: + return "Query" diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/client/gen.py b/content/en/docs/04/solution/ci/sdk/src/dagger/client/gen.py new file mode 100644 index 0000000..781503c --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/client/gen.py @@ -0,0 +1,9368 @@ +# Code generated by dagger. DO NOT EDIT. + +import warnings # noqa: F401 +from collections.abc import Callable +from dataclasses import dataclass + +from typing_extensions import Self + +from dagger.client._core import Arg +from dagger.client._guards import typecheck +from dagger.client.base import Enum, Input, Root, Scalar, Type + + +class BindingID(Scalar): + """The `BindingID` scalar type represents an identifier for an object + of type Binding.""" + + +class CacheVolumeID(Scalar): + """The `CacheVolumeID` scalar type represents an identifier for an + object of type CacheVolume.""" + + +class CloudID(Scalar): + """The `CloudID` scalar type represents an identifier for an object of + type Cloud.""" + + +class ContainerID(Scalar): + """The `ContainerID` scalar type represents an identifier for an + object of type Container.""" + + +class CurrentModuleID(Scalar): + """The `CurrentModuleID` scalar type represents an identifier for an + object of type CurrentModule.""" + + +class DirectoryID(Scalar): + """The `DirectoryID` scalar type represents an identifier for an + object of type Directory.""" + + +class EnumTypeDefID(Scalar): + """The `EnumTypeDefID` scalar type represents an identifier for an + object of type EnumTypeDef.""" + + +class EnumValueTypeDefID(Scalar): + """The `EnumValueTypeDefID` scalar type represents an identifier for + an object of type EnumValueTypeDef.""" + + +class EnvID(Scalar): + """The `EnvID` scalar type represents an identifier for an object of + type Env.""" + + +class EnvVariableID(Scalar): + """The `EnvVariableID` scalar type represents an identifier for an + object of type EnvVariable.""" + + +class ErrorID(Scalar): + """The `ErrorID` scalar type represents an identifier for an object of + type Error.""" + + +class ErrorValueID(Scalar): + """The `ErrorValueID` scalar type represents an identifier for an + object of type ErrorValue.""" + + +class FieldTypeDefID(Scalar): + """The `FieldTypeDefID` scalar type represents an identifier for an + object of type FieldTypeDef.""" + + +class FileID(Scalar): + """The `FileID` scalar type represents an identifier for an object of + type File.""" + + +class FunctionArgID(Scalar): + """The `FunctionArgID` scalar type represents an identifier for an + object of type FunctionArg.""" + + +class FunctionCallArgValueID(Scalar): + """The `FunctionCallArgValueID` scalar type represents an identifier + for an object of type FunctionCallArgValue.""" + + +class FunctionCallID(Scalar): + """The `FunctionCallID` scalar type represents an identifier for an + object of type FunctionCall.""" + + +class FunctionID(Scalar): + """The `FunctionID` scalar type represents an identifier for an object + of type Function.""" + + +class GeneratedCodeID(Scalar): + """The `GeneratedCodeID` scalar type represents an identifier for an + object of type GeneratedCode.""" + + +class GitRefID(Scalar): + """The `GitRefID` scalar type represents an identifier for an object + of type GitRef.""" + + +class GitRepositoryID(Scalar): + """The `GitRepositoryID` scalar type represents an identifier for an + object of type GitRepository.""" + + +class InputTypeDefID(Scalar): + """The `InputTypeDefID` scalar type represents an identifier for an + object of type InputTypeDef.""" + + +class InterfaceTypeDefID(Scalar): + """The `InterfaceTypeDefID` scalar type represents an identifier for + an object of type InterfaceTypeDef.""" + + +class JSON(Scalar): + """An arbitrary JSON-encoded value.""" + + +class LLMID(Scalar): + """The `LLMID` scalar type represents an identifier for an object of + type LLM.""" + + +class LLMTokenUsageID(Scalar): + """The `LLMTokenUsageID` scalar type represents an identifier for an + object of type LLMTokenUsage.""" + + +class LabelID(Scalar): + """The `LabelID` scalar type represents an identifier for an object of + type Label.""" + + +class ListTypeDefID(Scalar): + """The `ListTypeDefID` scalar type represents an identifier for an + object of type ListTypeDef.""" + + +class ModuleConfigClientID(Scalar): + """The `ModuleConfigClientID` scalar type represents an identifier for + an object of type ModuleConfigClient.""" + + +class ModuleID(Scalar): + """The `ModuleID` scalar type represents an identifier for an object + of type Module.""" + + +class ModuleSourceID(Scalar): + """The `ModuleSourceID` scalar type represents an identifier for an + object of type ModuleSource.""" + + +class ObjectTypeDefID(Scalar): + """The `ObjectTypeDefID` scalar type represents an identifier for an + object of type ObjectTypeDef.""" + + +class Platform(Scalar): + """The platform config OS and architecture in a Container. The format + is [os]/[platform]/[version] (e.g., "darwin/arm64/v7", + "windows/amd64", "linux/arm64"). """ + + +class PortID(Scalar): + """The `PortID` scalar type represents an identifier for an object of + type Port.""" + + +class SDKConfigID(Scalar): + """The `SDKConfigID` scalar type represents an identifier for an + object of type SDKConfig.""" + + +class ScalarTypeDefID(Scalar): + """The `ScalarTypeDefID` scalar type represents an identifier for an + object of type ScalarTypeDef.""" + + +class SecretID(Scalar): + """The `SecretID` scalar type represents an identifier for an object + of type Secret.""" + + +class ServiceID(Scalar): + """The `ServiceID` scalar type represents an identifier for an object + of type Service.""" + + +class SocketID(Scalar): + """The `SocketID` scalar type represents an identifier for an object + of type Socket.""" + + +class SourceMapID(Scalar): + """The `SourceMapID` scalar type represents an identifier for an + object of type SourceMap.""" + + +class TerminalID(Scalar): + """The `TerminalID` scalar type represents an identifier for an object + of type Terminal.""" + + +class TrivyID(Scalar): + """The `TrivyID` scalar type represents an identifier for an object of + type Trivy.""" + + +class TrivyScanID(Scalar): + """The `TrivyScanID` scalar type represents an identifier for an + object of type TrivyScan.""" + + +class TypeDefID(Scalar): + """The `TypeDefID` scalar type represents an identifier for an object + of type TypeDef.""" + + +class Void(Scalar): + """The absence of a value. A Null Void is used as a placeholder for + resolvers that do not return anything. """ + + +class CacheSharingMode(Enum): + """Sharing mode of the cache volume.""" + + LOCKED = "LOCKED" + """Shares the cache volume amongst many build pipelines, but will serialize the writes""" + + PRIVATE = "PRIVATE" + """Keeps a cache volume for a single build pipeline""" + + SHARED = "SHARED" + """Shares the cache volume amongst many build pipelines""" + + +class ImageLayerCompression(Enum): + """Compression algorithm to use for image layers.""" + + ESTARGZ = "EStarGZ" + + EStarGZ = "EStarGZ" + + GZIP = "Gzip" + + Gzip = "Gzip" + + UNCOMPRESSED = "Uncompressed" + + Uncompressed = "Uncompressed" + + ZSTD = "Zstd" + + Zstd = "Zstd" + + +class ImageMediaTypes(Enum): + """Mediatypes to use in published or exported image metadata.""" + + DOCKER = "DockerMediaTypes" + + DockerMediaTypes = "DockerMediaTypes" + + OCI = "OCIMediaTypes" + + OCIMediaTypes = "OCIMediaTypes" + + +class ModuleSourceKind(Enum): + """The kind of module source.""" + + DIR = "DIR_SOURCE" + + DIR_SOURCE = "DIR_SOURCE" + + GIT = "GIT_SOURCE" + + GIT_SOURCE = "GIT_SOURCE" + + LOCAL = "LOCAL_SOURCE" + + LOCAL_SOURCE = "LOCAL_SOURCE" + + +class NetworkProtocol(Enum): + """Transport layer network protocol associated to a port.""" + + TCP = "TCP" + + UDP = "UDP" + + +class ReturnType(Enum): + """Expected return type of an execution""" + + ANY = "ANY" + """Any execution (exit codes 0-127)""" + + FAILURE = "FAILURE" + """A failed execution (exit codes 1-127)""" + + SUCCESS = "SUCCESS" + """A successful execution (exit code 0)""" + + +class TypeDefKind(Enum): + """Distinguishes the different kinds of TypeDefs.""" + + BOOLEAN = "BOOLEAN_KIND" + """A boolean value.""" + + BOOLEAN_KIND = "BOOLEAN_KIND" + """A boolean value.""" + + ENUM = "ENUM_KIND" + """A GraphQL enum type and its values + + Always paired with an EnumTypeDef. + """ + + ENUM_KIND = "ENUM_KIND" + """A GraphQL enum type and its values + + Always paired with an EnumTypeDef. + """ + + FLOAT = "FLOAT_KIND" + """A float value.""" + + FLOAT_KIND = "FLOAT_KIND" + """A float value.""" + + INPUT = "INPUT_KIND" + """A graphql input type, used only when representing the core API via TypeDefs.""" + + INPUT_KIND = "INPUT_KIND" + """A graphql input type, used only when representing the core API via TypeDefs.""" + + INTEGER = "INTEGER_KIND" + """An integer value.""" + + INTEGER_KIND = "INTEGER_KIND" + """An integer value.""" + + INTERFACE = "INTERFACE_KIND" + """Always paired with an InterfaceTypeDef. + + A named type of functions that can be matched+implemented by other objects+interfaces. + """ + + INTERFACE_KIND = "INTERFACE_KIND" + """Always paired with an InterfaceTypeDef. + + A named type of functions that can be matched+implemented by other objects+interfaces. + """ + + LIST = "LIST_KIND" + """Always paired with a ListTypeDef. + + A list of values all having the same type. + """ + + LIST_KIND = "LIST_KIND" + """Always paired with a ListTypeDef. + + A list of values all having the same type. + """ + + OBJECT = "OBJECT_KIND" + """Always paired with an ObjectTypeDef. + + A named type defined in the GraphQL schema, with fields and functions. + """ + + OBJECT_KIND = "OBJECT_KIND" + """Always paired with an ObjectTypeDef. + + A named type defined in the GraphQL schema, with fields and functions. + """ + + SCALAR = "SCALAR_KIND" + """A scalar value of any basic kind.""" + + SCALAR_KIND = "SCALAR_KIND" + """A scalar value of any basic kind.""" + + STRING = "STRING_KIND" + """A string value.""" + + STRING_KIND = "STRING_KIND" + """A string value.""" + + VOID = "VOID_KIND" + """A special kind used to signify that no value is returned. + + This is used for functions that have no return value. The outer TypeDef specifying this Kind is always Optional, as the Void is never actually represented. + """ + + VOID_KIND = "VOID_KIND" + """A special kind used to signify that no value is returned. + + This is used for functions that have no return value. The outer TypeDef specifying this Kind is always Optional, as the Void is never actually represented. + """ + + +@typecheck +@dataclass(slots=True) +class BuildArg(Input): + """Key value object that represents a build argument.""" + + name: str + """The build argument name.""" + + value: str + """The build argument value.""" + + +@typecheck +@dataclass(slots=True) +class PipelineLabel(Input): + """Key value object that represents a pipeline label.""" + + name: str + """Label name.""" + + value: str + """Label value.""" + + +@typecheck +@dataclass(slots=True) +class PortForward(Input): + """Port forwarding rules for tunneling network traffic.""" + + backend: int + """Destination port for traffic.""" + + frontend: int | None = None + """Port to expose to clients. If unspecified, a default will be chosen.""" + + protocol: NetworkProtocol | None = NetworkProtocol.TCP + """Transport layer protocol to use for traffic.""" + + +@typecheck +class Binding(Type): + + def as_cache_volume(self) -> "CacheVolume": + """Retrieve the binding value, as type CacheVolume""" + _args: list[Arg] = [] + _ctx = self._select("asCacheVolume", _args) + return CacheVolume(_ctx) + + def as_cloud(self) -> "Cloud": + """Retrieve the binding value, as type Cloud""" + _args: list[Arg] = [] + _ctx = self._select("asCloud", _args) + return Cloud(_ctx) + + def as_container(self) -> "Container": + """Retrieve the binding value, as type Container""" + _args: list[Arg] = [] + _ctx = self._select("asContainer", _args) + return Container(_ctx) + + def as_directory(self) -> "Directory": + """Retrieve the binding value, as type Directory""" + _args: list[Arg] = [] + _ctx = self._select("asDirectory", _args) + return Directory(_ctx) + + def as_env(self) -> "Env": + """Retrieve the binding value, as type Env""" + _args: list[Arg] = [] + _ctx = self._select("asEnv", _args) + return Env(_ctx) + + def as_file(self) -> "File": + """Retrieve the binding value, as type File""" + _args: list[Arg] = [] + _ctx = self._select("asFile", _args) + return File(_ctx) + + def as_git_ref(self) -> "GitRef": + """Retrieve the binding value, as type GitRef""" + _args: list[Arg] = [] + _ctx = self._select("asGitRef", _args) + return GitRef(_ctx) + + def as_git_repository(self) -> "GitRepository": + """Retrieve the binding value, as type GitRepository""" + _args: list[Arg] = [] + _ctx = self._select("asGitRepository", _args) + return GitRepository(_ctx) + + def as_llm(self) -> "LLM": + """Retrieve the binding value, as type LLM""" + _args: list[Arg] = [] + _ctx = self._select("asLLM", _args) + return LLM(_ctx) + + def as_module(self) -> "Module": + """Retrieve the binding value, as type Module""" + _args: list[Arg] = [] + _ctx = self._select("asModule", _args) + return Module(_ctx) + + def as_module_config_client(self) -> "ModuleConfigClient": + """Retrieve the binding value, as type ModuleConfigClient""" + _args: list[Arg] = [] + _ctx = self._select("asModuleConfigClient", _args) + return ModuleConfigClient(_ctx) + + def as_module_source(self) -> "ModuleSource": + """Retrieve the binding value, as type ModuleSource""" + _args: list[Arg] = [] + _ctx = self._select("asModuleSource", _args) + return ModuleSource(_ctx) + + def as_secret(self) -> "Secret": + """Retrieve the binding value, as type Secret""" + _args: list[Arg] = [] + _ctx = self._select("asSecret", _args) + return Secret(_ctx) + + def as_service(self) -> "Service": + """Retrieve the binding value, as type Service""" + _args: list[Arg] = [] + _ctx = self._select("asService", _args) + return Service(_ctx) + + def as_socket(self) -> "Socket": + """Retrieve the binding value, as type Socket""" + _args: list[Arg] = [] + _ctx = self._select("asSocket", _args) + return Socket(_ctx) + + async def as_string(self) -> str | None: + """The binding's string value + + Returns + ------- + str | None + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("asString", _args) + return await _ctx.execute(str | None) + + def as_trivy(self) -> "Trivy": + """Retrieve the binding value, as type Trivy""" + _args: list[Arg] = [] + _ctx = self._select("asTrivy", _args) + return Trivy(_ctx) + + def as_trivy_scan(self) -> "TrivyScan": + """Retrieve the binding value, as type TrivyScan""" + _args: list[Arg] = [] + _ctx = self._select("asTrivyScan", _args) + return TrivyScan(_ctx) + + async def digest(self) -> str: + """The digest of the binding value + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("digest", _args) + return await _ctx.execute(str) + + async def id(self) -> BindingID: + """A unique identifier for this Binding. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + BindingID + The `BindingID` scalar type represents an identifier for an object + of type Binding. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(BindingID) + + async def is_null(self) -> bool: + """Returns true if the binding is null + + Returns + ------- + bool + The `Boolean` scalar type represents `true` or `false`. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("isNull", _args) + return await _ctx.execute(bool) + + async def name(self) -> str: + """The binding name + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def type_name(self) -> str: + """The binding type + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("typeName", _args) + return await _ctx.execute(str) + + +@typecheck +class CacheVolume(Type): + """A directory whose contents persist across runs.""" + + async def id(self) -> CacheVolumeID: + """A unique identifier for this CacheVolume. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + CacheVolumeID + The `CacheVolumeID` scalar type represents an identifier for an + object of type CacheVolume. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(CacheVolumeID) + + +@typecheck +class Cloud(Type): + """Dagger Cloud configuration and state""" + + async def id(self) -> CloudID: + """A unique identifier for this Cloud. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + CloudID + The `CloudID` scalar type represents an identifier for an object + of type Cloud. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(CloudID) + + async def trace_url(self) -> str: + """The trace URL for the current session + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("traceURL", _args) + return await _ctx.execute(str) + + +@typecheck +class Container(Type): + """An OCI-compatible container, also known as a Docker container.""" + + def as_service(self, *, args: list[str] | None = None, use_entrypoint: bool | None = False, experimental_privileged_nesting: bool | None = False, insecure_root_capabilities: bool | None = False, expand: bool | None = False, no_init: bool | None = False,) -> "Service": + """Turn the container into a Service. + + Be sure to set any exposed ports before this conversion. + + Parameters + ---------- + args: + Command to run instead of the container's default command (e.g., + ["go", "run", "main.go"]). + If empty, the container's default command is used. + use_entrypoint: + If the container has an entrypoint, prepend it to the args. + experimental_privileged_nesting: + Provides Dagger access to the executed command. + insecure_root_capabilities: + Execute the command with all root capabilities. This is similar to + running a command with "sudo" or executing "docker run" with the " + --privileged" flag. Containerization does not provide any security + guarantees when using this option. It should only be used when + absolutely necessary and only with trusted commands. + expand: + Replace "${VAR}" or "$VAR" in the args according to the current + environment variables defined in the container (e.g. "/$VAR/foo"). + no_init: + If set, skip the automatic init process injected into containers + by default. + This should only be used if the user requires that their exec + process be the pid 1 process in the container. Otherwise it may + result in unexpected behavior. + """ + _args = [ + Arg("args", () if args is None else args, ()), + Arg("useEntrypoint", use_entrypoint, False), + Arg("experimentalPrivilegedNesting", experimental_privileged_nesting, False), + Arg("insecureRootCapabilities", insecure_root_capabilities, False), + Arg("expand", expand, False), + Arg("noInit", no_init, False), + ] + _ctx = self._select("asService", _args) + return Service(_ctx) + + def as_tarball(self, *, platform_variants: "list[Container] | None" = None, forced_compression: ImageLayerCompression | None = None, media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes,) -> "File": + """Package the container state as an OCI image, and return it as a tar + archive + + Parameters + ---------- + platform_variants: + Identifiers for other platform specific containers. + Used for multi-platform images. + forced_compression: + Force each layer of the image to use the specified compression + algorithm. + If this is unset, then if a layer already has a compressed blob in + the engine's cache, that will be used (this can result in a mix of + compression algorithms for different layers). If this is unset and + a layer has no compressed blob in the engine's cache, then it will + be compressed using Gzip. + media_types: + Use the specified media types for the image's layers. + Defaults to OCI, which is largely compatible with most recent + container runtimes, but Docker may be needed for older runtimes + without OCI support. + """ + _args = [ + Arg("platformVariants", () if platform_variants is None else platform_variants, ()), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("asTarball", _args) + return File(_ctx) + + def build(self, context: "Directory", *, dockerfile: str | None = "Dockerfile", target: str | None = "", build_args: list[BuildArg] | None = None, secrets: "list[Secret] | None" = None, no_init: bool | None = False,) -> Self: + """Initializes this container from a Dockerfile build. + + Parameters + ---------- + context: + Directory context used by the Dockerfile. + dockerfile: + Path to the Dockerfile to use. + target: + Target build stage to build. + build_args: + Additional build arguments. + secrets: + Secrets to pass to the build. + They will be mounted at /run/secrets/[secret-name] in the build + container + They can be accessed in the Dockerfile using the "secret" mount + type and mount path /run/secrets/[secret-name], e.g. RUN + --mount=type=secret,id=my-secret curl + [http://example.com?token=$(cat /run/secrets/my- + secret)](http://example.com?token=$(cat /run/secrets/my-secret)) + no_init: + If set, skip the automatic init process injected into containers + created by RUN statements. + This should only be used if the user requires that their exec + processes be the pid 1 process in the container. Otherwise it may + result in unexpected behavior. + """ + _args = [ + Arg("context", context), + Arg("dockerfile", dockerfile, "Dockerfile"), + Arg("target", target, ""), + Arg("buildArgs", () if build_args is None else build_args, ()), + Arg("secrets", () if secrets is None else secrets, ()), + Arg("noInit", no_init, False), + ] + _ctx = self._select("build", _args) + return Container(_ctx) + + async def default_args(self) -> list[str]: + """Return the container's default arguments. + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("defaultArgs", _args) + return await _ctx.execute(list[str]) + + def directory(self, path: str, *, expand: bool | None = False,) -> "Directory": + """Retrieve a directory from the container's root filesystem + + Mounts are included. + + Parameters + ---------- + path: + The path of the directory to retrieve (e.g., "./src"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + async def entrypoint(self) -> list[str]: + """Return the container's OCI entrypoint. + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("entrypoint", _args) + return await _ctx.execute(list[str]) + + async def env_variable(self, name: str) -> str | None: + """Retrieves the value of the specified environment variable. + + Parameters + ---------- + name: + The name of the environment variable to retrieve (e.g., "PATH"). + + Returns + ------- + str | None + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("envVariable", _args) + return await _ctx.execute(str | None) + + async def env_variables(self) -> list["EnvVariable"]: + """Retrieves the list of environment variables passed to commands.""" + _args: list[Arg] = [] + _ctx = self._select("envVariables", _args) + return await _ctx.execute_object_list(EnvVariable) + + async def exit_code(self) -> int: + """The exit code of the last executed command + + Returns an error if no command was executed + + Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("exitCode", _args) + return await _ctx.execute(int) + + def experimental_with_all_gp_us(self) -> Self: + """EXPERIMENTAL API! Subject to change/removal at any time. + + Configures all available GPUs on the host to be accessible to this + container. + + This currently works for Nvidia devices only. + """ + _args: list[Arg] = [] + _ctx = self._select("experimentalWithAllGPUs", _args) + return Container(_ctx) + + def experimental_with_gpu(self, devices: list[str]) -> Self: + """EXPERIMENTAL API! Subject to change/removal at any time. + + Configures the provided list of devices to be accessible to this + container. + + This currently works for Nvidia devices only. + + Parameters + ---------- + devices: + List of devices to be accessible to this container. + """ + _args = [ + Arg("devices", devices), + ] + _ctx = self._select("experimentalWithGPU", _args) + return Container(_ctx) + + async def export(self, path: str, *, platform_variants: "list[Container] | None" = None, forced_compression: ImageLayerCompression | None = None, media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes, expand: bool | None = False,) -> str: + """Writes the container as an OCI tarball to the destination file path on + the host. + + It can also export platform variants. + + Parameters + ---------- + path: + Host's destination path (e.g., "./tarball"). + Path can be relative to the engine's workdir or absolute. + platform_variants: + Identifiers for other platform specific containers. + Used for multi-platform image. + forced_compression: + Force each layer of the exported image to use the specified + compression algorithm. + If this is unset, then if a layer already has a compressed blob in + the engine's cache, that will be used (this can result in a mix of + compression algorithms for different layers). If this is unset and + a layer has no compressed blob in the engine's cache, then it will + be compressed using Gzip. + media_types: + Use the specified media types for the exported image's layers. + Defaults to OCI, which is largely compatible with most recent + container runtimes, but Docker may be needed for older runtimes + without OCI support. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("path", path), + Arg("platformVariants", () if platform_variants is None else platform_variants, ()), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + Arg("expand", expand, False), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + async def exposed_ports(self) -> list["Port"]: + """Retrieves the list of exposed ports. + + This includes ports already exposed by the image, even if not + explicitly added with dagger. + """ + _args: list[Arg] = [] + _ctx = self._select("exposedPorts", _args) + return await _ctx.execute_object_list(Port) + + def file(self, path: str, *, expand: bool | None = False,) -> "File": + """Retrieves a file at the given path. + + Mounts are included. + + Parameters + ---------- + path: + The path of the file to retrieve (e.g., "./README.md"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + def from_(self, address: str) -> Self: + """Download a container image, and apply it to the container state. All + previous state will be lost. + + Parameters + ---------- + address: + Address of the container image to download, in standard OCI ref + format. Example:"registry.dagger.io/engine:latest" + """ + _args = [ + Arg("address", address), + ] + _ctx = self._select("from", _args) + return Container(_ctx) + + async def id(self) -> ContainerID: + """A unique identifier for this Container. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ContainerID + The `ContainerID` scalar type represents an identifier for an + object of type Container. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ContainerID) + + async def image_ref(self) -> str: + """The unique image reference which can only be retrieved immediately + after the 'Container.From' call. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("imageRef", _args) + return await _ctx.execute(str) + + def import_(self, source: "File", *, tag: str | None = "",) -> Self: + """Reads the container from an OCI tarball. + + Parameters + ---------- + source: + File to read the container from. + tag: + Identifies the tag to import from the archive, if the archive + bundles multiple tags. + """ + _args = [ + Arg("source", source), + Arg("tag", tag, ""), + ] + _ctx = self._select("import", _args) + return Container(_ctx) + + async def label(self, name: str) -> str | None: + """Retrieves the value of the specified label. + + Parameters + ---------- + name: + The name of the label (e.g., + "org.opencontainers.artifact.created"). + + Returns + ------- + str | None + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("label", _args) + return await _ctx.execute(str | None) + + async def labels(self) -> list["Label"]: + """Retrieves the list of labels passed to container.""" + _args: list[Arg] = [] + _ctx = self._select("labels", _args) + return await _ctx.execute_object_list(Label) + + async def mounts(self) -> list[str]: + """Retrieves the list of paths where a directory is mounted. + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("mounts", _args) + return await _ctx.execute(list[str]) + + async def platform(self) -> Platform: + """The platform this container executes and publishes as. + + Returns + ------- + Platform + The platform config OS and architecture in a Container. The + format is [os]/[platform]/[version] (e.g., "darwin/arm64/v7", + "windows/amd64", "linux/arm64"). + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("platform", _args) + return await _ctx.execute(Platform) + + async def publish(self, address: str, *, platform_variants: "list[Container] | None" = None, forced_compression: ImageLayerCompression | None = None, media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes,) -> str: + """Package the container state as an OCI image, and publish it to a + registry + + Returns the fully qualified address of the published image, with + digest + + Parameters + ---------- + address: + The OCI address to publish to + Same format as "docker push". Example: + "registry.example.com/user/repo:tag" + platform_variants: + Identifiers for other platform specific containers. + Used for multi-platform image. + forced_compression: + Force each layer of the published image to use the specified + compression algorithm. + If this is unset, then if a layer already has a compressed blob in + the engine's cache, that will be used (this can result in a mix of + compression algorithms for different layers). If this is unset and + a layer has no compressed blob in the engine's cache, then it will + be compressed using Gzip. + media_types: + Use the specified media types for the published image's layers. + Defaults to "OCI", which is compatible with most recent + registries, but "Docker" may be needed for older registries + without OCI support. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("address", address), + Arg("platformVariants", () if platform_variants is None else platform_variants, ()), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("publish", _args) + return await _ctx.execute(str) + + def rootfs(self) -> "Directory": + """Return a snapshot of the container's root filesystem. The snapshot can + be modified then written back using withRootfs. Use that method for + filesystem modifications. + """ + _args: list[Arg] = [] + _ctx = self._select("rootfs", _args) + return Directory(_ctx) + + async def stderr(self) -> str: + """The buffered standard error stream of the last executed command + + Returns an error if no command was executed + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("stderr", _args) + return await _ctx.execute(str) + + async def stdout(self) -> str: + """The buffered standard output stream of the last executed command + + Returns an error if no command was executed + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("stdout", _args) + return await _ctx.execute(str) + + async def sync(self) -> Self: + """Forces evaluation of the pipeline in the engine. + + It doesn't run the default command if no exec has been set. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + def terminal(self, *, cmd: list[str] | None = None, experimental_privileged_nesting: bool | None = False, insecure_root_capabilities: bool | None = False,) -> Self: + """Opens an interactive terminal for this container using its configured + default terminal command if not overridden by args (or sh as a + fallback default). + + Parameters + ---------- + cmd: + If set, override the container's default terminal command and + invoke these command arguments instead. + experimental_privileged_nesting: + Provides Dagger access to the executed command. + insecure_root_capabilities: + Execute the command with all root capabilities. This is similar to + running a command with "sudo" or executing "docker run" with the " + --privileged" flag. Containerization does not provide any security + guarantees when using this option. It should only be used when + absolutely necessary and only with trusted commands. + """ + _args = [ + Arg("cmd", () if cmd is None else cmd, ()), + Arg("experimentalPrivilegedNesting", experimental_privileged_nesting, False), + Arg("insecureRootCapabilities", insecure_root_capabilities, False), + ] + _ctx = self._select("terminal", _args) + return Container(_ctx) + + async def up(self, *, random: bool | None = False, ports: list[PortForward] | None = None, args: list[str] | None = None, use_entrypoint: bool | None = False, experimental_privileged_nesting: bool | None = False, insecure_root_capabilities: bool | None = False, expand: bool | None = False, no_init: bool | None = False,) -> Void | None: + """Starts a Service and creates a tunnel that forwards traffic from the + caller's network to that service. + + Be sure to set any exposed ports before calling this api. + + Parameters + ---------- + random: + Bind each tunnel port to a random port on the host. + ports: + List of frontend/backend port mappings to forward. + Frontend is the port accepting traffic on the host, backend is the + service port. + args: + Command to run instead of the container's default command (e.g., + ["go", "run", "main.go"]). + If empty, the container's default command is used. + use_entrypoint: + If the container has an entrypoint, prepend it to the args. + experimental_privileged_nesting: + Provides Dagger access to the executed command. + insecure_root_capabilities: + Execute the command with all root capabilities. This is similar to + running a command with "sudo" or executing "docker run" with the " + --privileged" flag. Containerization does not provide any security + guarantees when using this option. It should only be used when + absolutely necessary and only with trusted commands. + expand: + Replace "${VAR}" or "$VAR" in the args according to the current + environment variables defined in the container (e.g. "/$VAR/foo"). + no_init: + If set, skip the automatic init process injected into containers + by default. + This should only be used if the user requires that their exec + process be the pid 1 process in the container. Otherwise it may + result in unexpected behavior. + + Returns + ------- + Void | None + The absence of a value. A Null Void is used as a placeholder for + resolvers that do not return anything. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("random", random, False), + Arg("ports", () if ports is None else ports, ()), + Arg("args", () if args is None else args, ()), + Arg("useEntrypoint", use_entrypoint, False), + Arg("experimentalPrivilegedNesting", experimental_privileged_nesting, False), + Arg("insecureRootCapabilities", insecure_root_capabilities, False), + Arg("expand", expand, False), + Arg("noInit", no_init, False), + ] + _ctx = self._select("up", _args) + await _ctx.execute() + + async def user(self) -> str: + """Retrieves the user to be set for all commands. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("user", _args) + return await _ctx.execute(str) + + def with_annotation(self, name: str, value: str) -> Self: + """Retrieves this container plus the given OCI anotation. + + Parameters + ---------- + name: + The name of the annotation. + value: + The value of the annotation. + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withAnnotation", _args) + return Container(_ctx) + + def with_default_args(self, args: list[str]) -> Self: + """Configures default arguments for future commands. Like CMD in + Dockerfile. + + Parameters + ---------- + args: + Arguments to prepend to future executions (e.g., ["-v", "--no- + cache"]). + """ + _args = [ + Arg("args", args), + ] + _ctx = self._select("withDefaultArgs", _args) + return Container(_ctx) + + def with_default_terminal_cmd(self, args: list[str], *, experimental_privileged_nesting: bool | None = False, insecure_root_capabilities: bool | None = False,) -> Self: + """Set the default command to invoke for the container's terminal API. + + Parameters + ---------- + args: + The args of the command. + experimental_privileged_nesting: + Provides Dagger access to the executed command. + insecure_root_capabilities: + Execute the command with all root capabilities. This is similar to + running a command with "sudo" or executing "docker run" with the " + --privileged" flag. Containerization does not provide any security + guarantees when using this option. It should only be used when + absolutely necessary and only with trusted commands. + """ + _args = [ + Arg("args", args), + Arg("experimentalPrivilegedNesting", experimental_privileged_nesting, False), + Arg("insecureRootCapabilities", insecure_root_capabilities, False), + ] + _ctx = self._select("withDefaultTerminalCmd", _args) + return Container(_ctx) + + def with_directory(self, path: str, directory: "Directory", *, exclude: list[str] | None = None, include: list[str] | None = None, owner: str | None = "", expand: bool | None = False,) -> Self: + """Return a new container snapshot, with a directory added to its + filesystem + + Parameters + ---------- + path: + Location of the written directory (e.g., "/tmp/directory"). + directory: + Identifier of the directory to write + exclude: + Patterns to exclude in the written directory (e.g. + ["node_modules/**", ".gitignore", ".git/"]). + include: + Patterns to include in the written directory (e.g. ["*.go", + "go.mod", "go.sum"]). + owner: + A user:group to set for the directory and its contents. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("directory", directory), + Arg("exclude", () if exclude is None else exclude, ()), + Arg("include", () if include is None else include, ()), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withDirectory", _args) + return Container(_ctx) + + def with_entrypoint(self, args: list[str], *, keep_default_args: bool | None = False,) -> Self: + """Set an OCI-style entrypoint. It will be included in the container's + OCI configuration. Note, withExec ignores the entrypoint by default. + + Parameters + ---------- + args: + Arguments of the entrypoint. Example: ["go", "run"]. + keep_default_args: + Don't reset the default arguments when setting the entrypoint. By + default it is reset, since entrypoint and default args are often + tightly coupled. + """ + _args = [ + Arg("args", args), + Arg("keepDefaultArgs", keep_default_args, False), + ] + _ctx = self._select("withEntrypoint", _args) + return Container(_ctx) + + def with_env_variable(self, name: str, value: str, *, expand: bool | None = False,) -> Self: + """Set a new environment variable in the container. + + Parameters + ---------- + name: + Name of the environment variable (e.g., "HOST"). + value: + Value of the environment variable. (e.g., "localhost"). + expand: + Replace "${VAR}" or "$VAR" in the value according to the current + environment variables defined in the container (e.g. + "/opt/bin:$PATH"). + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("expand", expand, False), + ] + _ctx = self._select("withEnvVariable", _args) + return Container(_ctx) + + def with_exec(self, args: list[str], *, use_entrypoint: bool | None = False, stdin: str | None = "", redirect_stdout: str | None = "", redirect_stderr: str | None = "", expect: ReturnType | None = ReturnType.SUCCESS, experimental_privileged_nesting: bool | None = False, insecure_root_capabilities: bool | None = False, expand: bool | None = False, no_init: bool | None = False,) -> Self: + """Execute a command in the container, and return a new snapshot of the + container state after execution. + + Parameters + ---------- + args: + Command to execute. Must be valid exec() arguments, not a shell + command. Example: ["go", "run", "main.go"]. + To run a shell command, execute the shell and pass the shell + command as argument. Example: ["sh", "-c", "ls -l | grep foo"] + Defaults to the container's default arguments (see "defaultArgs" + and "withDefaultArgs"). + use_entrypoint: + Apply the OCI entrypoint, if present, by prepending it to the + args. Ignored by default. + stdin: + Content to write to the command's standard input. Example: "Hello + world") + redirect_stdout: + Redirect the command's standard output to a file in the container. + Example: "./stdout.txt" + redirect_stderr: + Like redirectStdout, but for standard error + expect: + Exit codes this command is allowed to exit with without error + experimental_privileged_nesting: + Provides Dagger access to the executed command. + insecure_root_capabilities: + Execute the command with all root capabilities. Like --privileged + in Docker + DANGER: this grants the command full access to the host system. + Only use when 1) you trust the command being executed and 2) you + specifically need this level of access. + expand: + Replace "${VAR}" or "$VAR" in the args according to the current + environment variables defined in the container (e.g. "/$VAR/foo"). + no_init: + Skip the automatic init process injected into containers by + default. + Only use this if you specifically need the command to be pid 1 in + the container. Otherwise it may result in unexpected behavior. If + you're not sure, you don't need this. + """ + _args = [ + Arg("args", args), + Arg("useEntrypoint", use_entrypoint, False), + Arg("stdin", stdin, ""), + Arg("redirectStdout", redirect_stdout, ""), + Arg("redirectStderr", redirect_stderr, ""), + Arg("expect", expect, ReturnType.SUCCESS), + Arg("experimentalPrivilegedNesting", experimental_privileged_nesting, False), + Arg("insecureRootCapabilities", insecure_root_capabilities, False), + Arg("expand", expand, False), + Arg("noInit", no_init, False), + ] + _ctx = self._select("withExec", _args) + return Container(_ctx) + + def with_exposed_port(self, port: int, *, protocol: NetworkProtocol | None = NetworkProtocol.TCP, description: str | None = None, experimental_skip_healthcheck: bool | None = False,) -> Self: + """Expose a network port. Like EXPOSE in Dockerfile (but with healthcheck + support) + + Exposed ports serve two purposes: + + - For health checks and introspection, when running services + + - For setting the EXPOSE OCI field when publishing the container + + Parameters + ---------- + port: + Port number to expose. Example: 8080 + protocol: + Network protocol. Example: "tcp" + description: + Port description. Example: "payment API endpoint" + experimental_skip_healthcheck: + Skip the health check when run as a service. + """ + _args = [ + Arg("port", port), + Arg("protocol", protocol, NetworkProtocol.TCP), + Arg("description", description, None), + Arg("experimentalSkipHealthcheck", experimental_skip_healthcheck, False), + ] + _ctx = self._select("withExposedPort", _args) + return Container(_ctx) + + def with_file(self, path: str, source: "File", *, permissions: int | None = None, owner: str | None = "", expand: bool | None = False,) -> Self: + """Return a container snapshot with a file added + + Parameters + ---------- + path: + Path of the new file. Example: "/path/to/new-file.txt" + source: + File to add + permissions: + Permissions of the new file. Example: 0600 + owner: + A user:group to set for the file. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("permissions", permissions, None), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withFile", _args) + return Container(_ctx) + + def with_files(self, path: str, sources: list["File"], *, permissions: int | None = None, owner: str | None = "", expand: bool | None = False,) -> Self: + """Retrieves this container plus the contents of the given files copied + to the given path. + + Parameters + ---------- + path: + Location where copied files should be placed (e.g., "/src"). + sources: + Identifiers of the files to copy. + permissions: + Permission given to the copied files (e.g., 0600). + owner: + A user:group to set for the files. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("path", path), + Arg("sources", sources), + Arg("permissions", permissions, None), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withFiles", _args) + return Container(_ctx) + + def with_label(self, name: str, value: str) -> Self: + """Retrieves this container plus the given label. + + Parameters + ---------- + name: + The name of the label (e.g., + "org.opencontainers.artifact.created"). + value: + The value of the label (e.g., "2023-01-01T00:00:00Z"). + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withLabel", _args) + return Container(_ctx) + + def with_mounted_cache(self, path: str, cache: CacheVolume, *, source: "Directory | None" = None, sharing: CacheSharingMode | None = CacheSharingMode.SHARED, owner: str | None = "", expand: bool | None = False,) -> Self: + """Retrieves this container plus a cache volume mounted at the given + path. + + Parameters + ---------- + path: + Location of the cache directory (e.g., "/root/.npm"). + cache: + Identifier of the cache volume to mount. + source: + Identifier of the directory to use as the cache volume's root. + sharing: + Sharing mode of the cache volume. + owner: + A user:group to set for the mounted cache directory. + Note that this changes the ownership of the specified mount along + with the initial filesystem provided by source (if any). It does + not have any effect if/when the cache has already been created. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("cache", cache), + Arg("source", source, None), + Arg("sharing", sharing, CacheSharingMode.SHARED), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedCache", _args) + return Container(_ctx) + + def with_mounted_directory(self, path: str, source: "Directory", *, owner: str | None = "", expand: bool | None = False,) -> Self: + """Retrieves this container plus a directory mounted at the given path. + + Parameters + ---------- + path: + Location of the mounted directory (e.g., "/mnt/directory"). + source: + Identifier of the mounted directory. + owner: + A user:group to set for the mounted directory and its contents. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedDirectory", _args) + return Container(_ctx) + + def with_mounted_file(self, path: str, source: "File", *, owner: str | None = "", expand: bool | None = False,) -> Self: + """Retrieves this container plus a file mounted at the given path. + + Parameters + ---------- + path: + Location of the mounted file (e.g., "/tmp/file.txt"). + source: + Identifier of the mounted file. + owner: + A user or user:group to set for the mounted file. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedFile", _args) + return Container(_ctx) + + def with_mounted_secret(self, path: str, source: "Secret", *, owner: str | None = "", mode: int | None = 256, expand: bool | None = False,) -> Self: + """Retrieves this container plus a secret mounted into a file at the + given path. + + Parameters + ---------- + path: + Location of the secret file (e.g., "/tmp/secret.txt"). + source: + Identifier of the secret to mount. + owner: + A user:group to set for the mounted secret. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + mode: + Permission given to the mounted secret (e.g., 0600). + This option requires an owner to be set to be active. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("owner", owner, ""), + Arg("mode", mode, 256), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedSecret", _args) + return Container(_ctx) + + def with_mounted_temp(self, path: str, *, size: int | None = None, expand: bool | None = False,) -> Self: + """Retrieves this container plus a temporary directory mounted at the + given path. Any writes will be ephemeral to a single withExec call; + they will not be persisted to subsequent withExecs. + + Parameters + ---------- + path: + Location of the temporary directory (e.g., "/tmp/temp_dir"). + size: + Size of the temporary directory in bytes. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("size", size, None), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedTemp", _args) + return Container(_ctx) + + def with_new_file(self, path: str, contents: str, *, permissions: int | None = 420, owner: str | None = "", expand: bool | None = False,) -> Self: + """Return a new container snapshot, with a file added to its filesystem + with text content + + Parameters + ---------- + path: + Path of the new file. May be relative or absolute. Example: + "README.md" or "/etc/profile" + contents: + Contents of the new file. Example: "Hello world!" + permissions: + Permissions of the new file. Example: 0600 + owner: + A user:group to set for the file. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("path", path), + Arg("contents", contents), + Arg("permissions", permissions, 420), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withNewFile", _args) + return Container(_ctx) + + def with_registry_auth(self, address: str, username: str, secret: "Secret",) -> Self: + """Attach credentials for future publishing to a registry. Use in + combination with publish + + Parameters + ---------- + address: + The image address that needs authentication. Same format as + "docker push". Example: "registry.dagger.io/dagger:latest" + username: + The username to authenticate with. Example: "alice" + secret: + The API key, password or token to authenticate to this registry + """ + _args = [ + Arg("address", address), + Arg("username", username), + Arg("secret", secret), + ] + _ctx = self._select("withRegistryAuth", _args) + return Container(_ctx) + + def with_rootfs(self, directory: "Directory") -> Self: + """Change the container's root filesystem. The previous root filesystem + will be lost. + + Parameters + ---------- + directory: + The new root filesystem. + """ + _args = [ + Arg("directory", directory), + ] + _ctx = self._select("withRootfs", _args) + return Container(_ctx) + + def with_secret_variable(self, name: str, secret: "Secret") -> Self: + """Set a new environment variable, using a secret value + + Parameters + ---------- + name: + Name of the secret variable (e.g., "API_SECRET"). + secret: + Identifier of the secret value. + """ + _args = [ + Arg("name", name), + Arg("secret", secret), + ] + _ctx = self._select("withSecretVariable", _args) + return Container(_ctx) + + def with_service_binding(self, alias: str, service: "Service") -> Self: + """Establish a runtime dependency from a container to a network service. + + The service will be started automatically when needed and detached + when it is no longer needed, executing the default command if none is + set. + + The service will be reachable from the container via the provided + hostname alias. + + The service dependency will also convey to any files or directories + produced by the container. + + Parameters + ---------- + alias: + Hostname that will resolve to the target service (only accessible + from within this container) + service: + The target service + """ + _args = [ + Arg("alias", alias), + Arg("service", service), + ] + _ctx = self._select("withServiceBinding", _args) + return Container(_ctx) + + def with_symlink(self, target: str, link_name: str, *, expand: bool | None = False,) -> Self: + """Return a snapshot with a symlink + + Parameters + ---------- + target: + Location of the file or directory to link to (e.g., + "/existing/file"). + link_name: + Location where the symbolic link will be created (e.g., "/new- + file-link"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("target", target), + Arg("linkName", link_name), + Arg("expand", expand, False), + ] + _ctx = self._select("withSymlink", _args) + return Container(_ctx) + + def with_unix_socket(self, path: str, source: "Socket", *, owner: str | None = "", expand: bool | None = False,) -> Self: + """Retrieves this container plus a socket forwarded to the given Unix + socket path. + + Parameters + ---------- + path: + Location of the forwarded Unix socket (e.g., "/tmp/socket"). + source: + Identifier of the socket to forward. + owner: + A user:group to set for the mounted socket. + The user and group can either be an ID (1000:1000) or a name + (foo:bar). + If the group is omitted, it defaults to the same as the user. + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("owner", owner, ""), + Arg("expand", expand, False), + ] + _ctx = self._select("withUnixSocket", _args) + return Container(_ctx) + + def with_user(self, name: str) -> Self: + """Retrieves this container with a different command user. + + Parameters + ---------- + name: + The user to set (e.g., "root"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withUser", _args) + return Container(_ctx) + + def with_workdir(self, path: str, *, expand: bool | None = False,) -> Self: + """Change the container's working directory. Like WORKDIR in Dockerfile. + + Parameters + ---------- + path: + The path to set as the working directory (e.g., "/app"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("withWorkdir", _args) + return Container(_ctx) + + def without_annotation(self, name: str) -> Self: + """Retrieves this container minus the given OCI annotation. + + Parameters + ---------- + name: + The name of the annotation. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutAnnotation", _args) + return Container(_ctx) + + def without_default_args(self) -> Self: + """Remove the container's default arguments.""" + _args: list[Arg] = [] + _ctx = self._select("withoutDefaultArgs", _args) + return Container(_ctx) + + def without_directory(self, path: str, *, expand: bool | None = False,) -> Self: + """Return a new container snapshot, with a directory removed from its + filesystem + + Parameters + ---------- + path: + Location of the directory to remove (e.g., ".github/"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("withoutDirectory", _args) + return Container(_ctx) + + def without_entrypoint(self, *, keep_default_args: bool | None = False,) -> Self: + """Reset the container's OCI entrypoint. + + Parameters + ---------- + keep_default_args: + Don't remove the default arguments when unsetting the entrypoint. + """ + _args = [ + Arg("keepDefaultArgs", keep_default_args, False), + ] + _ctx = self._select("withoutEntrypoint", _args) + return Container(_ctx) + + def without_env_variable(self, name: str) -> Self: + """Retrieves this container minus the given environment variable. + + Parameters + ---------- + name: + The name of the environment variable (e.g., "HOST"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutEnvVariable", _args) + return Container(_ctx) + + def without_exposed_port(self, port: int, *, protocol: NetworkProtocol | None = NetworkProtocol.TCP,) -> Self: + """Unexpose a previously exposed port. + + Parameters + ---------- + port: + Port number to unexpose + protocol: + Port protocol to unexpose + """ + _args = [ + Arg("port", port), + Arg("protocol", protocol, NetworkProtocol.TCP), + ] + _ctx = self._select("withoutExposedPort", _args) + return Container(_ctx) + + def without_file(self, path: str, *, expand: bool | None = False,) -> Self: + """Retrieves this container with the file at the given path removed. + + Parameters + ---------- + path: + Location of the file to remove (e.g., "/file.txt"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("withoutFile", _args) + return Container(_ctx) + + def without_files(self, paths: list[str], *, expand: bool | None = False,) -> Self: + """Return a new container spanshot with specified files removed + + Parameters + ---------- + paths: + Paths of the files to remove. Example: ["foo.txt, + "/root/.ssh/config" + expand: + Replace "${VAR}" or "$VAR" in the value of paths according to the + current environment variables defined in the container (e.g. + "/$VAR/foo.txt"). + """ + _args = [ + Arg("paths", paths), + Arg("expand", expand, False), + ] + _ctx = self._select("withoutFiles", _args) + return Container(_ctx) + + def without_label(self, name: str) -> Self: + """Retrieves this container minus the given environment label. + + Parameters + ---------- + name: + The name of the label to remove (e.g., + "org.opencontainers.artifact.created"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutLabel", _args) + return Container(_ctx) + + def without_mount(self, path: str, *, expand: bool | None = False,) -> Self: + """Retrieves this container after unmounting everything at the given + path. + + Parameters + ---------- + path: + Location of the cache directory (e.g., "/root/.npm"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("withoutMount", _args) + return Container(_ctx) + + def without_registry_auth(self, address: str) -> Self: + """Retrieves this container without the registry authentication of a + given address. + + Parameters + ---------- + address: + Registry's address to remove the authentication from. + Formatted as [host]/[user]/[repo]:[tag] (e.g. + docker.io/dagger/dagger:main). + """ + _args = [ + Arg("address", address), + ] + _ctx = self._select("withoutRegistryAuth", _args) + return Container(_ctx) + + def without_secret_variable(self, name: str) -> Self: + """Retrieves this container minus the given environment variable + containing the secret. + + Parameters + ---------- + name: + The name of the environment variable (e.g., "HOST"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutSecretVariable", _args) + return Container(_ctx) + + def without_unix_socket(self, path: str, *, expand: bool | None = False,) -> Self: + """Retrieves this container with a previously added Unix socket removed. + + Parameters + ---------- + path: + Location of the socket to remove (e.g., "/tmp/socket"). + expand: + Replace "${VAR}" or "$VAR" in the value of path according to the + current environment variables defined in the container (e.g. + "/$VAR/foo"). + """ + _args = [ + Arg("path", path), + Arg("expand", expand, False), + ] + _ctx = self._select("withoutUnixSocket", _args) + return Container(_ctx) + + def without_user(self) -> Self: + """Retrieves this container with an unset command user. + + Should default to root. + """ + _args: list[Arg] = [] + _ctx = self._select("withoutUser", _args) + return Container(_ctx) + + def without_workdir(self) -> Self: + """Unset the container's working directory. + + Should default to "/". + """ + _args: list[Arg] = [] + _ctx = self._select("withoutWorkdir", _args) + return Container(_ctx) + + async def workdir(self) -> str: + """Retrieves the working directory for all commands. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("workdir", _args) + return await _ctx.execute(str) + + def with_(self, cb: Callable[["Container"], "Container"]) -> "Container": + """Call the provided callable with current Container. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class CurrentModule(Type): + """Reflective module API provided to functions at runtime.""" + + async def id(self) -> CurrentModuleID: + """A unique identifier for this CurrentModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + CurrentModuleID + The `CurrentModuleID` scalar type represents an identifier for an + object of type CurrentModule. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(CurrentModuleID) + + async def name(self) -> str: + """The name of the module being executed in + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source(self) -> "Directory": + """The directory containing the module's source code loaded into the + engine (plus any generated code that may have been created). + """ + _args: list[Arg] = [] + _ctx = self._select("source", _args) + return Directory(_ctx) + + def workdir(self, path: str, *, exclude: list[str] | None = None, include: list[str] | None = None,) -> "Directory": + """Load a directory from the module's scratch working directory, + including any changes that may have been made to it during module + function execution. + + Parameters + ---------- + path: + Location of the directory to access (e.g., "."). + exclude: + Exclude artifacts that match the given pattern (e.g., + ["node_modules/", ".git*"]). + include: + Include only artifacts that match the given pattern (e.g., + ["app/", "package.*"]). + """ + _args = [ + Arg("path", path), + Arg("exclude", () if exclude is None else exclude, ()), + Arg("include", () if include is None else include, ()), + ] + _ctx = self._select("workdir", _args) + return Directory(_ctx) + + def workdir_file(self, path: str) -> "File": + """Load a file from the module's scratch working directory, including any + changes that may have been made to it during module function + execution.Load a file from the module's scratch working directory, + including any changes that may have been made to it during module + function execution. + + Parameters + ---------- + path: + Location of the file to retrieve (e.g., "README.md"). + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("workdirFile", _args) + return File(_ctx) + + +@typecheck +class Directory(Type): + """A directory.""" + + def as_git(self) -> "GitRepository": + """Converts this directory to a local git repository""" + _args: list[Arg] = [] + _ctx = self._select("asGit", _args) + return GitRepository(_ctx) + + def as_module(self, *, source_root_path: str | None = ".",) -> "Module": + """Load the directory as a Dagger module source + + Parameters + ---------- + source_root_path: + An optional subpath of the directory which contains the module's + configuration file. + If not set, the module source code is loaded from the root of the + directory. + """ + _args = [ + Arg("sourceRootPath", source_root_path, "."), + ] + _ctx = self._select("asModule", _args) + return Module(_ctx) + + def as_module_source(self, *, source_root_path: str | None = ".",) -> "ModuleSource": + """Load the directory as a Dagger module source + + Parameters + ---------- + source_root_path: + An optional subpath of the directory which contains the module's + configuration file. + If not set, the module source code is loaded from the root of the + directory. + """ + _args = [ + Arg("sourceRootPath", source_root_path, "."), + ] + _ctx = self._select("asModuleSource", _args) + return ModuleSource(_ctx) + + def diff(self, other: Self) -> Self: + """Return the difference between this directory and an another directory. + The difference is encoded as a directory. + + Parameters + ---------- + other: + The directory to compare against + """ + _args = [ + Arg("other", other), + ] + _ctx = self._select("diff", _args) + return Directory(_ctx) + + async def digest(self) -> str: + """Return the directory's digest. The format of the digest is not + guaranteed to be stable between releases of Dagger. It is guaranteed + to be stable between invocations of the same Dagger engine. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("digest", _args) + return await _ctx.execute(str) + + def directory(self, path: str) -> Self: + """Retrieves a directory at the given path. + + Parameters + ---------- + path: + Location of the directory to retrieve. Example: "/src" + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + def docker_build(self, *, dockerfile: str | None = "Dockerfile", platform: Platform | None = None, build_args: list[BuildArg] | None = None, target: str | None = "", secrets: "list[Secret] | None" = None, no_init: bool | None = False,) -> Container: + """Use Dockerfile compatibility to build a container from this directory. + Only use this function for Dockerfile compatibility. Otherwise use the + native Container type directly, it is feature-complete and supports + all Dockerfile features. + + Parameters + ---------- + dockerfile: + Path to the Dockerfile to use (e.g., "frontend.Dockerfile"). + platform: + The platform to build. + build_args: + Build arguments to use in the build. + target: + Target build stage to build. + secrets: + Secrets to pass to the build. + They will be mounted at /run/secrets/[secret-name]. + no_init: + If set, skip the automatic init process injected into containers + created by RUN statements. + This should only be used if the user requires that their exec + processes be the pid 1 process in the container. Otherwise it may + result in unexpected behavior. + """ + _args = [ + Arg("dockerfile", dockerfile, "Dockerfile"), + Arg("platform", platform, None), + Arg("buildArgs", () if build_args is None else build_args, ()), + Arg("target", target, ""), + Arg("secrets", () if secrets is None else secrets, ()), + Arg("noInit", no_init, False), + ] + _ctx = self._select("dockerBuild", _args) + return Container(_ctx) + + async def entries(self, *, path: str | None = None) -> list[str]: + """Returns a list of files and directories at the given path. + + Parameters + ---------- + path: + Location of the directory to look at (e.g., "/src"). + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("path", path, None), + ] + _ctx = self._select("entries", _args) + return await _ctx.execute(list[str]) + + async def export(self, path: str, *, wipe: bool | None = False,) -> str: + """Writes the contents of the directory to a path on the host. + + Parameters + ---------- + path: + Location of the copied directory (e.g., "logs/"). + wipe: + If true, then the host directory will be wiped clean before + exporting so that it exactly matches the directory being exported; + this means it will delete any files on the host that aren't in the + exported dir. If false (the default), the contents of the + directory will be merged with any existing contents of the host + directory, leaving any existing files on the host that aren't in + the exported directory alone. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("path", path), + Arg("wipe", wipe, False), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + def file(self, path: str) -> "File": + """Retrieve a file at the given path. + + Parameters + ---------- + path: + Location of the file to retrieve (e.g., "README.md"). + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + def filter(self, *, exclude: list[str] | None = None, include: list[str] | None = None,) -> Self: + """Return a snapshot with some paths included or excluded + + Parameters + ---------- + exclude: + If set, paths matching one of these glob patterns is excluded from + the new snapshot. Example: ["node_modules/", ".git*", ".env"] + include: + If set, only paths matching one of these glob patterns is included + in the new snapshot. Example: (e.g., ["app/", "package.*"]). + """ + _args = [ + Arg("exclude", () if exclude is None else exclude, ()), + Arg("include", () if include is None else include, ()), + ] + _ctx = self._select("filter", _args) + return Directory(_ctx) + + async def glob(self, pattern: str) -> list[str]: + """Returns a list of files and directories that matche the given pattern. + + Parameters + ---------- + pattern: + Pattern to match (e.g., "*.md"). + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("pattern", pattern), + ] + _ctx = self._select("glob", _args) + return await _ctx.execute(list[str]) + + async def id(self) -> DirectoryID: + """A unique identifier for this Directory. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + DirectoryID + The `DirectoryID` scalar type represents an identifier for an + object of type Directory. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(DirectoryID) + + async def name(self) -> str: + """Returns the name of the directory. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def sync(self) -> Self: + """Force evaluation in the engine. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + def terminal(self, *, container: Container | None = None, cmd: list[str] | None = None, experimental_privileged_nesting: bool | None = False, insecure_root_capabilities: bool | None = False,) -> Self: + """Opens an interactive terminal in new container with this directory + mounted inside. + + Parameters + ---------- + container: + If set, override the default container used for the terminal. + cmd: + If set, override the container's default terminal command and + invoke these command arguments instead. + experimental_privileged_nesting: + Provides Dagger access to the executed command. + insecure_root_capabilities: + Execute the command with all root capabilities. This is similar to + running a command with "sudo" or executing "docker run" with the " + --privileged" flag. Containerization does not provide any security + guarantees when using this option. It should only be used when + absolutely necessary and only with trusted commands. + """ + _args = [ + Arg("container", container, None), + Arg("cmd", () if cmd is None else cmd, ()), + Arg("experimentalPrivilegedNesting", experimental_privileged_nesting, False), + Arg("insecureRootCapabilities", insecure_root_capabilities, False), + ] + _ctx = self._select("terminal", _args) + return Directory(_ctx) + + def with_directory(self, path: str, directory: Self, *, exclude: list[str] | None = None, include: list[str] | None = None,) -> Self: + """Return a snapshot with a directory added + + Parameters + ---------- + path: + Location of the written directory (e.g., "/src/"). + directory: + Identifier of the directory to copy. + exclude: + Exclude artifacts that match the given pattern (e.g., + ["node_modules/", ".git*"]). + include: + Include only artifacts that match the given pattern (e.g., + ["app/", "package.*"]). + """ + _args = [ + Arg("path", path), + Arg("directory", directory), + Arg("exclude", () if exclude is None else exclude, ()), + Arg("include", () if include is None else include, ()), + ] + _ctx = self._select("withDirectory", _args) + return Directory(_ctx) + + def with_file(self, path: str, source: "File", *, permissions: int | None = None,) -> Self: + """Retrieves this directory plus the contents of the given file copied to + the given path. + + Parameters + ---------- + path: + Location of the copied file (e.g., "/file.txt"). + source: + Identifier of the file to copy. + permissions: + Permission given to the copied file (e.g., 0600). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("permissions", permissions, None), + ] + _ctx = self._select("withFile", _args) + return Directory(_ctx) + + def with_files(self, path: str, sources: list["File"], *, permissions: int | None = None,) -> Self: + """Retrieves this directory plus the contents of the given files copied + to the given path. + + Parameters + ---------- + path: + Location where copied files should be placed (e.g., "/src"). + sources: + Identifiers of the files to copy. + permissions: + Permission given to the copied files (e.g., 0600). + """ + _args = [ + Arg("path", path), + Arg("sources", sources), + Arg("permissions", permissions, None), + ] + _ctx = self._select("withFiles", _args) + return Directory(_ctx) + + def with_new_directory(self, path: str, *, permissions: int | None = 420,) -> Self: + """Retrieves this directory plus a new directory created at the given + path. + + Parameters + ---------- + path: + Location of the directory created (e.g., "/logs"). + permissions: + Permission granted to the created directory (e.g., 0777). + """ + _args = [ + Arg("path", path), + Arg("permissions", permissions, 420), + ] + _ctx = self._select("withNewDirectory", _args) + return Directory(_ctx) + + def with_new_file(self, path: str, contents: str, *, permissions: int | None = 420,) -> Self: + """Return a snapshot with a new file added + + Parameters + ---------- + path: + Path of the new file. Example: "foo/bar.txt" + contents: + Contents of the new file. Example: "Hello world!" + permissions: + Permissions of the new file. Example: 0600 + """ + _args = [ + Arg("path", path), + Arg("contents", contents), + Arg("permissions", permissions, 420), + ] + _ctx = self._select("withNewFile", _args) + return Directory(_ctx) + + def with_symlink(self, target: str, link_name: str) -> Self: + """Return a snapshot with a symlink + + Parameters + ---------- + target: + Location of the file or directory to link to (e.g., + "/existing/file"). + link_name: + Location where the symbolic link will be created (e.g., "/new- + file-link"). + """ + _args = [ + Arg("target", target), + Arg("linkName", link_name), + ] + _ctx = self._select("withSymlink", _args) + return Directory(_ctx) + + def with_timestamps(self, timestamp: int) -> Self: + """Retrieves this directory with all file/dir timestamps set to the given + time. + + Parameters + ---------- + timestamp: + Timestamp to set dir/files in. + Formatted in seconds following Unix epoch (e.g., 1672531199). + """ + _args = [ + Arg("timestamp", timestamp), + ] + _ctx = self._select("withTimestamps", _args) + return Directory(_ctx) + + def without_directory(self, path: str) -> Self: + """Return a snapshot with a subdirectory removed + + Parameters + ---------- + path: + Path of the subdirectory to remove. Example: ".github/workflows" + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutDirectory", _args) + return Directory(_ctx) + + def without_file(self, path: str) -> Self: + """Return a snapshot with a file removed + + Parameters + ---------- + path: + Path of the file to remove (e.g., "/file.txt"). + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutFile", _args) + return Directory(_ctx) + + def without_files(self, paths: list[str]) -> Self: + """Return a snapshot with files removed + + Parameters + ---------- + paths: + Paths of the files to remove (e.g., ["/file.txt"]). + """ + _args = [ + Arg("paths", paths), + ] + _ctx = self._select("withoutFiles", _args) + return Directory(_ctx) + + def with_(self, cb: Callable[["Directory"], "Directory"]) -> "Directory": + """Call the provided callable with current Directory. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class EnumTypeDef(Type): + """A definition of a custom enum defined in a Module.""" + + async def description(self) -> str: + """A doc string for the enum, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> EnumTypeDefID: + """A unique identifier for this EnumTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + EnumTypeDefID + The `EnumTypeDefID` scalar type represents an identifier for an + object of type EnumTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(EnumTypeDefID) + + async def members(self) -> list["EnumValueTypeDef"]: + """The members of the enum.""" + _args: list[Arg] = [] + _ctx = self._select("members", _args) + return await _ctx.execute_object_list(EnumValueTypeDef) + + async def name(self) -> str: + """The name of the enum. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source_map(self) -> "SourceMap": + """The location of this enum declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + async def source_module_name(self) -> str: + """If this EnumTypeDef is associated with a Module, the name of the + module. Unset otherwise. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceModuleName", _args) + return await _ctx.execute(str) + + async def values(self) -> list["EnumValueTypeDef"]: + """.. deprecated:: + use members instead + """ + warnings.warn( + "Method \"values\" is deprecated: use members instead", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("values", _args) + return await _ctx.execute_object_list(EnumValueTypeDef) + + +@typecheck +class EnumValueTypeDef(Type): + """A definition of a value in a custom enum defined in a Module.""" + + async def description(self) -> str: + """A doc string for the enum member, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> EnumValueTypeDefID: + """A unique identifier for this EnumValueTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + EnumValueTypeDefID + The `EnumValueTypeDefID` scalar type represents an identifier for + an object of type EnumValueTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(EnumValueTypeDefID) + + async def name(self) -> str: + """The name of the enum member. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source_map(self) -> "SourceMap": + """The location of this enum member declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + async def value(self) -> str: + """The value of the enum member + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("value", _args) + return await _ctx.execute(str) + + +@typecheck +class Env(Type): + + async def id(self) -> EnvID: + """A unique identifier for this Env. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + EnvID + The `EnvID` scalar type represents an identifier for an object of + type Env. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(EnvID) + + def input(self, name: str) -> Binding: + """retrieve an input value by name""" + _args = [ + Arg("name", name), + ] + _ctx = self._select("input", _args) + return Binding(_ctx) + + async def inputs(self) -> list[Binding]: + """return all input values for the environment""" + _args: list[Arg] = [] + _ctx = self._select("inputs", _args) + return await _ctx.execute_object_list(Binding) + + def output(self, name: str) -> Binding: + """retrieve an output value by name""" + _args = [ + Arg("name", name), + ] + _ctx = self._select("output", _args) + return Binding(_ctx) + + async def outputs(self) -> list[Binding]: + """return all output values for the environment""" + _args: list[Arg] = [] + _ctx = self._select("outputs", _args) + return await _ctx.execute_object_list(Binding) + + def with_cache_volume_input(self, name: str, value: CacheVolume, description: str,) -> Self: + """Create or update a binding of type CacheVolume in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The CacheVolume value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withCacheVolumeInput", _args) + return Env(_ctx) + + def with_cache_volume_output(self, name: str, description: str) -> Self: + """Declare a desired CacheVolume output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withCacheVolumeOutput", _args) + return Env(_ctx) + + def with_cloud_input(self, name: str, value: Cloud, description: str,) -> Self: + """Create or update a binding of type Cloud in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Cloud value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withCloudInput", _args) + return Env(_ctx) + + def with_cloud_output(self, name: str, description: str) -> Self: + """Declare a desired Cloud output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withCloudOutput", _args) + return Env(_ctx) + + def with_container_input(self, name: str, value: Container, description: str,) -> Self: + """Create or update a binding of type Container in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Container value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withContainerInput", _args) + return Env(_ctx) + + def with_container_output(self, name: str, description: str) -> Self: + """Declare a desired Container output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withContainerOutput", _args) + return Env(_ctx) + + def with_directory_input(self, name: str, value: Directory, description: str,) -> Self: + """Create or update a binding of type Directory in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Directory value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withDirectoryInput", _args) + return Env(_ctx) + + def with_directory_output(self, name: str, description: str) -> Self: + """Declare a desired Directory output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withDirectoryOutput", _args) + return Env(_ctx) + + def with_env_input(self, name: str, value: Self, description: str,) -> Self: + """Create or update a binding of type Env in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Env value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withEnvInput", _args) + return Env(_ctx) + + def with_env_output(self, name: str, description: str) -> Self: + """Declare a desired Env output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withEnvOutput", _args) + return Env(_ctx) + + def with_file_input(self, name: str, value: "File", description: str,) -> Self: + """Create or update a binding of type File in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The File value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withFileInput", _args) + return Env(_ctx) + + def with_file_output(self, name: str, description: str) -> Self: + """Declare a desired File output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withFileOutput", _args) + return Env(_ctx) + + def with_git_ref_input(self, name: str, value: "GitRef", description: str,) -> Self: + """Create or update a binding of type GitRef in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The GitRef value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withGitRefInput", _args) + return Env(_ctx) + + def with_git_ref_output(self, name: str, description: str) -> Self: + """Declare a desired GitRef output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withGitRefOutput", _args) + return Env(_ctx) + + def with_git_repository_input(self, name: str, value: "GitRepository", description: str,) -> Self: + """Create or update a binding of type GitRepository in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The GitRepository value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withGitRepositoryInput", _args) + return Env(_ctx) + + def with_git_repository_output(self, name: str, description: str) -> Self: + """Declare a desired GitRepository output to be assigned in the + environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withGitRepositoryOutput", _args) + return Env(_ctx) + + def with_llm_input(self, name: str, value: "LLM", description: str,) -> Self: + """Create or update a binding of type LLM in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The LLM value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withLLMInput", _args) + return Env(_ctx) + + def with_llm_output(self, name: str, description: str) -> Self: + """Declare a desired LLM output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withLLMOutput", _args) + return Env(_ctx) + + def with_module_config_client_input(self, name: str, value: "ModuleConfigClient", description: str,) -> Self: + """Create or update a binding of type ModuleConfigClient in the + environment + + Parameters + ---------- + name: + The name of the binding + value: + The ModuleConfigClient value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withModuleConfigClientInput", _args) + return Env(_ctx) + + def with_module_config_client_output(self, name: str, description: str) -> Self: + """Declare a desired ModuleConfigClient output to be assigned in the + environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withModuleConfigClientOutput", _args) + return Env(_ctx) + + def with_module_input(self, name: str, value: "Module", description: str,) -> Self: + """Create or update a binding of type Module in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Module value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withModuleInput", _args) + return Env(_ctx) + + def with_module_output(self, name: str, description: str) -> Self: + """Declare a desired Module output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withModuleOutput", _args) + return Env(_ctx) + + def with_module_source_input(self, name: str, value: "ModuleSource", description: str,) -> Self: + """Create or update a binding of type ModuleSource in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The ModuleSource value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withModuleSourceInput", _args) + return Env(_ctx) + + def with_module_source_output(self, name: str, description: str) -> Self: + """Declare a desired ModuleSource output to be assigned in the + environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withModuleSourceOutput", _args) + return Env(_ctx) + + def with_secret_input(self, name: str, value: "Secret", description: str,) -> Self: + """Create or update a binding of type Secret in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Secret value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withSecretInput", _args) + return Env(_ctx) + + def with_secret_output(self, name: str, description: str) -> Self: + """Declare a desired Secret output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withSecretOutput", _args) + return Env(_ctx) + + def with_service_input(self, name: str, value: "Service", description: str,) -> Self: + """Create or update a binding of type Service in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Service value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withServiceInput", _args) + return Env(_ctx) + + def with_service_output(self, name: str, description: str) -> Self: + """Declare a desired Service output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withServiceOutput", _args) + return Env(_ctx) + + def with_socket_input(self, name: str, value: "Socket", description: str,) -> Self: + """Create or update a binding of type Socket in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Socket value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withSocketInput", _args) + return Env(_ctx) + + def with_socket_output(self, name: str, description: str) -> Self: + """Declare a desired Socket output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withSocketOutput", _args) + return Env(_ctx) + + def with_string_input(self, name: str, value: str, description: str,) -> Self: + """Create or update an input value of type string + + Parameters + ---------- + name: + The name of the binding + value: + The string value to assign to the binding + description: + The description of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withStringInput", _args) + return Env(_ctx) + + def with_string_output(self, name: str, description: str) -> Self: + """Create or update an input value of type string + + Parameters + ---------- + name: + The name of the binding + description: + The description of the output + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withStringOutput", _args) + return Env(_ctx) + + def with_trivy_input(self, name: str, value: "Trivy", description: str,) -> Self: + """Create or update a binding of type Trivy in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The Trivy value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withTrivyInput", _args) + return Env(_ctx) + + def with_trivy_output(self, name: str, description: str) -> Self: + """Declare a desired Trivy output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withTrivyOutput", _args) + return Env(_ctx) + + def with_trivy_scan_input(self, name: str, value: "TrivyScan", description: str,) -> Self: + """Create or update a binding of type TrivyScan in the environment + + Parameters + ---------- + name: + The name of the binding + value: + The TrivyScan value to assign to the binding + description: + The purpose of the input + """ + _args = [ + Arg("name", name), + Arg("value", value), + Arg("description", description), + ] + _ctx = self._select("withTrivyScanInput", _args) + return Env(_ctx) + + def with_trivy_scan_output(self, name: str, description: str) -> Self: + """Declare a desired TrivyScan output to be assigned in the environment + + Parameters + ---------- + name: + The name of the binding + description: + A description of the desired value of the binding + """ + _args = [ + Arg("name", name), + Arg("description", description), + ] + _ctx = self._select("withTrivyScanOutput", _args) + return Env(_ctx) + + def with_(self, cb: Callable[["Env"], "Env"]) -> "Env": + """Call the provided callable with current Env. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class EnvVariable(Type): + """An environment variable name and value.""" + + async def id(self) -> EnvVariableID: + """A unique identifier for this EnvVariable. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + EnvVariableID + The `EnvVariableID` scalar type represents an identifier for an + object of type EnvVariable. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(EnvVariableID) + + async def name(self) -> str: + """The environment variable name. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def value(self) -> str: + """The environment variable value. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("value", _args) + return await _ctx.execute(str) + + +@typecheck +class Error(Type): + + async def id(self) -> ErrorID: + """A unique identifier for this Error. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ErrorID + The `ErrorID` scalar type represents an identifier for an object + of type Error. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ErrorID) + + async def message(self) -> str: + """A description of the error. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("message", _args) + return await _ctx.execute(str) + + async def values(self) -> list["ErrorValue"]: + """The extensions of the error.""" + _args: list[Arg] = [] + _ctx = self._select("values", _args) + return await _ctx.execute_object_list(ErrorValue) + + def with_value(self, name: str, value: JSON) -> Self: + """Add a value to the error. + + Parameters + ---------- + name: + The name of the value. + value: + The value to store on the error. + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withValue", _args) + return Error(_ctx) + + def with_(self, cb: Callable[["Error"], "Error"]) -> "Error": + """Call the provided callable with current Error. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class ErrorValue(Type): + + async def id(self) -> ErrorValueID: + """A unique identifier for this ErrorValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ErrorValueID + The `ErrorValueID` scalar type represents an identifier for an + object of type ErrorValue. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ErrorValueID) + + async def name(self) -> str: + """The name of the value. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def value(self) -> JSON: + """The value. + + Returns + ------- + JSON + An arbitrary JSON-encoded value. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("value", _args) + return await _ctx.execute(JSON) + + +@typecheck +class FieldTypeDef(Type): + """A definition of a field on a custom object defined in a Module. A + field on an object has a static value, as opposed to a function on an + object whose value is computed by invoking code (and can accept + arguments). """ + + async def description(self) -> str: + """A doc string for the field, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> FieldTypeDefID: + """A unique identifier for this FieldTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + FieldTypeDefID + The `FieldTypeDefID` scalar type represents an identifier for an + object of type FieldTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(FieldTypeDefID) + + async def name(self) -> str: + """The name of the field in lowerCamelCase format. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source_map(self) -> "SourceMap": + """The location of this field declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + def type_def(self) -> "TypeDef": + """The type of the field.""" + _args: list[Arg] = [] + _ctx = self._select("typeDef", _args) + return TypeDef(_ctx) + + +@typecheck +class File(Type): + """A file.""" + + async def contents(self) -> str: + """Retrieves the contents of the file. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("contents", _args) + return await _ctx.execute(str) + + async def digest(self, *, exclude_metadata: bool | None = False,) -> str: + """Return the file's digest. The format of the digest is not guaranteed + to be stable between releases of Dagger. It is guaranteed to be stable + between invocations of the same Dagger engine. + + Parameters + ---------- + exclude_metadata: + If true, exclude metadata from the digest. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("excludeMetadata", exclude_metadata, False), + ] + _ctx = self._select("digest", _args) + return await _ctx.execute(str) + + async def export(self, path: str, *, allow_parent_dir_path: bool | None = False,) -> str: + """Writes the file to a file path on the host. + + Parameters + ---------- + path: + Location of the written directory (e.g., "output.txt"). + allow_parent_dir_path: + If allowParentDirPath is true, the path argument can be a + directory path, in which case the file will be created in that + directory. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("path", path), + Arg("allowParentDirPath", allow_parent_dir_path, False), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + async def id(self) -> FileID: + """A unique identifier for this File. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + FileID + The `FileID` scalar type represents an identifier for an object of + type File. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(FileID) + + async def name(self) -> str: + """Retrieves the name of the file. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def size(self) -> int: + """Retrieves the size of the file, in bytes. + + Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("size", _args) + return await _ctx.execute(int) + + async def sync(self) -> Self: + """Force evaluation in the engine. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + def with_name(self, name: str) -> Self: + """Retrieves this file with its name set to the given name. + + Parameters + ---------- + name: + Name to set file to. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withName", _args) + return File(_ctx) + + def with_timestamps(self, timestamp: int) -> Self: + """Retrieves this file with its created/modified timestamps set to the + given time. + + Parameters + ---------- + timestamp: + Timestamp to set dir/files in. + Formatted in seconds following Unix epoch (e.g., 1672531199). + """ + _args = [ + Arg("timestamp", timestamp), + ] + _ctx = self._select("withTimestamps", _args) + return File(_ctx) + + def with_(self, cb: Callable[["File"], "File"]) -> "File": + """Call the provided callable with current File. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class Function(Type): + """Function represents a resolver provided by a Module. A function + always evaluates against a parent object and is given a set of named + arguments. """ + + async def args(self) -> list["FunctionArg"]: + """Arguments accepted by the function, if any.""" + _args: list[Arg] = [] + _ctx = self._select("args", _args) + return await _ctx.execute_object_list(FunctionArg) + + async def description(self) -> str: + """A doc string for the function, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> FunctionID: + """A unique identifier for this Function. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + FunctionID + The `FunctionID` scalar type represents an identifier for an + object of type Function. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(FunctionID) + + async def name(self) -> str: + """The name of the function. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def return_type(self) -> "TypeDef": + """The type returned by the function.""" + _args: list[Arg] = [] + _ctx = self._select("returnType", _args) + return TypeDef(_ctx) + + def source_map(self) -> "SourceMap": + """The location of this function declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + def with_arg(self, name: str, type_def: "TypeDef", *, description: str | None = "", default_value: JSON | None = None, default_path: str | None = "", ignore: list[str] | None = None, source_map: "SourceMap | None" = None,) -> Self: + """Returns the function with the provided argument + + Parameters + ---------- + name: + The name of the argument + type_def: + The type of the argument + description: + A doc string for the argument, if any + default_value: + A default value to use for this argument if not explicitly set by + the caller, if any + default_path: + If the argument is a Directory or File type, default to load path + from context directory, relative to root directory. + ignore: + Patterns to ignore when loading the contextual argument value. + source_map: + The source map for the argument definition. + """ + _args = [ + Arg("name", name), + Arg("typeDef", type_def), + Arg("description", description, ""), + Arg("defaultValue", default_value, None), + Arg("defaultPath", default_path, ""), + Arg("ignore", () if ignore is None else ignore, ()), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withArg", _args) + return Function(_ctx) + + def with_description(self, description: str) -> Self: + """Returns the function with the given doc string. + + Parameters + ---------- + description: + The doc string to set. + """ + _args = [ + Arg("description", description), + ] + _ctx = self._select("withDescription", _args) + return Function(_ctx) + + def with_source_map(self, source_map: "SourceMap") -> Self: + """Returns the function with the given source map. + + Parameters + ---------- + source_map: + The source map for the function definition. + """ + _args = [ + Arg("sourceMap", source_map), + ] + _ctx = self._select("withSourceMap", _args) + return Function(_ctx) + + def with_(self, cb: Callable[["Function"], "Function"]) -> "Function": + """Call the provided callable with current Function. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class FunctionArg(Type): + """An argument accepted by a function. This is a specification for an + argument at function definition time, not an argument passed at + function call time. """ + + async def default_path(self) -> str: + """Only applies to arguments of type File or Directory. If the argument + is not set, load it from the given path in the context directory + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("defaultPath", _args) + return await _ctx.execute(str) + + async def default_value(self) -> JSON: + """A default value to use for this argument when not explicitly set by + the caller, if any. + + Returns + ------- + JSON + An arbitrary JSON-encoded value. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("defaultValue", _args) + return await _ctx.execute(JSON) + + async def description(self) -> str: + """A doc string for the argument, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> FunctionArgID: + """A unique identifier for this FunctionArg. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + FunctionArgID + The `FunctionArgID` scalar type represents an identifier for an + object of type FunctionArg. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(FunctionArgID) + + async def ignore(self) -> list[str]: + """Only applies to arguments of type Directory. The ignore patterns are + applied to the input directory, and matching entries are filtered out, + in a cache-efficient manner. + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("ignore", _args) + return await _ctx.execute(list[str]) + + async def name(self) -> str: + """The name of the argument in lowerCamelCase format. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source_map(self) -> "SourceMap": + """The location of this arg declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + def type_def(self) -> "TypeDef": + """The type of the argument.""" + _args: list[Arg] = [] + _ctx = self._select("typeDef", _args) + return TypeDef(_ctx) + + +@typecheck +class FunctionCall(Type): + """An active function call.""" + + async def id(self) -> FunctionCallID: + """A unique identifier for this FunctionCall. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + FunctionCallID + The `FunctionCallID` scalar type represents an identifier for an + object of type FunctionCall. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(FunctionCallID) + + async def input_args(self) -> list["FunctionCallArgValue"]: + """The argument values the function is being invoked with.""" + _args: list[Arg] = [] + _ctx = self._select("inputArgs", _args) + return await _ctx.execute_object_list(FunctionCallArgValue) + + async def name(self) -> str: + """The name of the function being called. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def parent(self) -> JSON: + """The value of the parent object of the function being called. If the + function is top-level to the module, this is always an empty object. + + Returns + ------- + JSON + An arbitrary JSON-encoded value. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("parent", _args) + return await _ctx.execute(JSON) + + async def parent_name(self) -> str: + """The name of the parent object of the function being called. If the + function is top-level to the module, this is the name of the module. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("parentName", _args) + return await _ctx.execute(str) + + async def return_error(self, error: Error) -> Void | None: + """Return an error from the function. + + Parameters + ---------- + error: + The error to return. + + Returns + ------- + Void | None + The absence of a value. A Null Void is used as a placeholder for + resolvers that do not return anything. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("error", error), + ] + _ctx = self._select("returnError", _args) + await _ctx.execute() + + async def return_value(self, value: JSON) -> Void | None: + """Set the return value of the function call to the provided value. + + Parameters + ---------- + value: + JSON serialization of the return value. + + Returns + ------- + Void | None + The absence of a value. A Null Void is used as a placeholder for + resolvers that do not return anything. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("returnValue", _args) + await _ctx.execute() + + +@typecheck +class FunctionCallArgValue(Type): + """A value passed as a named argument to a function call.""" + + async def id(self) -> FunctionCallArgValueID: + """A unique identifier for this FunctionCallArgValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + FunctionCallArgValueID + The `FunctionCallArgValueID` scalar type represents an identifier + for an object of type FunctionCallArgValue. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(FunctionCallArgValueID) + + async def name(self) -> str: + """The name of the argument. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def value(self) -> JSON: + """The value of the argument represented as a JSON serialized string. + + Returns + ------- + JSON + An arbitrary JSON-encoded value. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("value", _args) + return await _ctx.execute(JSON) + + +@typecheck +class GeneratedCode(Type): + """The result of running an SDK's codegen.""" + + def code(self) -> Directory: + """The directory containing the generated code.""" + _args: list[Arg] = [] + _ctx = self._select("code", _args) + return Directory(_ctx) + + async def id(self) -> GeneratedCodeID: + """A unique identifier for this GeneratedCode. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + GeneratedCodeID + The `GeneratedCodeID` scalar type represents an identifier for an + object of type GeneratedCode. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(GeneratedCodeID) + + async def vcs_generated_paths(self) -> list[str]: + """List of paths to mark generated in version control (i.e. + .gitattributes). + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("vcsGeneratedPaths", _args) + return await _ctx.execute(list[str]) + + async def vcs_ignored_paths(self) -> list[str]: + """List of paths to ignore in version control (i.e. .gitignore). + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("vcsIgnoredPaths", _args) + return await _ctx.execute(list[str]) + + def with_vcs_generated_paths(self, paths: list[str]) -> Self: + """Set the list of paths to mark generated in version control.""" + _args = [ + Arg("paths", paths), + ] + _ctx = self._select("withVCSGeneratedPaths", _args) + return GeneratedCode(_ctx) + + def with_vcs_ignored_paths(self, paths: list[str]) -> Self: + """Set the list of paths to ignore in version control.""" + _args = [ + Arg("paths", paths), + ] + _ctx = self._select("withVCSIgnoredPaths", _args) + return GeneratedCode(_ctx) + + def with_(self, cb: Callable[["GeneratedCode"], "GeneratedCode"]) -> "GeneratedCode": + """Call the provided callable with current GeneratedCode. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class GitRef(Type): + """A git ref (tag, branch, or commit).""" + + async def commit(self) -> str: + """The resolved commit id at this ref. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("commit", _args) + return await _ctx.execute(str) + + async def id(self) -> GitRefID: + """A unique identifier for this GitRef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + GitRefID + The `GitRefID` scalar type represents an identifier for an object + of type GitRef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(GitRefID) + + async def ref(self) -> str: + """The resolved ref name at this ref. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("ref", _args) + return await _ctx.execute(str) + + def tree(self, *, discard_git_dir: bool | None = False, depth: int | None = 1,) -> Directory: + """The filesystem tree at this ref. + + Parameters + ---------- + discard_git_dir: + Set to true to discard .git directory. + depth: + The depth of the tree to fetch. + """ + _args = [ + Arg("discardGitDir", discard_git_dir, False), + Arg("depth", depth, 1), + ] + _ctx = self._select("tree", _args) + return Directory(_ctx) + + +@typecheck +class GitRepository(Type): + """A git repository.""" + + def branch(self, name: str) -> GitRef: + """Returns details of a branch. + + Parameters + ---------- + name: + Branch's name (e.g., "main"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("branch", _args) + return GitRef(_ctx) + + async def branches(self, *, patterns: list[str] | None = None,) -> list[str]: + """branches that match any of the given glob patterns. + + Parameters + ---------- + patterns: + Glob patterns (e.g., "refs/tags/v*"). + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("patterns", patterns, None), + ] + _ctx = self._select("branches", _args) + return await _ctx.execute(list[str]) + + def commit(self, id: str) -> GitRef: + """Returns details of a commit. + + Parameters + ---------- + id: + Identifier of the commit (e.g., + "b6315d8f2810962c601af73f86831f6866ea798b"). + """ + _args = [ + Arg("id", id), + ] + _ctx = self._select("commit", _args) + return GitRef(_ctx) + + def head(self) -> GitRef: + """Returns details for HEAD.""" + _args: list[Arg] = [] + _ctx = self._select("head", _args) + return GitRef(_ctx) + + async def id(self) -> GitRepositoryID: + """A unique identifier for this GitRepository. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + GitRepositoryID + The `GitRepositoryID` scalar type represents an identifier for an + object of type GitRepository. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(GitRepositoryID) + + def ref(self, name: str) -> GitRef: + """Returns details of a ref. + + Parameters + ---------- + name: + Ref's name (can be a commit identifier, a tag name, a branch name, + or a fully-qualified ref). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("ref", _args) + return GitRef(_ctx) + + def tag(self, name: str) -> GitRef: + """Returns details of a tag. + + Parameters + ---------- + name: + Tag's name (e.g., "v0.3.9"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("tag", _args) + return GitRef(_ctx) + + async def tags(self, *, patterns: list[str] | None = None,) -> list[str]: + """tags that match any of the given glob patterns. + + Parameters + ---------- + patterns: + Glob patterns (e.g., "refs/tags/v*"). + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("patterns", patterns, None), + ] + _ctx = self._select("tags", _args) + return await _ctx.execute(list[str]) + + def with_auth_header(self, header: "Secret") -> Self: + """Header to authenticate the remote with. + + .. deprecated:: + Use "httpAuthHeader" in the constructor instead. + + Parameters + ---------- + header: + Secret used to populate the Authorization HTTP header + """ + warnings.warn( + "Method \"with_auth_header\" is deprecated: Use \"httpAuthHeader\" in the constructor instead.", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("header", header), + ] + _ctx = self._select("withAuthHeader", _args) + return GitRepository(_ctx) + + def with_auth_token(self, token: "Secret") -> Self: + """Token to authenticate the remote with. + + .. deprecated:: + Use "httpAuthToken" in the constructor instead. + + Parameters + ---------- + token: + Secret used to populate the password during basic HTTP + Authorization + """ + warnings.warn( + "Method \"with_auth_token\" is deprecated: Use \"httpAuthToken\" in the constructor instead.", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("token", token), + ] + _ctx = self._select("withAuthToken", _args) + return GitRepository(_ctx) + + def with_(self, cb: Callable[["GitRepository"], "GitRepository"]) -> "GitRepository": + """Call the provided callable with current GitRepository. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class InputTypeDef(Type): + """A graphql input type, which is essentially just a group of named + args. This is currently only used to represent pre-existing usage of + graphql input types in the core API. It is not used by user modules + and shouldn't ever be as user module accept input objects via their id + rather than graphql input types. """ + + async def fields(self) -> list[FieldTypeDef]: + """Static fields defined on this input object, if any.""" + _args: list[Arg] = [] + _ctx = self._select("fields", _args) + return await _ctx.execute_object_list(FieldTypeDef) + + async def id(self) -> InputTypeDefID: + """A unique identifier for this InputTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + InputTypeDefID + The `InputTypeDefID` scalar type represents an identifier for an + object of type InputTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(InputTypeDefID) + + async def name(self) -> str: + """The name of the input object. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + +@typecheck +class InterfaceTypeDef(Type): + """A definition of a custom interface defined in a Module.""" + + async def description(self) -> str: + """The doc string for the interface, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def functions(self) -> list[Function]: + """Functions defined on this interface, if any.""" + _args: list[Arg] = [] + _ctx = self._select("functions", _args) + return await _ctx.execute_object_list(Function) + + async def id(self) -> InterfaceTypeDefID: + """A unique identifier for this InterfaceTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + InterfaceTypeDefID + The `InterfaceTypeDefID` scalar type represents an identifier for + an object of type InterfaceTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(InterfaceTypeDefID) + + async def name(self) -> str: + """The name of the interface. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source_map(self) -> "SourceMap": + """The location of this interface declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + async def source_module_name(self) -> str: + """If this InterfaceTypeDef is associated with a Module, the name of the + module. Unset otherwise. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceModuleName", _args) + return await _ctx.execute(str) + + +@typecheck +class LLM(Type): + + def attempt(self, number: int) -> Self: + """create a branch in the LLM's history""" + _args = [ + Arg("number", number), + ] + _ctx = self._select("attempt", _args) + return LLM(_ctx) + + def bind_result(self, name: str) -> Binding: + """returns the type of the current state""" + _args = [ + Arg("name", name), + ] + _ctx = self._select("bindResult", _args) + return Binding(_ctx) + + def env(self) -> Env: + """return the LLM's current environment""" + _args: list[Arg] = [] + _ctx = self._select("env", _args) + return Env(_ctx) + + async def history(self) -> list[str]: + """return the llm message history + + Returns + ------- + list[str] + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("history", _args) + return await _ctx.execute(list[str]) + + async def history_json(self) -> JSON: + """return the raw llm message history as json + + Returns + ------- + JSON + An arbitrary JSON-encoded value. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("historyJSON", _args) + return await _ctx.execute(JSON) + + async def id(self) -> LLMID: + """A unique identifier for this LLM. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + LLMID + The `LLMID` scalar type represents an identifier for an object of + type LLM. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(LLMID) + + async def last_reply(self) -> str: + """return the last llm reply from the history + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("lastReply", _args) + return await _ctx.execute(str) + + def loop(self) -> Self: + """synchronize LLM state""" + _args: list[Arg] = [] + _ctx = self._select("loop", _args) + return LLM(_ctx) + + async def model(self) -> str: + """return the model used by the llm + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("model", _args) + return await _ctx.execute(str) + + async def provider(self) -> str: + """return the provider used by the llm + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("provider", _args) + return await _ctx.execute(str) + + async def sync(self) -> Self: + """synchronize LLM state + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + def token_usage(self) -> "LLMTokenUsage": + """returns the token usage of the current state""" + _args: list[Arg] = [] + _ctx = self._select("tokenUsage", _args) + return LLMTokenUsage(_ctx) + + async def tools(self) -> str: + """print documentation for available tools + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("tools", _args) + return await _ctx.execute(str) + + def with_env(self, env: Env) -> Self: + """allow the LLM to interact with an environment via MCP""" + _args = [ + Arg("env", env), + ] + _ctx = self._select("withEnv", _args) + return LLM(_ctx) + + def with_model(self, model: str) -> Self: + """swap out the llm model + + Parameters + ---------- + model: + The model to use + """ + _args = [ + Arg("model", model), + ] + _ctx = self._select("withModel", _args) + return LLM(_ctx) + + def with_prompt(self, prompt: str) -> Self: + """append a prompt to the llm context + + Parameters + ---------- + prompt: + The prompt to send + """ + _args = [ + Arg("prompt", prompt), + ] + _ctx = self._select("withPrompt", _args) + return LLM(_ctx) + + def with_prompt_file(self, file: File) -> Self: + """append the contents of a file to the llm context + + Parameters + ---------- + file: + The file to read the prompt from + """ + _args = [ + Arg("file", file), + ] + _ctx = self._select("withPromptFile", _args) + return LLM(_ctx) + + def with_system_prompt(self, prompt: str) -> Self: + """Add a system prompt to the LLM's environment + + Parameters + ---------- + prompt: + The system prompt to send + """ + _args = [ + Arg("prompt", prompt), + ] + _ctx = self._select("withSystemPrompt", _args) + return LLM(_ctx) + + def without_default_system_prompt(self) -> Self: + """Disable the default system prompt""" + _args: list[Arg] = [] + _ctx = self._select("withoutDefaultSystemPrompt", _args) + return LLM(_ctx) + + def with_(self, cb: Callable[["LLM"], "LLM"]) -> "LLM": + """Call the provided callable with current LLM. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class LLMTokenUsage(Type): + + async def cached_token_reads(self) -> int: + """Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("cachedTokenReads", _args) + return await _ctx.execute(int) + + async def cached_token_writes(self) -> int: + """Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("cachedTokenWrites", _args) + return await _ctx.execute(int) + + async def id(self) -> LLMTokenUsageID: + """A unique identifier for this LLMTokenUsage. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + LLMTokenUsageID + The `LLMTokenUsageID` scalar type represents an identifier for an + object of type LLMTokenUsage. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(LLMTokenUsageID) + + async def input_tokens(self) -> int: + """Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("inputTokens", _args) + return await _ctx.execute(int) + + async def output_tokens(self) -> int: + """Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("outputTokens", _args) + return await _ctx.execute(int) + + async def total_tokens(self) -> int: + """Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("totalTokens", _args) + return await _ctx.execute(int) + + +@typecheck +class Label(Type): + """A simple key value object that represents a label.""" + + async def id(self) -> LabelID: + """A unique identifier for this Label. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + LabelID + The `LabelID` scalar type represents an identifier for an object + of type Label. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(LabelID) + + async def name(self) -> str: + """The label name. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def value(self) -> str: + """The label value. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("value", _args) + return await _ctx.execute(str) + + +@typecheck +class ListTypeDef(Type): + """A definition of a list type in a Module.""" + + def element_type_def(self) -> "TypeDef": + """The type of the elements in the list.""" + _args: list[Arg] = [] + _ctx = self._select("elementTypeDef", _args) + return TypeDef(_ctx) + + async def id(self) -> ListTypeDefID: + """A unique identifier for this ListTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ListTypeDefID + The `ListTypeDefID` scalar type represents an identifier for an + object of type ListTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ListTypeDefID) + + +@typecheck +class Module(Type): + """A Dagger module.""" + + async def dependencies(self) -> list["Module"]: + """The dependencies of the module.""" + _args: list[Arg] = [] + _ctx = self._select("dependencies", _args) + return await _ctx.execute_object_list(Module) + + async def description(self) -> str: + """The doc string of the module, if any + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def enums(self) -> list["TypeDef"]: + """Enumerations served by this module.""" + _args: list[Arg] = [] + _ctx = self._select("enums", _args) + return await _ctx.execute_object_list(TypeDef) + + def generated_context_directory(self) -> Directory: + """The generated files and directories made on top of the module source's + context directory. + """ + _args: list[Arg] = [] + _ctx = self._select("generatedContextDirectory", _args) + return Directory(_ctx) + + async def id(self) -> ModuleID: + """A unique identifier for this Module. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ModuleID + The `ModuleID` scalar type represents an identifier for an object + of type Module. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ModuleID) + + async def interfaces(self) -> list["TypeDef"]: + """Interfaces served by this module.""" + _args: list[Arg] = [] + _ctx = self._select("interfaces", _args) + return await _ctx.execute_object_list(TypeDef) + + async def name(self) -> str: + """The name of the module + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def objects(self) -> list["TypeDef"]: + """Objects served by this module.""" + _args: list[Arg] = [] + _ctx = self._select("objects", _args) + return await _ctx.execute_object_list(TypeDef) + + def runtime(self) -> Container: + """The container that runs the module's entrypoint. It will fail to + execute if the module doesn't compile. + """ + _args: list[Arg] = [] + _ctx = self._select("runtime", _args) + return Container(_ctx) + + def sdk(self) -> "SDKConfig": + """The SDK config used by this module.""" + _args: list[Arg] = [] + _ctx = self._select("sdk", _args) + return SDKConfig(_ctx) + + async def serve(self, *, include_dependencies: bool | None = None,) -> Void | None: + """Serve a module's API in the current session. + + Note: this can only be called once per session. In the future, it + could return a stream or service to remove the side effect. + + Parameters + ---------- + include_dependencies: + Expose the dependencies of this module to the client + + Returns + ------- + Void | None + The absence of a value. A Null Void is used as a placeholder for + resolvers that do not return anything. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("includeDependencies", include_dependencies, None), + ] + _ctx = self._select("serve", _args) + await _ctx.execute() + + def source(self) -> "ModuleSource": + """The source for the module.""" + _args: list[Arg] = [] + _ctx = self._select("source", _args) + return ModuleSource(_ctx) + + async def sync(self) -> Self: + """Forces evaluation of the module, including any loading into the engine + and associated validation. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + def with_description(self, description: str) -> Self: + """Retrieves the module with the given description + + Parameters + ---------- + description: + The description to set + """ + _args = [ + Arg("description", description), + ] + _ctx = self._select("withDescription", _args) + return Module(_ctx) + + def with_enum(self, enum: "TypeDef") -> Self: + """This module plus the given Enum type and associated values""" + _args = [ + Arg("enum", enum), + ] + _ctx = self._select("withEnum", _args) + return Module(_ctx) + + def with_interface(self, iface: "TypeDef") -> Self: + """This module plus the given Interface type and associated functions""" + _args = [ + Arg("iface", iface), + ] + _ctx = self._select("withInterface", _args) + return Module(_ctx) + + def with_object(self, object: "TypeDef") -> Self: + """This module plus the given Object type and associated functions.""" + _args = [ + Arg("object", object), + ] + _ctx = self._select("withObject", _args) + return Module(_ctx) + + def with_(self, cb: Callable[["Module"], "Module"]) -> "Module": + """Call the provided callable with current Module. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class ModuleConfigClient(Type): + """The client generated for the module.""" + + async def directory(self) -> str: + """The directory the client is generated in. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("directory", _args) + return await _ctx.execute(str) + + async def generator(self) -> str: + """The generator to use + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("generator", _args) + return await _ctx.execute(str) + + async def id(self) -> ModuleConfigClientID: + """A unique identifier for this ModuleConfigClient. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ModuleConfigClientID + The `ModuleConfigClientID` scalar type represents an identifier + for an object of type ModuleConfigClient. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ModuleConfigClientID) + + +@typecheck +class ModuleSource(Type): + """The source needed to load and run a module, along with any metadata + about the source such as versions/urls/etc.""" + + def as_module(self) -> Module: + """Load the source as a module. If this is a local source, the parent + directory must have been provided during module source creation + """ + _args: list[Arg] = [] + _ctx = self._select("asModule", _args) + return Module(_ctx) + + async def as_string(self) -> str: + """A human readable ref string representation of this module source. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("asString", _args) + return await _ctx.execute(str) + + async def clone_ref(self) -> str: + """The ref to clone the root of the git repo from. Only valid for git + sources. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("cloneRef", _args) + return await _ctx.execute(str) + + async def commit(self) -> str: + """The resolved commit of the git repo this source points to. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("commit", _args) + return await _ctx.execute(str) + + async def config_clients(self) -> list[ModuleConfigClient]: + """The clients generated for the module.""" + _args: list[Arg] = [] + _ctx = self._select("configClients", _args) + return await _ctx.execute_object_list(ModuleConfigClient) + + async def config_exists(self) -> bool: + """Whether an existing dagger.json for the module was found. + + Returns + ------- + bool + The `Boolean` scalar type represents `true` or `false`. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("configExists", _args) + return await _ctx.execute(bool) + + def context_directory(self) -> Directory: + """The full directory loaded for the module source, including the source + code as a subdirectory. + """ + _args: list[Arg] = [] + _ctx = self._select("contextDirectory", _args) + return Directory(_ctx) + + async def dependencies(self) -> list["ModuleSource"]: + """The dependencies of the module source.""" + _args: list[Arg] = [] + _ctx = self._select("dependencies", _args) + return await _ctx.execute_object_list(ModuleSource) + + async def digest(self) -> str: + """A content-hash of the module source. Module sources with the same + digest will output the same generated context and convert into the + same module instance. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("digest", _args) + return await _ctx.execute(str) + + def directory(self, path: str) -> Directory: + """The directory containing the module configuration and source code + (source code may be in a subdir). + + Parameters + ---------- + path: + A subpath from the source directory to select. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + async def engine_version(self) -> str: + """The engine version of the module. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("engineVersion", _args) + return await _ctx.execute(str) + + def generated_context_directory(self) -> Directory: + """The generated files and directories made on top of the module source's + context directory. + """ + _args: list[Arg] = [] + _ctx = self._select("generatedContextDirectory", _args) + return Directory(_ctx) + + async def html_repo_url(self) -> str: + """The URL to access the web view of the repository (e.g., GitHub, + GitLab, Bitbucket). + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("htmlRepoURL", _args) + return await _ctx.execute(str) + + async def html_url(self) -> str: + """The URL to the source's git repo in a web browser. Only valid for git + sources. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("htmlURL", _args) + return await _ctx.execute(str) + + async def id(self) -> ModuleSourceID: + """A unique identifier for this ModuleSource. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ModuleSourceID + The `ModuleSourceID` scalar type represents an identifier for an + object of type ModuleSource. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ModuleSourceID) + + async def kind(self) -> ModuleSourceKind: + """The kind of module source (currently local, git or dir). + + Returns + ------- + ModuleSourceKind + The kind of module source. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("kind", _args) + return await _ctx.execute(ModuleSourceKind) + + async def local_context_directory_path(self) -> str: + """The full absolute path to the context directory on the caller's host + filesystem that this module source is loaded from. Only valid for + local module sources. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("localContextDirectoryPath", _args) + return await _ctx.execute(str) + + async def module_name(self) -> str: + """The name of the module, including any setting via the withName API. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("moduleName", _args) + return await _ctx.execute(str) + + async def module_original_name(self) -> str: + """The original name of the module as read from the module's dagger.json + (or set for the first time with the withName API). + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("moduleOriginalName", _args) + return await _ctx.execute(str) + + async def original_subpath(self) -> str: + """The original subpath used when instantiating this module source, + relative to the context directory. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("originalSubpath", _args) + return await _ctx.execute(str) + + async def pin(self) -> str: + """The pinned version of this module source. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("pin", _args) + return await _ctx.execute(str) + + async def repo_root_path(self) -> str: + """The import path corresponding to the root of the git repo this source + points to. Only valid for git sources. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("repoRootPath", _args) + return await _ctx.execute(str) + + def sdk(self) -> "SDKConfig": + """The SDK configuration of the module.""" + _args: list[Arg] = [] + _ctx = self._select("sdk", _args) + return SDKConfig(_ctx) + + async def source_root_subpath(self) -> str: + """The path, relative to the context directory, that contains the + module's dagger.json. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceRootSubpath", _args) + return await _ctx.execute(str) + + async def source_subpath(self) -> str: + """The path to the directory containing the module's source code, + relative to the context directory. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceSubpath", _args) + return await _ctx.execute(str) + + async def sync(self) -> Self: + """Forces evaluation of the module source, including any loading into the + engine and associated validation. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + async def version(self) -> str: + """The specified version of the git repo this source points to. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("version", _args) + return await _ctx.execute(str) + + def with_client(self, generator: str, output_dir: str) -> Self: + """Update the module source with a new client to generate. + + Parameters + ---------- + generator: + The generator to use + output_dir: + The output directory for the generated client. + """ + _args = [ + Arg("generator", generator), + Arg("outputDir", output_dir), + ] + _ctx = self._select("withClient", _args) + return ModuleSource(_ctx) + + def with_dependencies(self, dependencies: list["ModuleSource"]) -> Self: + """Append the provided dependencies to the module source's dependency + list. + + Parameters + ---------- + dependencies: + The dependencies to append. + """ + _args = [ + Arg("dependencies", dependencies), + ] + _ctx = self._select("withDependencies", _args) + return ModuleSource(_ctx) + + def with_engine_version(self, version: str) -> Self: + """Upgrade the engine version of the module to the given value. + + Parameters + ---------- + version: + The engine version to upgrade to. + """ + _args = [ + Arg("version", version), + ] + _ctx = self._select("withEngineVersion", _args) + return ModuleSource(_ctx) + + def with_includes(self, patterns: list[str]) -> Self: + """Update the module source with additional include patterns for + files+directories from its context that are required for building it + + Parameters + ---------- + patterns: + The new additional include patterns. + """ + _args = [ + Arg("patterns", patterns), + ] + _ctx = self._select("withIncludes", _args) + return ModuleSource(_ctx) + + def with_name(self, name: str) -> Self: + """Update the module source with a new name. + + Parameters + ---------- + name: + The name to set. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withName", _args) + return ModuleSource(_ctx) + + def with_sdk(self, source: str) -> Self: + """Update the module source with a new SDK. + + Parameters + ---------- + source: + The SDK source to set. + """ + _args = [ + Arg("source", source), + ] + _ctx = self._select("withSDK", _args) + return ModuleSource(_ctx) + + def with_source_subpath(self, path: str) -> Self: + """Update the module source with a new source subpath. + + Parameters + ---------- + path: + The path to set as the source subpath. Must be relative to the + module source's source root directory. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withSourceSubpath", _args) + return ModuleSource(_ctx) + + def with_update_dependencies(self, dependencies: list[str]) -> Self: + """Update one or more module dependencies. + + Parameters + ---------- + dependencies: + The dependencies to update. + """ + _args = [ + Arg("dependencies", dependencies), + ] + _ctx = self._select("withUpdateDependencies", _args) + return ModuleSource(_ctx) + + def without_client(self, path: str) -> Self: + """Remove a client from the module source. + + Parameters + ---------- + path: + The path of the client to remove. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutClient", _args) + return ModuleSource(_ctx) + + def without_dependencies(self, dependencies: list[str]) -> Self: + """Remove the provided dependencies from the module source's dependency + list. + + Parameters + ---------- + dependencies: + The dependencies to remove. + """ + _args = [ + Arg("dependencies", dependencies), + ] + _ctx = self._select("withoutDependencies", _args) + return ModuleSource(_ctx) + + def with_(self, cb: Callable[["ModuleSource"], "ModuleSource"]) -> "ModuleSource": + """Call the provided callable with current ModuleSource. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class ObjectTypeDef(Type): + """A definition of a custom object defined in a Module.""" + + def constructor(self) -> Function: + """The function used to construct new instances of this object, if any""" + _args: list[Arg] = [] + _ctx = self._select("constructor", _args) + return Function(_ctx) + + async def description(self) -> str: + """The doc string for the object, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def fields(self) -> list[FieldTypeDef]: + """Static fields defined on this object, if any.""" + _args: list[Arg] = [] + _ctx = self._select("fields", _args) + return await _ctx.execute_object_list(FieldTypeDef) + + async def functions(self) -> list[Function]: + """Functions defined on this object, if any.""" + _args: list[Arg] = [] + _ctx = self._select("functions", _args) + return await _ctx.execute_object_list(Function) + + async def id(self) -> ObjectTypeDefID: + """A unique identifier for this ObjectTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ObjectTypeDefID + The `ObjectTypeDefID` scalar type represents an identifier for an + object of type ObjectTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ObjectTypeDefID) + + async def name(self) -> str: + """The name of the object. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + def source_map(self) -> "SourceMap": + """The location of this object declaration.""" + _args: list[Arg] = [] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + async def source_module_name(self) -> str: + """If this ObjectTypeDef is associated with a Module, the name of the + module. Unset otherwise. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceModuleName", _args) + return await _ctx.execute(str) + + +@typecheck +class Port(Type): + """A port exposed by a container.""" + + async def description(self) -> str | None: + """The port description. + + Returns + ------- + str | None + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str | None) + + async def experimental_skip_healthcheck(self) -> bool: + """Skip the health check when run as a service. + + Returns + ------- + bool + The `Boolean` scalar type represents `true` or `false`. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("experimentalSkipHealthcheck", _args) + return await _ctx.execute(bool) + + async def id(self) -> PortID: + """A unique identifier for this Port. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + PortID + The `PortID` scalar type represents an identifier for an object of + type Port. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(PortID) + + async def port(self) -> int: + """The port number. + + Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("port", _args) + return await _ctx.execute(int) + + async def protocol(self) -> NetworkProtocol: + """The transport layer protocol. + + Returns + ------- + NetworkProtocol + Transport layer network protocol associated to a port. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("protocol", _args) + return await _ctx.execute(NetworkProtocol) + + +@typecheck +class Client(Root): + """The root of the DAG.""" + + def cache_volume(self, key: str) -> CacheVolume: + """Constructs a cache volume for a given cache key. + + Parameters + ---------- + key: + A string identifier to target this cache volume (e.g., "modules- + cache"). + """ + _args = [ + Arg("key", key), + ] + _ctx = self._select("cacheVolume", _args) + return CacheVolume(_ctx) + + def cloud(self) -> Cloud: + """Dagger Cloud configuration and state""" + _args: list[Arg] = [] + _ctx = self._select("cloud", _args) + return Cloud(_ctx) + + def container(self, *, platform: Platform | None = None,) -> Container: + """Creates a scratch container, with no image or metadata. + + To pull an image, follow up with the "from" function. + + Parameters + ---------- + platform: + Platform to initialize the container with. Defaults to the native + platform of the current engine + """ + _args = [ + Arg("platform", platform, None), + ] + _ctx = self._select("container", _args) + return Container(_ctx) + + def current_function_call(self) -> FunctionCall: + """The FunctionCall context that the SDK caller is currently executing + in. + + If the caller is not currently executing in a function, this will + return an error. + """ + _args: list[Arg] = [] + _ctx = self._select("currentFunctionCall", _args) + return FunctionCall(_ctx) + + def current_module(self) -> CurrentModule: + """The module currently being served in the session, if any.""" + _args: list[Arg] = [] + _ctx = self._select("currentModule", _args) + return CurrentModule(_ctx) + + async def current_type_defs(self) -> list["TypeDef"]: + """The TypeDef representations of the objects currently being served in + the session. + """ + _args: list[Arg] = [] + _ctx = self._select("currentTypeDefs", _args) + return await _ctx.execute_object_list(TypeDef) + + async def default_platform(self) -> Platform: + """The default platform of the engine. + + Returns + ------- + Platform + The platform config OS and architecture in a Container. The + format is [os]/[platform]/[version] (e.g., "darwin/arm64/v7", + "windows/amd64", "linux/arm64"). + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("defaultPlatform", _args) + return await _ctx.execute(Platform) + + def directory(self) -> Directory: + """Creates an empty directory.""" + _args: list[Arg] = [] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + def env(self, *, privileged: bool | None = False, writable: bool | None = False,) -> Env: + """Initialize a new environment + + .. caution:: + Experimental: Environments are not yet stabilized + + Parameters + ---------- + privileged: + Give the environment the same privileges as the caller: core API + including host access, current module, and dependencies + writable: + Allow new outputs to be declared and saved in the environment + """ + _args = [ + Arg("privileged", privileged, False), + Arg("writable", writable, False), + ] + _ctx = self._select("env", _args) + return Env(_ctx) + + def error(self, message: str) -> Error: + """Create a new error. + + Parameters + ---------- + message: + A brief description of the error. + """ + _args = [ + Arg("message", message), + ] + _ctx = self._select("error", _args) + return Error(_ctx) + + def file(self, name: str, contents: str, *, permissions: int | None = 420,) -> File: + """Creates a file with the specified contents. + + Parameters + ---------- + name: + Name of the new file. Example: "foo.txt" + contents: + Contents of the new file. Example: "Hello world!" + permissions: + Permissions of the new file. Example: 0600 + """ + _args = [ + Arg("name", name), + Arg("contents", contents), + Arg("permissions", permissions, 420), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + def function(self, name: str, return_type: "TypeDef") -> Function: + """Creates a function. + + Parameters + ---------- + name: + Name of the function, in its original format from the + implementation language. + return_type: + Return type of the function. + """ + _args = [ + Arg("name", name), + Arg("returnType", return_type), + ] + _ctx = self._select("function", _args) + return Function(_ctx) + + def generated_code(self, code: Directory) -> GeneratedCode: + """Create a code generation result, given a directory containing the + generated code. + """ + _args = [ + Arg("code", code), + ] + _ctx = self._select("generatedCode", _args) + return GeneratedCode(_ctx) + + def git(self, url: str, *, keep_git_dir: bool | None = True, ssh_known_hosts: str | None = "", ssh_auth_socket: "Socket | None" = None, http_auth_username: str | None = "", http_auth_token: "Secret | None" = None, http_auth_header: "Secret | None" = None, experimental_service_host: "Service | None" = None,) -> GitRepository: + """Queries a Git repository. + + Parameters + ---------- + url: + URL of the git repository. + Can be formatted as `https://{host}/{owner}/{repo}`, + `git@{host}:{owner}/{repo}`. + Suffix ".git" is optional. + keep_git_dir: + DEPRECATED: Set to true to keep .git directory. + ssh_known_hosts: + Set SSH known hosts + ssh_auth_socket: + Set SSH auth socket + http_auth_username: + Username used to populate the password during basic HTTP + Authorization + http_auth_token: + Secret used to populate the password during basic HTTP + Authorization + http_auth_header: + Secret used to populate the Authorization HTTP header + experimental_service_host: + A service which must be started before the repo is fetched. + """ + _args = [ + Arg("url", url), + Arg("keepGitDir", keep_git_dir, True), + Arg("sshKnownHosts", ssh_known_hosts, ""), + Arg("sshAuthSocket", ssh_auth_socket, None), + Arg("httpAuthUsername", http_auth_username, ""), + Arg("httpAuthToken", http_auth_token, None), + Arg("httpAuthHeader", http_auth_header, None), + Arg("experimentalServiceHost", experimental_service_host, None), + ] + _ctx = self._select("git", _args) + return GitRepository(_ctx) + + def http(self, url: str, *, name: str | None = None, permissions: int | None = None, auth_header: "Secret | None" = None, experimental_service_host: "Service | None" = None,) -> File: + """Returns a file containing an http remote url content. + + Parameters + ---------- + url: + HTTP url to get the content from (e.g., "https://docs.dagger.io"). + name: + File name to use for the file. Defaults to the last part of the + URL. + permissions: + Permissions to set on the file. + auth_header: + Secret used to populate the Authorization HTTP header + experimental_service_host: + A service which must be started before the URL is fetched. + """ + _args = [ + Arg("url", url), + Arg("name", name, None), + Arg("permissions", permissions, None), + Arg("authHeader", auth_header, None), + Arg("experimentalServiceHost", experimental_service_host, None), + ] + _ctx = self._select("http", _args) + return File(_ctx) + + def llm(self, *, model: str | None = None, max_api_calls: int | None = None,) -> LLM: + """Initialize a Large Language Model (LLM) + + .. caution:: + Experimental: LLM support is not yet stabilized + + Parameters + ---------- + model: + Model to use + max_api_calls: + Cap the number of API calls for this LLM + """ + _args = [ + Arg("model", model, None), + Arg("maxAPICalls", max_api_calls, None), + ] + _ctx = self._select("llm", _args) + return LLM(_ctx) + + def load_binding_from_id(self, id: BindingID) -> Binding: + """Load a Binding from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadBindingFromID", _args) + return Binding(_ctx) + + def load_cache_volume_from_id(self, id: CacheVolumeID) -> CacheVolume: + """Load a CacheVolume from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadCacheVolumeFromID", _args) + return CacheVolume(_ctx) + + def load_cloud_from_id(self, id: CloudID) -> Cloud: + """Load a Cloud from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadCloudFromID", _args) + return Cloud(_ctx) + + def load_container_from_id(self, id: ContainerID) -> Container: + """Load a Container from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadContainerFromID", _args) + return Container(_ctx) + + def load_current_module_from_id(self, id: CurrentModuleID) -> CurrentModule: + """Load a CurrentModule from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadCurrentModuleFromID", _args) + return CurrentModule(_ctx) + + def load_directory_from_id(self, id: DirectoryID) -> Directory: + """Load a Directory from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadDirectoryFromID", _args) + return Directory(_ctx) + + def load_enum_type_def_from_id(self, id: EnumTypeDefID) -> EnumTypeDef: + """Load a EnumTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadEnumTypeDefFromID", _args) + return EnumTypeDef(_ctx) + + def load_enum_value_type_def_from_id(self, id: EnumValueTypeDefID) -> EnumValueTypeDef: + """Load a EnumValueTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadEnumValueTypeDefFromID", _args) + return EnumValueTypeDef(_ctx) + + def load_env_from_id(self, id: EnvID) -> Env: + """Load a Env from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadEnvFromID", _args) + return Env(_ctx) + + def load_env_variable_from_id(self, id: EnvVariableID) -> EnvVariable: + """Load a EnvVariable from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadEnvVariableFromID", _args) + return EnvVariable(_ctx) + + def load_error_from_id(self, id: ErrorID) -> Error: + """Load a Error from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadErrorFromID", _args) + return Error(_ctx) + + def load_error_value_from_id(self, id: ErrorValueID) -> ErrorValue: + """Load a ErrorValue from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadErrorValueFromID", _args) + return ErrorValue(_ctx) + + def load_field_type_def_from_id(self, id: FieldTypeDefID) -> FieldTypeDef: + """Load a FieldTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadFieldTypeDefFromID", _args) + return FieldTypeDef(_ctx) + + def load_file_from_id(self, id: FileID) -> File: + """Load a File from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadFileFromID", _args) + return File(_ctx) + + def load_function_arg_from_id(self, id: FunctionArgID) -> FunctionArg: + """Load a FunctionArg from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadFunctionArgFromID", _args) + return FunctionArg(_ctx) + + def load_function_call_arg_value_from_id(self, id: FunctionCallArgValueID) -> FunctionCallArgValue: + """Load a FunctionCallArgValue from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadFunctionCallArgValueFromID", _args) + return FunctionCallArgValue(_ctx) + + def load_function_call_from_id(self, id: FunctionCallID) -> FunctionCall: + """Load a FunctionCall from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadFunctionCallFromID", _args) + return FunctionCall(_ctx) + + def load_function_from_id(self, id: FunctionID) -> Function: + """Load a Function from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadFunctionFromID", _args) + return Function(_ctx) + + def load_generated_code_from_id(self, id: GeneratedCodeID) -> GeneratedCode: + """Load a GeneratedCode from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadGeneratedCodeFromID", _args) + return GeneratedCode(_ctx) + + def load_git_ref_from_id(self, id: GitRefID) -> GitRef: + """Load a GitRef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadGitRefFromID", _args) + return GitRef(_ctx) + + def load_git_repository_from_id(self, id: GitRepositoryID) -> GitRepository: + """Load a GitRepository from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadGitRepositoryFromID", _args) + return GitRepository(_ctx) + + def load_input_type_def_from_id(self, id: InputTypeDefID) -> InputTypeDef: + """Load a InputTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadInputTypeDefFromID", _args) + return InputTypeDef(_ctx) + + def load_interface_type_def_from_id(self, id: InterfaceTypeDefID) -> InterfaceTypeDef: + """Load a InterfaceTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadInterfaceTypeDefFromID", _args) + return InterfaceTypeDef(_ctx) + + def load_llm_from_id(self, id: LLMID) -> LLM: + """Load a LLM from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadLLMFromID", _args) + return LLM(_ctx) + + def load_llm_token_usage_from_id(self, id: LLMTokenUsageID) -> LLMTokenUsage: + """Load a LLMTokenUsage from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadLLMTokenUsageFromID", _args) + return LLMTokenUsage(_ctx) + + def load_label_from_id(self, id: LabelID) -> Label: + """Load a Label from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadLabelFromID", _args) + return Label(_ctx) + + def load_list_type_def_from_id(self, id: ListTypeDefID) -> ListTypeDef: + """Load a ListTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadListTypeDefFromID", _args) + return ListTypeDef(_ctx) + + def load_module_config_client_from_id(self, id: ModuleConfigClientID) -> ModuleConfigClient: + """Load a ModuleConfigClient from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadModuleConfigClientFromID", _args) + return ModuleConfigClient(_ctx) + + def load_module_from_id(self, id: ModuleID) -> Module: + """Load a Module from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadModuleFromID", _args) + return Module(_ctx) + + def load_module_source_from_id(self, id: ModuleSourceID) -> ModuleSource: + """Load a ModuleSource from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadModuleSourceFromID", _args) + return ModuleSource(_ctx) + + def load_object_type_def_from_id(self, id: ObjectTypeDefID) -> ObjectTypeDef: + """Load a ObjectTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadObjectTypeDefFromID", _args) + return ObjectTypeDef(_ctx) + + def load_port_from_id(self, id: PortID) -> Port: + """Load a Port from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadPortFromID", _args) + return Port(_ctx) + + def load_sdk_config_from_id(self, id: SDKConfigID) -> "SDKConfig": + """Load a SDKConfig from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadSDKConfigFromID", _args) + return SDKConfig(_ctx) + + def load_scalar_type_def_from_id(self, id: ScalarTypeDefID) -> "ScalarTypeDef": + """Load a ScalarTypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadScalarTypeDefFromID", _args) + return ScalarTypeDef(_ctx) + + def load_secret_from_id(self, id: SecretID) -> "Secret": + """Load a Secret from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadSecretFromID", _args) + return Secret(_ctx) + + def load_service_from_id(self, id: ServiceID) -> "Service": + """Load a Service from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadServiceFromID", _args) + return Service(_ctx) + + def load_socket_from_id(self, id: SocketID) -> "Socket": + """Load a Socket from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadSocketFromID", _args) + return Socket(_ctx) + + def load_source_map_from_id(self, id: SourceMapID) -> "SourceMap": + """Load a SourceMap from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadSourceMapFromID", _args) + return SourceMap(_ctx) + + def load_terminal_from_id(self, id: TerminalID) -> "Terminal": + """Load a Terminal from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadTerminalFromID", _args) + return Terminal(_ctx) + + def load_trivy_from_id(self, id: TrivyID) -> "Trivy": + """Load a Trivy from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadTrivyFromID", _args) + return Trivy(_ctx) + + def load_trivy_scan_from_id(self, id: TrivyScanID) -> "TrivyScan": + """Load a TrivyScan from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadTrivyScanFromID", _args) + return TrivyScan(_ctx) + + def load_type_def_from_id(self, id: TypeDefID) -> "TypeDef": + """Load a TypeDef from its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("loadTypeDefFromID", _args) + return TypeDef(_ctx) + + def module(self) -> Module: + """Create a new module.""" + _args: list[Arg] = [] + _ctx = self._select("module", _args) + return Module(_ctx) + + def module_source(self, ref_string: str, *, ref_pin: str | None = "", disable_find_up: bool | None = False, allow_not_exists: bool | None = False, require_kind: ModuleSourceKind | None = None,) -> ModuleSource: + """Create a new module source instance from a source ref string + + Parameters + ---------- + ref_string: + The string ref representation of the module source + ref_pin: + The pinned version of the module source + disable_find_up: + If true, do not attempt to find dagger.json in a parent directory + of the provided path. Only relevant for local module sources. + allow_not_exists: + If true, do not error out if the provided ref string is a local + path and does not exist yet. Useful when initializing new modules + in directories that don't exist yet. + require_kind: + If set, error out if the ref string is not of the provided + requireKind. + """ + _args = [ + Arg("refString", ref_string), + Arg("refPin", ref_pin, ""), + Arg("disableFindUp", disable_find_up, False), + Arg("allowNotExists", allow_not_exists, False), + Arg("requireKind", require_kind, None), + ] + _ctx = self._select("moduleSource", _args) + return ModuleSource(_ctx) + + def secret(self, uri: str, *, cache_key: str | None = None,) -> "Secret": + """Creates a new secret. + + Parameters + ---------- + uri: + The URI of the secret store + cache_key: + If set, the given string will be used as the cache key for this + secret. This means that any secrets with the same cache key will + be considered equivalent in terms of cache lookups, even if they + have different URIs or plaintext values. + For example, two secrets with the same cache key provided as + secret env vars to other wise equivalent containers will result in + the container withExecs hitting the cache for each other. + If not set, the cache key for the secret will be derived from its + plaintext value as looked up when the secret is constructed. + """ + _args = [ + Arg("uri", uri), + Arg("cacheKey", cache_key, None), + ] + _ctx = self._select("secret", _args) + return Secret(_ctx) + + def set_secret(self, name: str, plaintext: str) -> "Secret": + """Sets a secret given a user defined name to its plaintext and returns + the secret. + + The plaintext value is limited to a size of 128000 bytes. + + Parameters + ---------- + name: + The user defined name for this secret + plaintext: + The plaintext of the secret + """ + _args = [ + Arg("name", name), + Arg("plaintext", plaintext), + ] + _ctx = self._select("setSecret", _args) + return Secret(_ctx) + + def source_map(self, filename: str, line: int, column: int,) -> "SourceMap": + """Creates source map metadata. + + Parameters + ---------- + filename: + The filename from the module source. + line: + The line number within the filename. + column: + The column number within the line. + """ + _args = [ + Arg("filename", filename), + Arg("line", line), + Arg("column", column), + ] + _ctx = self._select("sourceMap", _args) + return SourceMap(_ctx) + + def trivy(self, *, version: str | None = None, container: Container | None = None, config: File | None = None, cache: CacheVolume | None = None, database_repository: str | None = None, warm_database_cache: bool | None = None,) -> "Trivy": + """Parameters + ---------- + version: + Version (image tag) to use from the official image repository as a + base container. + container: + Custom container to use as a base container. Takes precedence over + version. + config: + Trivy configuration file. + cache: + Persist Trivy cache between runs. + database_repository: + OCI repository to retrieve trivy-db from. (default + "ghcr.io/aquasecurity/trivy-db:2") + warm_database_cache: + Warm the vulnerability database cache. + """ + _args = [ + Arg("version", version, None), + Arg("container", container, None), + Arg("config", config, None), + Arg("cache", cache, None), + Arg("databaseRepository", database_repository, None), + Arg("warmDatabaseCache", warm_database_cache, None), + ] + _ctx = self._select("trivy", _args) + return Trivy(_ctx) + + def type_def(self) -> "TypeDef": + """Create a new TypeDef.""" + _args: list[Arg] = [] + _ctx = self._select("typeDef", _args) + return TypeDef(_ctx) + + async def version(self) -> str: + """Get the current Dagger Engine version. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("version", _args) + return await _ctx.execute(str) + + +@typecheck +class SDKConfig(Type): + """The SDK config of the module.""" + + async def id(self) -> SDKConfigID: + """A unique identifier for this SDKConfig. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + SDKConfigID + The `SDKConfigID` scalar type represents an identifier for an + object of type SDKConfig. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(SDKConfigID) + + async def source(self) -> str: + """Source of the SDK. Either a name of a builtin SDK or a module source + ref string pointing to the SDK's implementation. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("source", _args) + return await _ctx.execute(str) + + +@typecheck +class ScalarTypeDef(Type): + """A definition of a custom scalar defined in a Module.""" + + async def description(self) -> str: + """A doc string for the scalar, if any. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("description", _args) + return await _ctx.execute(str) + + async def id(self) -> ScalarTypeDefID: + """A unique identifier for this ScalarTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ScalarTypeDefID + The `ScalarTypeDefID` scalar type represents an identifier for an + object of type ScalarTypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ScalarTypeDefID) + + async def name(self) -> str: + """The name of the scalar. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def source_module_name(self) -> str: + """If this ScalarTypeDef is associated with a Module, the name of the + module. Unset otherwise. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("sourceModuleName", _args) + return await _ctx.execute(str) + + +@typecheck +class Secret(Type): + """A reference to a secret value, which can be handled more safely + than the value itself.""" + + async def id(self) -> SecretID: + """A unique identifier for this Secret. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + SecretID + The `SecretID` scalar type represents an identifier for an object + of type Secret. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(SecretID) + + async def name(self) -> str: + """The name of this secret. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("name", _args) + return await _ctx.execute(str) + + async def plaintext(self) -> str: + """The value of this secret. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("plaintext", _args) + return await _ctx.execute(str) + + async def uri(self) -> str: + """The URI of this secret. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("uri", _args) + return await _ctx.execute(str) + + +@typecheck +class Service(Type): + """A content-addressed service providing TCP connectivity.""" + + async def endpoint(self, *, port: int | None = None, scheme: str | None = "",) -> str: + """Retrieves an endpoint that clients can use to reach this container. + + If no port is specified, the first exposed port is used. If none exist + an error is returned. + + If a scheme is specified, a URL is returned. Otherwise, a host:port + pair is returned. + + Parameters + ---------- + port: + The exposed port number for the endpoint + scheme: + Return a URL with the given scheme, eg. http for http:// + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("port", port, None), + Arg("scheme", scheme, ""), + ] + _ctx = self._select("endpoint", _args) + return await _ctx.execute(str) + + async def hostname(self) -> str: + """Retrieves a hostname which can be used by clients to reach this + container. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("hostname", _args) + return await _ctx.execute(str) + + async def id(self) -> ServiceID: + """A unique identifier for this Service. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + ServiceID + The `ServiceID` scalar type represents an identifier for an object + of type Service. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(ServiceID) + + async def ports(self) -> list[Port]: + """Retrieves the list of ports provided by the service.""" + _args: list[Arg] = [] + _ctx = self._select("ports", _args) + return await _ctx.execute_object_list(Port) + + async def start(self) -> Self: + """Start the service and wait for its health checks to succeed. + + Services bound to a Container do not need to be manually started. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "start", _args) + + async def stop(self, *, kill: bool | None = False) -> Self: + """Stop the service. + + Parameters + ---------- + kill: + Immediately kill the service without waiting for a graceful exit + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("kill", kill, False), + ] + return await self._ctx.execute_sync(self, "stop", _args) + + async def up(self, *, ports: list[PortForward] | None = None, random: bool | None = False,) -> Void | None: + """Creates a tunnel that forwards traffic from the caller's network to + this service. + + Parameters + ---------- + ports: + List of frontend/backend port mappings to forward. + Frontend is the port accepting traffic on the host, backend is the + service port. + random: + Bind each tunnel port to a random port on the host. + + Returns + ------- + Void | None + The absence of a value. A Null Void is used as a placeholder for + resolvers that do not return anything. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("ports", () if ports is None else ports, ()), + Arg("random", random, False), + ] + _ctx = self._select("up", _args) + await _ctx.execute() + + def with_hostname(self, hostname: str) -> Self: + """Configures a hostname which can be used by clients within the session + to reach this container. + + Parameters + ---------- + hostname: + The hostname to use. + """ + _args = [ + Arg("hostname", hostname), + ] + _ctx = self._select("withHostname", _args) + return Service(_ctx) + + def with_(self, cb: Callable[["Service"], "Service"]) -> "Service": + """Call the provided callable with current Service. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class Socket(Type): + """A Unix or TCP/IP socket that can be mounted into a container.""" + + async def id(self) -> SocketID: + """A unique identifier for this Socket. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + SocketID + The `SocketID` scalar type represents an identifier for an object + of type Socket. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(SocketID) + + +@typecheck +class SourceMap(Type): + """Source location information.""" + + async def column(self) -> int: + """The column number within the line. + + Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("column", _args) + return await _ctx.execute(int) + + async def filename(self) -> str: + """The filename from the module source. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("filename", _args) + return await _ctx.execute(str) + + async def id(self) -> SourceMapID: + """A unique identifier for this SourceMap. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + SourceMapID + The `SourceMapID` scalar type represents an identifier for an + object of type SourceMap. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(SourceMapID) + + async def line(self) -> int: + """The line number within the filename. + + Returns + ------- + int + The `Int` scalar type represents non-fractional signed whole + numeric values. Int can represent values between -(2^31) and 2^31 + - 1. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("line", _args) + return await _ctx.execute(int) + + async def module(self) -> str: + """The module dependency this was declared in. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("module", _args) + return await _ctx.execute(str) + + +@typecheck +class Terminal(Type): + """An interactive terminal that clients can connect to.""" + + async def id(self) -> TerminalID: + """A unique identifier for this Terminal. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + TerminalID + The `TerminalID` scalar type represents an identifier for an + object of type Terminal. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(TerminalID) + + async def sync(self) -> Self: + """Forces evaluation of the pipeline in the engine. + + It doesn't run the default command if no exec has been set. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + return await self._ctx.execute_sync(self, "sync", _args) + + def __await__(self): + return self.sync().__await__() + + +@typecheck +class Trivy(Type): + + def binary(self, binary: File, *, config: File | None = None,) -> "TrivyScan": + """ + Scan a binary. + + This is a convenience method to scan a binary file that normally falls + under the rootfs target. + + See https://aquasecurity.github.io/trivy/latest/docs/target/rootfs/ + for more information. + + Parameters + ---------- + binary: + Binary to scan. + config: + Trivy configuration file. + """ + _args = [ + Arg("binary", binary), + Arg("config", config, None), + ] + _ctx = self._select("binary", _args) + return TrivyScan(_ctx) + + def container(self, container: Container, *, config: File | None = None,) -> "TrivyScan": + """ + Scan a container. + + See https://aquasecurity.github.io/trivy/latest/docs/target/container_ + image/ for more information. + + Parameters + ---------- + container: + Image container to scan. + config: + Trivy configuration file. + """ + _args = [ + Arg("container", container), + Arg("config", config, None), + ] + _ctx = self._select("container", _args) + return TrivyScan(_ctx) + + def filesystem(self, directory: Directory, *, target: str | None = ".", config: File | None = None,) -> "TrivyScan": + """ + Scan a filesystem. + + See + https://aquasecurity.github.io/trivy/latest/docs/target/filesystem/ + for more information. + + Parameters + ---------- + directory: + Directory to scan. + target: + Subpath within the directory to scan. + config: + Trivy configuration file. + """ + _args = [ + Arg("directory", directory), + Arg("target", target, "."), + Arg("config", config, None), + ] + _ctx = self._select("filesystem", _args) + return TrivyScan(_ctx) + + def helm_chart(self, chart: File, *, set: list[str] | None = None, set_string: list[str] | None = None, values: list[File] | None = None, kube_version: str | None = None, api_versions: list[str] | None = None, config: File | None = None,) -> "TrivyScan": + """ + Scan a Helm chart. + + Parameters + ---------- + chart: + Helm chart package to scan. + set: + Inline values for the Helm chart (equivalent of --set parameter of + the helm install command). + set_string: + Inline values for the Helm chart (equivalent of --set-string + parameter of the helm install command). + values: + Values files for the Helm chart (equivalent of --values parameter + of the helm install command). + kube_version: + Kubernetes version used for Capabilities.KubeVersion. + api_versions: + Available API versions used for Capabilities.APIVersions. + config: + Trivy configuration file. + """ + _args = [ + Arg("chart", chart), + Arg("set", set, None), + Arg("setString", set_string, None), + Arg("values", values, None), + Arg("kubeVersion", kube_version, None), + Arg("apiVersions", api_versions, None), + Arg("config", config, None), + ] + _ctx = self._select("helmChart", _args) + return TrivyScan(_ctx) + + async def id(self) -> TrivyID: + """A unique identifier for this Trivy. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + TrivyID + The `TrivyID` scalar type represents an identifier for an object + of type Trivy. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(TrivyID) + + def image(self, image: str, *, config: File | None = None,) -> "TrivyScan": + """ + Scan a container image. + + See https://aquasecurity.github.io/trivy/latest/docs/target/container_ + image/ for more information. + + Parameters + ---------- + image: + Name of the image to scan. + config: + Trivy configuration file. + """ + _args = [ + Arg("image", image), + Arg("config", config, None), + ] + _ctx = self._select("image", _args) + return TrivyScan(_ctx) + + def image_tarball(self, image: File, *, config: File | None = None,) -> "TrivyScan": + """ + Scan a container image tarball. + + See https://aquasecurity.github.io/trivy/latest/docs/target/container_ + image/ for more information. + + Parameters + ---------- + image: + Input file to the image (to use instead of pulling). + config: + Trivy configuration file. + """ + _args = [ + Arg("image", image), + Arg("config", config, None), + ] + _ctx = self._select("imageTarball", _args) + return TrivyScan(_ctx) + + def rootfs(self, directory: Directory, *, target: str | None = ".", config: File | None = None,) -> "TrivyScan": + """ + Scan a root filesystem. + + See https://aquasecurity.github.io/trivy/latest/docs/target/rootfs/ + for more information. + + Parameters + ---------- + directory: + Directory to scan. + target: + Subpath within the directory to scan. + config: + Trivy configuration file. + """ + _args = [ + Arg("directory", directory), + Arg("target", target, "."), + Arg("config", config, None), + ] + _ctx = self._select("rootfs", _args) + return TrivyScan(_ctx) + + def sbom(self, sbom: File, *, config: File | None = None,) -> "TrivyScan": + """ + Scan an SBOM. + + See https://aquasecurity.github.io/trivy/latest/docs/target/sbom/ for + more information. + + Parameters + ---------- + sbom: + SBOM to scan. + config: + Trivy configuration file. + """ + _args = [ + Arg("sbom", sbom), + Arg("config", config, None), + ] + _ctx = self._select("sbom", _args) + return TrivyScan(_ctx) + + +@typecheck +class TrivyScan(Type): + + async def id(self) -> TrivyScanID: + """A unique identifier for this TrivyScan. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + TrivyScanID + The `TrivyScanID` scalar type represents an identifier for an + object of type TrivyScan. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(TrivyScanID) + + async def output(self, *, format: str | None = None) -> str: + """ + Get the scan results. + + Parameters + ---------- + format: + Trivy report format. + + Returns + ------- + str + The `String` scalar type represents textual data, represented as + UTF-8 character sequences. The String type is most often used by + GraphQL to represent free-form human-readable text. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args = [ + Arg("format", format, None), + ] + _ctx = self._select("output", _args) + return await _ctx.execute(str) + + def report(self, format: str) -> File: + """ + Get the scan report as a file. + + Parameters + ---------- + format: + Trivy report format. + """ + _args = [ + Arg("format", format), + ] + _ctx = self._select("report", _args) + return File(_ctx) + + +@typecheck +class TypeDef(Type): + """A definition of a parameter or return type in a Module.""" + + def as_enum(self) -> EnumTypeDef: + """If kind is ENUM, the enum-specific type definition. If kind is not + ENUM, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asEnum", _args) + return EnumTypeDef(_ctx) + + def as_input(self) -> InputTypeDef: + """If kind is INPUT, the input-specific type definition. If kind is not + INPUT, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asInput", _args) + return InputTypeDef(_ctx) + + def as_interface(self) -> InterfaceTypeDef: + """If kind is INTERFACE, the interface-specific type definition. If kind + is not INTERFACE, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asInterface", _args) + return InterfaceTypeDef(_ctx) + + def as_list(self) -> ListTypeDef: + """If kind is LIST, the list-specific type definition. If kind is not + LIST, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asList", _args) + return ListTypeDef(_ctx) + + def as_object(self) -> ObjectTypeDef: + """If kind is OBJECT, the object-specific type definition. If kind is not + OBJECT, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asObject", _args) + return ObjectTypeDef(_ctx) + + def as_scalar(self) -> ScalarTypeDef: + """If kind is SCALAR, the scalar-specific type definition. If kind is not + SCALAR, this will be null. + """ + _args: list[Arg] = [] + _ctx = self._select("asScalar", _args) + return ScalarTypeDef(_ctx) + + async def id(self) -> TypeDefID: + """A unique identifier for this TypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + TypeDefID + The `TypeDefID` scalar type represents an identifier for an object + of type TypeDef. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("id", _args) + return await _ctx.execute(TypeDefID) + + async def kind(self) -> TypeDefKind: + """The kind of type this is (e.g. primitive, list, object). + + Returns + ------- + TypeDefKind + Distinguishes the different kinds of TypeDefs. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("kind", _args) + return await _ctx.execute(TypeDefKind) + + async def optional(self) -> bool: + """Whether this type can be set to null. Defaults to false. + + Returns + ------- + bool + The `Boolean` scalar type represents `true` or `false`. + + Raises + ------ + ExecuteTimeoutError + If the time to execute the query exceeds the configured timeout. + QueryError + If the API returns an error. + """ + _args: list[Arg] = [] + _ctx = self._select("optional", _args) + return await _ctx.execute(bool) + + def with_constructor(self, function: Function) -> Self: + """Adds a function for constructing a new instance of an Object TypeDef, + failing if the type is not an object. + """ + _args = [ + Arg("function", function), + ] + _ctx = self._select("withConstructor", _args) + return TypeDef(_ctx) + + def with_enum(self, name: str, *, description: str | None = "", source_map: SourceMap | None = None,) -> Self: + """Returns a TypeDef of kind Enum with the provided name. + + Note that an enum's values may be omitted if the intent is only to + refer to an enum. This is how functions are able to return their own, + or any other circular reference. + + Parameters + ---------- + name: + The name of the enum + description: + A doc string for the enum, if any + source_map: + The source map for the enum definition. + """ + _args = [ + Arg("name", name), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withEnum", _args) + return TypeDef(_ctx) + + def with_enum_member(self, name: str, *, value: str | None = "", description: str | None = "", source_map: SourceMap | None = None,) -> Self: + """Adds a static value for an Enum TypeDef, failing if the type is not an + enum. + + Parameters + ---------- + name: + The name of the member in the enum + value: + The value of the member in the enum + description: + A doc string for the member, if any + source_map: + The source map for the enum member definition. + """ + _args = [ + Arg("name", name), + Arg("value", value, ""), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withEnumMember", _args) + return TypeDef(_ctx) + + def with_enum_value(self, value: str, *, description: str | None = "", source_map: SourceMap | None = None,) -> Self: + """Adds a static value for an Enum TypeDef, failing if the type is not an + enum. + + .. deprecated:: + Use :py:meth:`with_enum_member` instead + + Parameters + ---------- + value: + The name of the value in the enum + description: + A doc string for the value, if any + source_map: + The source map for the enum value definition. + """ + warnings.warn( + "Method \"with_enum_value\" is deprecated: Use \"with_enum_member\" instead", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("value", value), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withEnumValue", _args) + return TypeDef(_ctx) + + def with_field(self, name: str, type_def: Self, *, description: str | None = "", source_map: SourceMap | None = None,) -> Self: + """Adds a static field for an Object TypeDef, failing if the type is not + an object. + + Parameters + ---------- + name: + The name of the field in the object + type_def: + The type of the field + description: + A doc string for the field, if any + source_map: + The source map for the field definition. + """ + _args = [ + Arg("name", name), + Arg("typeDef", type_def), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withField", _args) + return TypeDef(_ctx) + + def with_function(self, function: Function) -> Self: + """Adds a function for an Object or Interface TypeDef, failing if the + type is not one of those kinds. + """ + _args = [ + Arg("function", function), + ] + _ctx = self._select("withFunction", _args) + return TypeDef(_ctx) + + def with_interface(self, name: str, *, description: str | None = "", source_map: SourceMap | None = None,) -> Self: + """Returns a TypeDef of kind Interface with the provided name.""" + _args = [ + Arg("name", name), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withInterface", _args) + return TypeDef(_ctx) + + def with_kind(self, kind: TypeDefKind) -> Self: + """Sets the kind of the type.""" + _args = [ + Arg("kind", kind), + ] + _ctx = self._select("withKind", _args) + return TypeDef(_ctx) + + def with_list_of(self, element_type: Self) -> Self: + """Returns a TypeDef of kind List with the provided type for its + elements. + """ + _args = [ + Arg("elementType", element_type), + ] + _ctx = self._select("withListOf", _args) + return TypeDef(_ctx) + + def with_object(self, name: str, *, description: str | None = "", source_map: SourceMap | None = None,) -> Self: + """Returns a TypeDef of kind Object with the provided name. + + Note that an object's fields and functions may be omitted if the + intent is only to refer to an object. This is how functions are able + to return their own object, or any other circular reference. + """ + _args = [ + Arg("name", name), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + ] + _ctx = self._select("withObject", _args) + return TypeDef(_ctx) + + def with_optional(self, optional: bool) -> Self: + """Sets whether this type can be set to null.""" + _args = [ + Arg("optional", optional), + ] + _ctx = self._select("withOptional", _args) + return TypeDef(_ctx) + + def with_scalar(self, name: str, *, description: str | None = "",) -> Self: + """Returns a TypeDef of kind Scalar with the provided name.""" + _args = [ + Arg("name", name), + Arg("description", description, ""), + ] + _ctx = self._select("withScalar", _args) + return TypeDef(_ctx) + + def with_(self, cb: Callable[["TypeDef"], "TypeDef"]) -> "TypeDef": + """Call the provided callable with current TypeDef. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +dag = Client() +"""The global client instance.""" + +__all__ = [ + "Binding", + "BindingID", + "BuildArg", + "CacheSharingMode", + "CacheVolume", + "CacheVolumeID", + "Client", + "Cloud", + "CloudID", + "Container", + "ContainerID", + "CurrentModule", + "CurrentModuleID", + "Directory", + "DirectoryID", + "EnumTypeDef", + "EnumTypeDefID", + "EnumValueTypeDef", + "EnumValueTypeDefID", + "Env", + "EnvID", + "EnvVariable", + "EnvVariableID", + "Error", + "ErrorID", + "ErrorValue", + "ErrorValueID", + "FieldTypeDef", + "FieldTypeDefID", + "File", + "FileID", + "Function", + "FunctionArg", + "FunctionArgID", + "FunctionCall", + "FunctionCallArgValue", + "FunctionCallArgValueID", + "FunctionCallID", + "FunctionID", + "GeneratedCode", + "GeneratedCodeID", + "GitRef", + "GitRefID", + "GitRepository", + "GitRepositoryID", + "ImageLayerCompression", + "ImageMediaTypes", + "InputTypeDef", + "InputTypeDefID", + "InterfaceTypeDef", + "InterfaceTypeDefID", + "JSON", + "LLM", + "LLMID", + "LLMTokenUsage", + "LLMTokenUsageID", + "Label", + "LabelID", + "ListTypeDef", + "ListTypeDefID", + "Module", + "ModuleConfigClient", + "ModuleConfigClientID", + "ModuleID", + "ModuleSource", + "ModuleSourceID", + "ModuleSourceKind", + "NetworkProtocol", + "ObjectTypeDef", + "ObjectTypeDefID", + "PipelineLabel", + "Platform", + "Port", + "PortForward", + "PortID", + "ReturnType", + "SDKConfig", + "SDKConfigID", + "ScalarTypeDef", + "ScalarTypeDefID", + "Secret", + "SecretID", + "Service", + "ServiceID", + "Socket", + "SocketID", + "SourceMap", + "SourceMapID", + "Terminal", + "TerminalID", + "Trivy", + "TrivyID", + "TrivyScan", + "TrivyScanID", + "TypeDef", + "TypeDefID", + "TypeDefKind", + "Void", + "dag", +] \ No newline at end of file diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/log.py b/content/en/docs/04/solution/ci/sdk/src/dagger/log.py new file mode 100644 index 0000000..1d0d7ee --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/log.py @@ -0,0 +1,36 @@ +import logging +import logging.config + + +def configure_logging(level: int | str = logging.WARNING): + """Configure logging for the dagger package. + + Sets a console handler with a simple format and defaults to WARNING level, + but can be set to DEBUG to see more information. + """ + config = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": {"format": "[{levelname}] {name}: {message}", "style": "{"}, + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + "loggers": { + "dagger": { + "handlers": ["console"], + "level": level, + }, + }, + } + logging.config.dictConfig(config) + + +def configure_debug_logging(): + """Configure logging for the dagger package with DEBUG level.""" + configure_logging(logging.DEBUG) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/__init__.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/__init__.py new file mode 100644 index 0000000..0ec2dd4 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/__init__.py @@ -0,0 +1,35 @@ +from typing_extensions import Doc + +from dagger.mod._arguments import DefaultPath +from dagger.mod._arguments import Ignore +from dagger.mod._arguments import Name +from dagger.mod._module import Module +from dagger.mod._types import Enum + + +_default_mod = Module() + +enum_type = _default_mod.enum_type +function = _default_mod.function +field = _default_mod.field +interface = _default_mod.interface +object_type = _default_mod.object_type + + +def default_module() -> Module: + """Return the default Module builder instance.""" + return _default_mod + + +__all__ = [ + "DefaultPath", + "Doc", # Only re-exported because it's in `typing_extensions`. + "Enum", + "Ignore", + "Name", + "enum_type", + "field", + "function", + "interface", + "object_type", +] diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_arguments.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_arguments.py new file mode 100644 index 0000000..296aba5 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_arguments.py @@ -0,0 +1,153 @@ +import dataclasses +import inspect +import logging + +from cattrs.preconf.json import JsonConverter + +import dagger +from dagger.mod._types import APIName, ContextPath + +logger = logging.getLogger(__name__) + + +@dataclasses.dataclass(slots=True, frozen=True) +class Name: + """An alternative name when exposing a function argument to the API. + + Useful to avoid conflicts with reserved words. + + Example usage:: + + @function + def pull(self, from_: Annotated[str, Name("from")]): ... + """ + + name: APIName + + def __str__(self) -> str: + return self.name + + +@dataclasses.dataclass(slots=True, frozen=True) +class DefaultPath: + """If the argument is omitted, load it from the given path in the context directory. + + Only applies to arguments of type :py:class:`dagger.Directory` or + :py:class:`dagger.File`. + + Mutually exclusive with setting a default value for the parameter. When + used within Python, the parameter should be required. + + Example usage:: + + @function + def build(self, src: Annotated[dagger.Directory, DefaultPath("..")]): ... + """ + + from_context: ContextPath + + def __str__(self) -> str: + return self.from_context + + +@dataclasses.dataclass(slots=True, frozen=True) +class Ignore: + """Ignore patterns for :py:class:`dagger.Directory` arguments. + + The ignore patterns are applied to the input directory, and matching entries + are filtered out, in a cache-efficient manner. + + Useful if it's known in advance which files or directories should be + excluded when loading the directory. + + Example usage:: + + @function + def build(self, src: Annotated[dagger.Directory, Ignore([".venv"])]): ... + """ + + patterns: list[str] + + # TODO: to allow frozen=True, the patterns can't be in a list (mutable), + # but changing it to an immutable sequence now will produce IDE errors + # for users which requires a change to their existing code. It's not that + # important to be immutable though, just for future consideration. + def __hash__(self) -> int: + return hash(tuple(self.patterns)) + + +@dataclasses.dataclass(slots=True, kw_only=True) +class Parameter: + """Parameter from function signature in :py:class:`FunctionResolver`.""" + + name: APIName + + # Inspect + signature: inspect.Parameter + resolved_type: type + is_nullable: bool + + # Metadata + doc: str | None = None + ignore: list[str] | None = None + default_path: ContextPath | None = None + default_value: dagger.JSON | None = None + + conv: dataclasses.InitVar[JsonConverter] + + def __post_init__(self, conv: JsonConverter): + self._validate() + + if not self.has_default: + return + try: + self.default_value = dagger.JSON(conv.dumps(self.signature.default)) + except TypeError as e: + # Rather than failing on a default value that's not JSON + # serializable and going through hoops to support more and more + # types, just don't register it. It'll still be registered + # as optional so the API server will call the function without + # it and let Python handle it. + logger.debug( + "Not registering default value for %s: %s", + self.signature, + e, + ) + self.is_nullable = True + + @property + def has_default(self) -> bool: + return self.signature.default is not inspect.Parameter.empty + + @property + def is_optional(self) -> bool: + return self.has_default or self.default_path is not None or self.is_nullable + + def _validate(self): + # These validations are already done by the engine, just repeating them + # here for better error messages. + if not self.is_nullable and self.has_default and self.signature.default is None: + msg = ( + "Can't use a default value of None on a non-nullable type for " + f"parameter '{self.signature.name}'" + ) + raise ValueError(msg) + + if self.default_path: + if self.has_default and not ( + self.is_nullable and self.signature.default is None + ): + msg = ( + "Can't use DefaultPath with a default value for " + f"parameter '{self.signature.name}'" + ) + raise AssertionError(msg) + + if not self.default_path: + # NB: We could instead warn or just ignore, but it's better to fail + # fast to avoid astonishment. + msg = ( + "DefaultPath can't be used with an empty path in " + f"parameter '{self.signature.name}'" + ) + raise ValueError(msg) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_converter.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_converter.py new file mode 100644 index 0000000..8c646ad --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_converter.py @@ -0,0 +1,225 @@ +import enum +import functools +import inspect +import logging +import typing + +from beartype.door import TypeHint +from cattrs.preconf.json import make_converter as make_json_converter + +import dagger +from dagger import dag +from dagger.client._core import Arg, configure_converter_enum +from dagger.client._guards import is_id_type, is_id_type_subclass +from dagger.client.base import Interface, Scalar, Type +from dagger.mod._resolver import Function +from dagger.mod._utils import ( + get_doc, + get_module, + get_object_type, + is_annotated, + is_dagger_interface_type, + is_dagger_object_type, + is_initvar, + is_nullable, + is_subclass, + is_union, + list_of, + non_null, + strip_annotations, + syncify, + to_camel_case, +) + +logger = logging.getLogger(__name__) + +if typing.TYPE_CHECKING: + from dagger import TypeDef + + +def make_converter(): + conv = make_json_converter( + detailed_validation=True, + ) + + conv.register_structure_hook_func( + is_id_type_subclass, + dagger_type_structure, + ) + conv.register_unstructure_hook_func( + lambda t: is_id_type_subclass(t) or is_dagger_interface_type(t), + dagger_type_unstructure, + ) + + conv.register_structure_hook_func( + is_dagger_interface_type, + dagger_interface_structure, + ) + + configure_converter_enum(conv) + + return conv + + +def dagger_type_structure(id_: str | Scalar, cls: type[Type]): + """Get dagger object type from id.""" + cls = strip_annotations(cls) + + if not is_id_type_subclass(cls) and not is_dagger_interface_type(cls): + msg = f"Unsupported type '{cls.__name__}'" + raise TypeError(msg) + + return cls( + dag._select(f"load{cls._graphql_name()}FromID", [Arg("id", id_)]) # noqa: SLF001 + ) + + +def dagger_interface_structure(id_, cls: type[Interface]): + """Get dagger interface implementation from id.""" + return dagger_type_structure(id_, to_interface_impl(cls)) + + +def dagger_type_unstructure(obj): + """Get id from dagger object.""" + if not is_id_type(obj) and not isinstance(obj, Interface): + msg = f"Expected dagger Type object, got `{type(obj)}`" + raise TypeError(msg) + return syncify(obj.id) + + +@functools.cache +def to_interface_impl(proto: type) -> type[Interface]: + """Return a dynamically generated client binding for the interface.""" + typ = get_object_type(proto) + mod = get_module(proto) + + if typ is None or not typ.interface or mod is None: + msg = f"Unexpected interface type `{proto}`" + raise TypeError(msg) + + methods = { + func.original_name: make_method(name, func, proto) + for name, func in typ.functions.items() + } + + return type( + mod.main_cls.__name__ + proto.__name__, + (Interface,), + {"_declaration": proto, **methods}, + ) + + +def make_method(name: str, func: Function, proto: type) -> typing.Callable: # noqa: C901 + """Generate method for interface client binding.""" + ret_type = func.return_type + _is_self = ret_type is proto + + if not _is_self and is_dagger_interface_type(ret_type): + ret_type = to_interface_impl(ret_type) + + # Need to convert names to GraphQL convention for query builder + gql_name = to_camel_case(name) + gql_arg_names = { + param.name: to_camel_case(param.name) for param in func.parameters.values() + } + + # Generate query builder selection based on inputs + def select(obj: Interface, *args, **kwargs): + bound = func.signature.bind(obj, *args, **kwargs) + args = [ + Arg(name=gql_arg_names[arg_name], value=arg_value) + for arg_name, arg_value in bound.arguments.items() + if arg_name != "self" + ] + return obj._select(gql_name, args) # noqa: SLF001 + + # Mimic function signature defined in the interface + def wrap(c: typing.Callable): + c.__signature__ = func.signature + return functools.wraps(func.wrapped)(c) + + # If return type is an object, then it's a lazy/chain method (sync) + if _is_self or is_dagger_object_type(ret_type): + + def chain_method(self, *args, **kwargs): + _ctx = select(self, *args, **kwargs) + if _is_self: + # we don't have a finished type yet but we can use self + return type(self)(_ctx) + return ret_type(_ctx) + + return wrap(chain_method) + + # Anything else triggers execution (async) + async def exec_method(self, *args, **kwargs): + _ctx = select(self, *args, **kwargs) + if cls := list_of(ret_type): + if cls is proto: + cls = type(self) + elif is_dagger_interface_type(cls): + cls = to_interface_impl(cls) + if is_dagger_object_type(cls): + return await _ctx.execute_object_list(cls) + return await _ctx.execute(ret_type) + + return wrap(exec_method) + + +@functools.cache +def to_typedef(annotation: typing.Any) -> "TypeDef": # noqa: C901, PLR0911 + """Convert Python object to API type.""" + if is_initvar(annotation): + return to_typedef(annotation.type) + + if is_annotated(annotation): + return to_typedef(strip_annotations(annotation)) + + td = dag.type_def() + + typ = TypeHint(annotation) + + if is_nullable(typ): + td = td.with_optional(True) + + typ = non_null(typ) + + # Can't represent unions in the API. + if is_union(typ): + msg = f"Unsupported union type: {typ.hint}" + raise TypeError(msg) + + builtins = { + str: dagger.TypeDefKind.STRING_KIND, + int: dagger.TypeDefKind.INTEGER_KIND, + float: dagger.TypeDefKind.FLOAT_KIND, + bool: dagger.TypeDefKind.BOOLEAN_KIND, + type(None): dagger.TypeDefKind.VOID_KIND, + } + + if typ.hint in builtins: + return td.with_kind(builtins[typ.hint]) + + if el := list_of(typ.hint): + return td.with_list_of(to_typedef(el)) + + if inspect.isclass(cls := typ.hint): + name = cls.__name__ + + if is_subclass(cls, enum.Enum): + return td.with_enum(name, description=get_doc(cls)) + + if is_subclass(cls, Scalar): + return td.with_scalar(name, description=get_doc(cls)) + + # object defined in this module + if obj_type := get_object_type(cls): + if obj_type.interface: + return td.with_interface(name) + return td.with_object(name) + + # object type from API (codegen) + if is_id_type_subclass(cls): + return td.with_object(name) + + msg = f"Unsupported type: {typ.hint!r}" + raise TypeError(msg) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_exceptions.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_exceptions.py new file mode 100644 index 0000000..67393da --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_exceptions.py @@ -0,0 +1,88 @@ +import dataclasses +from functools import partial +from typing import Any + +import cattrs +from rich.console import Console +from rich.panel import Panel + +from dagger import DaggerError + +_console = Console(stderr=True, style="red") + + +class ExtensionError(DaggerError): + """Base class for all errors raised by extensions.""" + + def rich_print(self): + _console.print( + Panel( + str(self), + border_style="red", + title="Error", + title_align="left", + ), + markup=False, + ) + + +class FatalError(ExtensionError): + """An unrecoverable error.""" + + +class InternalError(FatalError): + """An error in Dagger itself.""" + + +class UserError(FatalError): + """An error that could be recovered in user code.""" + + +class NameConflictError(UserError): + """An error caused by a name conflict.""" + + +class FunctionError(UserError): + """An error while executing a user function.""" + + +@dataclasses.dataclass(slots=True) +class ConversionError(Exception): + """An error while converting data.""" + + exc: Exception + msg: str = "" + origin: Any | None = None + typ: type | None = None + + def __str__(self): + return transform_error(self.exc, self.msg, self.origin, self.typ) + + def as_user(self, msg: str): + return UserError(str(dataclasses.replace(self, msg=msg))) + + +def transform_error( + exc: Exception, + msg: str = "", + origin: Any | None = None, + typ: type | None = None, +) -> str: + """Transform a cattrs error into a list of error messages.""" + path = "$" + + if origin is not None: + path = getattr(origin, "__qualname__", "") + if hasattr(origin, "__module__"): + path = f"{origin.__module__}.{path}" + + fn = partial(cattrs.transform_error, path=path) + + if typ is not None: + fn = partial( + fn, + format_exception=lambda e, _: cattrs.v.format_exception(e, typ), + ) + + errors = "; ".join(error.removesuffix(" @ $") for error in fn(exc)) + return f"{msg}: {errors}" if msg else errors diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_module.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_module.py new file mode 100644 index 0000000..50a5dcf --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_module.py @@ -0,0 +1,712 @@ +import dataclasses +import enum +import inspect +import json +import logging +import os +import textwrap +import typing +from collections.abc import Awaitable, Callable, Mapping +from typing import Any, TypeVar, cast + +import anyio +import cattrs +import cattrs.gen +from cattrs.preconf import is_primitive_enum +from cattrs.preconf.json import JsonConverter +from typing_extensions import dataclass_transform, overload + +import dagger +from dagger import dag +from dagger.client._core import configure_converter_enum +from dagger.mod._arguments import Parameter +from dagger.mod._converter import make_converter, to_typedef +from dagger.mod._exceptions import ( + ConversionError, + FatalError, + FunctionError, + InternalError, + UserError, +) +from dagger.mod._resolver import ( + Constructor, + Field, + Func, + Function, + ObjectType, + P, + R, +) +from dagger.mod._types import APIName, FieldDefinition, FunctionDefinition, PythonName +from dagger.mod._utils import ( + asyncify, + await_maybe, + extract_enum_member_doc, + get_doc, + get_parent_module_doc, + is_annotated, + to_pascal_case, +) + +logger = logging.getLogger(__name__) + +OBJECT_DEF_KEY: typing.Final[str] = "__dagger_object__" +FIELD_DEF_KEY: typing.Final[str] = "__dagger_field__" +FUNCTION_DEF_KEY: typing.Final[str] = "__dagger_function__" + +T = TypeVar("T", bound=type) + + +class Module: + """Builder for a :py:class:`dagger.Module`.""" + + def __init__(self, name: str = os.getenv("DAGGER_MODULE_NAME", "")): + self.name: str = name + self._converter: JsonConverter = make_converter() + self._objects: dict[str, ObjectType] = {} + self._enums: dict[str, type[enum.Enum]] = {} + self._main: ObjectType | None = None + + @property + def main_cls(self) -> type: + assert self._main is not None + return self._main.cls + + def is_main(self, other: ObjectType) -> bool: + """Check if the given object is the main object of the module.""" + return self.main_cls is other.cls + + def set_module_name(self, name: str): + self.name = name + self._main = self.get_object(to_pascal_case(name)) + + def __call__(self) -> None: + anyio.run(self._run) + + async def _run(self): + async with await dagger.connect(): + await self.serve() + + async def serve(self): + self.set_module_name(await dag.current_module().name()) + + try: + if parent_name := await dag.current_function_call().parent_name(): + result = await self._invoke(parent_name) + else: + result = await self._register() + except FunctionError as e: + logger.exception("Error while executing function") + await dag.current_function_call().return_error(dag.error(str(e))) + raise SystemExit(2) from None + + try: + output = json.dumps(result) + except (TypeError, ValueError) as e: + msg = f"Failed to serialize result: {e}" + raise InternalError(msg) from e + + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "output => %s", + textwrap.shorten(repr(output), 144), + ) + + await dag.current_function_call().return_value(dagger.JSON(output)) + + async def _register(self) -> dagger.ModuleID: # noqa: C901, PLR0912 + """Register the module and its types with the Dagger API.""" + mod = dag.module() + + # Object types + for obj_name, obj_type in self._objects.items(): + if self.is_main(obj_type): + # Only the main object's constructor is needed. + # It's the entrypoint to the module. + obj_type.get_constructor(self._converter) + + # Module description from main object's parent module + if desc := get_parent_module_doc(obj_type.cls): + mod = mod.with_description(desc) + + # Object/interface type + type_def = dag.type_def() + if obj_type.interface: + type_def = type_def.with_interface( + obj_name, + description=get_doc(obj_type.cls), + ) + else: + type_def = type_def.with_object( + obj_name, + description=get_doc(obj_type.cls), + ) + + # Object fields + if obj_type.fields: + types = typing.get_type_hints(obj_type.cls) + + for field_name, field in obj_type.fields.items(): + type_def = type_def.with_field( + field_name, + to_typedef(types[field.original_name]), + description=get_doc(field.return_type), + ) + + # Object/interface functions + for func_name, func in obj_type.functions.items(): + func_def = dag.function(func_name, to_typedef(func.return_type)) + + if doc := func.doc: + func_def = func_def.with_description(doc) + + for param in func.parameters.values(): + arg_def = to_typedef(param.resolved_type) + + if param.is_nullable: + arg_def = arg_def.with_optional(True) + + func_def = func_def.with_arg( + param.name, + arg_def, + description=param.doc, + default_value=param.default_value, + default_path=param.default_path, + ignore=param.ignore, + ) + + type_def = ( + type_def.with_constructor(func_def) + if func_name == "" + else type_def.with_function(func_def) + ) + + # Add object/interface to module + if obj_type.interface: + mod = mod.with_interface(type_def) + else: + mod = mod.with_object(type_def) + + # Enum types + for name, cls in self._enums.items(): + enum_def = dag.type_def().with_enum(name, description=get_doc(cls)) + member_docs = extract_enum_member_doc(cls) + + for member in cls: + # Get description from either description attribute or AST doc + description = getattr(member, "description", None) + if description is None: + description = member_docs.get(member.name) + + enum_def = enum_def.with_enum_member( + member.name, + value=str(member.value), + description=description, + ) + mod = mod.with_enum(enum_def) + + return await mod.id() + + async def _invoke(self, parent_name: str) -> Any: + """Invoke a function and return its result. + + This includes getting the call context from the API and deserializing data. + """ + fn_call = dag.current_function_call() + name = await fn_call.name() + parent_json = await fn_call.parent() + input_args = await fn_call.input_args() + + parent_state: dict[str, Any] = {} + if parent_json.strip(): + try: + parent_state = json.loads(parent_json) or {} + except ValueError as e: + msg = f"Unable to decode parent value `{parent_json}`: {e}" + raise FatalError(msg) from e + + inputs = {} + for arg in input_args: + # NB: These are already loaded by `input_args`, + # the await just returns the cached value. + arg_name = await arg.name() + arg_value = await arg.value() + try: + # Cattrs can decode JSON strings but use `json` directly + # for more granular control over the error. + inputs[arg_name] = json.loads(arg_value) + except ValueError as e: + msg = f"Unable to decode input argument `{arg_name}`: {e}" + raise InternalError(msg) from e + + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "invoke => %s", + { + "parent_name": parent_name, + "parent_json": textwrap.shorten(parent_json, 144), + "name": name, + "input_args": textwrap.shorten(repr(inputs), 144), + }, + ) + + result = await self.get_result( + parent_name, + parent_state, + name, + inputs, + ) + + if logger.isEnabledFor(logging.DEBUG): + logger.debug( + "result => %s", + textwrap.shorten(repr(result), 144), + ) + + return result + + async def get_structured_result( + self, + parent_name: str, + parent_state: Mapping[str, Any], + name: str, + raw_inputs: Mapping[str, Any], + ): + """Execute a function and return its result as a primitive value.""" + obj_type = self.get_object(parent_name) + + if name == "": + fn = obj_type.get_constructor(self._converter) + else: + parent = await self._get_parent_instance(obj_type.cls, parent_state) + + # NB: fields are not executed by the SDK, they're returned directly by + # the engine, but this is still useful for testing. + if name in obj_type.fields: + f = obj_type.fields[name] + result = getattr(parent, f.original_name) + return result, f.return_type + + fn = obj_type.get_bound_function(parent, name) + + inputs = await self._convert_inputs(fn.parameters, raw_inputs) + bound = fn.bind_arguments(**inputs) + + if logger.isEnabledFor(logging.DEBUG): + logger.debug("func => %s", repr(fn.signature)) + logger.debug("input args => %s", repr(raw_inputs)) + logger.debug("bound args => %s", repr(bound.arguments)) + + try: + result = await self.call(fn.wrapped, *bound.args, **bound.kwargs) + except Exception as e: + raise FunctionError(e) from e + + if inspect.iscoroutine(result): + msg = "Result is a coroutine. Did you forget to add async/await?" + raise UserError(msg) + + return result, fn.return_type + + async def get_result( + self, + parent_name: str, + parent_state: Mapping[str, Any], + name: str, + raw_inputs: Mapping[str, Any], + ) -> Any: + result, return_type = await self.get_structured_result( + parent_name, + parent_state, + name, + raw_inputs, + ) + if return_type is not None: + return await self.unstructure(result, return_type) + return None + + async def call(self, func: Func[P, R], *args: P.args, **kwargs: P.kwargs) -> R: + """Call a function and return its result.""" + return await await_maybe(func(*args, **kwargs)) + + async def structure(self, obj: Any, cl: type[T], origin: Any | None = None) -> T: + """Convert a primitive value to the expected type.""" + try: + return await asyncify(self._converter.structure, obj, cl) + except Exception as e: + raise ConversionError(e, origin=origin) from e + + async def unstructure(self, obj: Any, unstructure_as: Any) -> Awaitable[Any]: + """Convert a result to primitive values.""" + try: + return await asyncify(self._converter.unstructure, obj, unstructure_as) + except Exception as e: + msg = "Failed to convert result to primitive values" + raise ConversionError(e).as_user(msg) from e + + def get_object(self, name: str) -> ObjectType: + """Get the object type definition for the given name.""" + try: + return self._objects[name] + except KeyError as e: + msg = f"No `@dagger.object_type` decorated class named {name} was found" + raise UserError(msg) from e + + async def _get_parent_instance(self, cls: type[T], state: Mapping[str, Any]) -> T: + """Instantiate the parent object from its state.""" + try: + return await self.structure(state, cls) + except ConversionError as e: + msg = f"Failed to instantiate {cls.__name__}" + raise e.as_user(msg) from e + + async def _convert_inputs( + self, + params: Mapping[PythonName, Parameter], + inputs: Mapping[APIName, Any], + ) -> Mapping[PythonName, Any]: + """Convert arguments from lower level primitives to the expected types.""" + kwargs = {} + + # Convert arguments to the expected type. + for python_name, param in params.items(): + if param.name not in inputs: + if not param.is_optional: + msg = f"Missing required argument: {python_name}" + raise UserError(msg) + + if param.has_default: + continue + + # If the argument is optional and has no default, it's a nullable type. + # According to GraphQL spec, null is a valid value in case it's omitted. + value = inputs.get(param.name) + type_ = param.resolved_type + + try: + kwargs[python_name] = await self.structure(value, type_) + except ConversionError as e: + msg = f"Invalid argument `{param.name}`" + raise e.as_user(msg) from e + + if logger.isEnabledFor(logging.DEBUG): + logger.debug("structured args => %s", repr(kwargs)) + + return kwargs + + def field( + self, + *, + default: Callable[[], Any] | object = ..., + name: APIName | None = None, + init: bool = True, + ) -> Any: + """Exposes an attribute as a :py:class:`dagger.FieldTypeDef`. + + Should be used in a class decorated with :py:meth:`object_type`. + + Example usage:: + + @object_type + class Foo: + bar: str = field(default="foobar") + args: list[str] = field(default=list) + + + Parameters + ---------- + default: + The default value for the field or a 0-argument callable to + initialize a field's value. + name: + An alternative name for the API. Useful to avoid conflicts with + reserved words. + init: + Whether the field should be included in the constructor. + Defaults to `True`. + """ + kwargs = {} + optional = False + + if default is not ...: + optional = True + kwargs["default_factory" if callable(default) else "default"] = default + + return dataclasses.field( + metadata={FIELD_DEF_KEY: FieldDefinition(name, optional)}, + kw_only=True, + init=init, + repr=init, # default repr shows field as an __init__ argument + **kwargs, + ) + + @overload + def function( + self, + func: Func[P, R], + *, + name: APIName | None = None, + doc: str | None = None, + ) -> Func[P, R]: ... + + @overload + def function( + self, + *, + name: APIName | None = None, + doc: str | None = None, + ) -> Callable[[Func[P, R]], Func[P, R]]: ... + + def function( + self, + func: Func[P, R] | None = None, + *, + name: APIName | None = None, + doc: str | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Exposes a Python function as a :py:class:`dagger.Function`. + + Example usage:: + + @object_type + class Foo: + @function + def bar(self) -> str: + return "foobar" + + + Parameters + ---------- + func: + Should be an instance method in a class decorated with + :py:meth:`object_type`. Can be an async function or a class, + to use it's constructor. + name: + An alternative name for the API. Useful to avoid conflicts with + reserved words. + doc: + An alternative description for the API. Useful to use the + docstring for other purposes. + """ + + # TODO: Wrap appropriately + def wrapper(func: Func[P, R]) -> Func[P, R]: + # TODO: Use beartype to validate + assert callable(func), f"Expected a callable, got {type(func)}." + + meta = FunctionDefinition(name, doc) + + if inspect.isclass(func): + return Constructor(func, meta) + + setattr(func, FUNCTION_DEF_KEY, meta) + + return func + + return wrapper(func) if func else wrapper + + @overload + @dataclass_transform( + kw_only_default=True, + field_specifiers=(function, dataclasses.field, dataclasses.Field), + ) + def object_type(self, cls: T) -> T: ... + + @overload + @dataclass_transform( + kw_only_default=True, + field_specifiers=(function, dataclasses.field, dataclasses.Field), + ) + def object_type(self) -> Callable[[T], T]: ... + + def object_type(self, cls: T | None = None) -> T | Callable[[T], T]: + """Exposes a Python class as a :py:class:`dagger.ObjectTypeDef`. + + Used with :py:meth:`field` and :py:meth:`function` to expose + the object's members. + + Example usage:: + + import dagger + + + @dagger.object_type + class Foo: + @dagger.function + def bar(self) -> str: + return "foobar" + """ + + def wrapper(cls: T) -> T: + if not inspect.isclass(cls): + msg = f"Expected a class, got {type(cls)}" + raise UserError(msg) + + # Check for InitVar inside Annotated + # TODO: Maybe try to transform field automatically, but check + # with community first on how this is usually handled. + fields = inspect.get_annotations(cls) + for name, t in fields.items(): + if is_annotated(t) and isinstance(t.__origin__, dataclasses.InitVar): + # Pytohn 3.10 doesn't support `*meta* syntax + # in Annotated[init_t.type, *meta] + t.__origin__ = t.__origin__.type + msg = ( + f"Field `{name}` is an InitVar wrapped in Annotated. " + f"The correct syntax is: InitVar[{t}]" + ) + raise UserError(msg) + + wrapped = dataclasses.dataclass(kw_only=True)(cls) + return self._process_type(wrapped) + + return wrapper(cls) if cls else wrapper + + def _process_type(self, cls: T, interface: bool = False) -> T: + obj_def = ObjectType(cls, interface=interface) + + cls.__dagger_module__ = self + cls.__dagger_object_type__ = obj_def + self._objects[cls.__name__] = obj_def + + # Find all constructors from other objects, decorated with `@mod.function` + def _is_constructor(fn) -> typing.TypeGuard[Constructor]: + return isinstance(fn, Constructor) + + for _, fn in inspect.getmembers(cls, _is_constructor): + obj_def.functions[fn.name] = fn + + # Find all methods decorated with `@mod.function` + def _is_function(fn) -> typing.TypeGuard[Func]: + return hasattr(fn, FUNCTION_DEF_KEY) + + for _, meth in inspect.getmembers(cls, _is_function): + fn = Function( + meth, + meta=getattr(meth, FUNCTION_DEF_KEY), + origin=cls, + converter=self._converter, + ) + obj_def.functions[fn.name] = fn + + if interface: + return cls + + # Register hooks for renaming field names in `mod.field()`. + attr_overrides = {} + + # Find all fields exposed with `mod.field()`. + for field in dataclasses.fields(cls): + field_def: FieldDefinition | None + if field_def := field.metadata.get(FIELD_DEF_KEY, None): + r = Field( + meta=field_def, + original_name=field.name, + return_type=field.type, + ) + + if r.name != r.original_name: + attr_overrides[r.original_name] = cattrs.gen.override(rename=r.name) + + obj_def.fields[r.name] = r + + # Include fields that are excluded from the constructor. + self._converter.register_unstructure_hook( + cls, + cattrs.gen.make_dict_unstructure_fn( + cls, + self._converter, + _cattrs_include_init_false=True, + **attr_overrides, + ), + ) + self._converter.register_structure_hook( + cls, + cattrs.gen.make_dict_structure_fn( + cls, + self._converter, + _cattrs_include_init_false=True, + **attr_overrides, + ), + ) + + return cls + + @overload + def interface(self, cls: T) -> T: ... + + @overload + def interface(self) -> Callable[[T], T]: ... + + def interface(self, cls: T | None = None) -> T | Callable[[T], T]: + """Exposes a Python class as a :py:class:`dagger.InterfaceTypeDef`. + + Used with :py:meth:`function` to expose the interface's functions. + + Example usage:: + + import typing + import dagger + + + @dager.interface + class Foo(typing.Protocol): + @dagger.function + async def bar(self) -> str: ... + """ + + def wrapper(cls: T) -> T: + new_cls = typing.runtime_checkable(cls) + return self._process_type(new_cls, interface=True) + + return wrapper(cls) if cls else wrapper + + @overload + def enum_type(self, cls: T) -> T: ... + + @overload + def enum_type(self) -> Callable[[T], T]: ... + + def enum_type(self, cls: T | None = None) -> T | Callable[[T], T]: + '''Exposes a Python :py:class:`enum.Enum` as a :py:class:`dagger.EnumTypeDef`. + + Example usage:: + + import enum + import dagger + + + @dagger.enum_type + class Options(enum.Enum): + """Enumeration description""" + + ONE = "ONE" + """Description for the first value""" + + TWO = "TWO" + """Description for the second value""" + ''' + + def wrapper(cls: T) -> T: + if not inspect.isclass(cls): + msg = f"Expected an enum, got {type(cls)}" + raise UserError(msg) + + if not issubclass(cls, enum.Enum): + msg = f"Class {cls.__name__} is not an enum.Enum" + raise UserError(msg) + + cls = cast(T, enum.unique(cls)) + self._enums.setdefault(cls.__name__, cls) + + # Primitive enums get converted based on their primitive type rather + # than the custom hook for converting based on member names so we + # need to register the hooks for each specific class. Not necessary + # to add hooks for non-primitive enums because those are already + # handled by the general enum.Enum subclass check. + if is_primitive_enum(cls): + configure_converter_enum(self._converter, cls) + + return cls + + return wrapper(cls) if cls else wrapper diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_resolver.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_resolver.py new file mode 100644 index 0000000..c2da60f --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_resolver.py @@ -0,0 +1,246 @@ +import dataclasses +import inspect +import logging +from collections.abc import Callable +from functools import cached_property +from typing import ( + Any, + Generic, + ParamSpec, + TypeAlias, + cast, + get_type_hints, + overload, +) + +from beartype.door import TypeHint +from cattrs.preconf.json import JsonConverter, make_converter +from typing_extensions import Self, TypeVar, override + +from dagger.mod._arguments import Parameter +from dagger.mod._exceptions import FatalError, UserError +from dagger.mod._types import APIName, FieldDefinition, FunctionDefinition, PythonName +from dagger.mod._utils import ( + get_alt_constructor, + get_alt_name, + get_default_path, + get_doc, + get_ignore, + is_nullable, + is_self, + list_of, + normalize_name, +) + +logger = logging.getLogger(__name__) + +T = TypeVar("T") +R = TypeVar("R", infer_variance=True) +P = ParamSpec("P") + +Func: TypeAlias = Callable[P, R] + + +@dataclasses.dataclass(kw_only=True, slots=True) +class Field: + meta: FieldDefinition + original_name: PythonName + return_type: Any + name: APIName = dataclasses.field(init=False) + + def __post_init__(self): + self.name = self.meta.name or normalize_name(self.original_name) + + +@dataclasses.dataclass +class Function(Generic[P, R]): + wrapped: Func[P, R] + meta: FunctionDefinition = dataclasses.field(default_factory=FunctionDefinition) + original_name: PythonName = dataclasses.field(init=False) + origin: type | None = dataclasses.field(default=None) + converter: JsonConverter = dataclasses.field(default_factory=make_converter) + + def __post_init__(self): + self.original_name = self.wrapped.__name__ + + def __str__(self): + return repr(self.wrapped) + + @cached_property + def name(self): + return ( + self.meta.name + if self.meta.name is not None + else normalize_name(self.original_name) + ) + + @property + def doc(self): + """Return the description for the callable to invoke.""" + return self.meta.doc if self.meta.doc is not None else get_doc(self.wrapped) + + @cached_property + def type_hints(self): + return get_type_hints(self.wrapped) + + @cached_property + def signature(self): + return inspect.signature(self.wrapped, follow_wrapped=True) + + @cached_property + def parameters(self): + """Return the parameter annotations of the wrapped function. + + Keys are the Python parameter names. + """ + mapping: dict[PythonName, Parameter] = {} + + for param in self.signature.parameters.values(): + # Skip `self` parameter on instance methods. + # It will be added manually on `get_result`. + if param.name == "self": + continue + + if param.kind is inspect.Parameter.POSITIONAL_ONLY: + msg = "Positional-only parameters are not supported" + raise TypeError(msg) + + mapping[param.name] = self._make_parameter(param) + + return mapping + + def _make_parameter(self, param: inspect.Parameter) -> Parameter: + """Create a parameter object from an inspect.Parameter.""" + try: + # Use type_hints instead of param.annotation to get + # resolved forward references and stripped Annotated. + annotation = self.type_hints[param.name] + except KeyError: + logger.warning( + "Missing type annotation for parameter '%s'", + param.name, + ) + annotation = Any + + if isinstance(annotation, dataclasses.InitVar): + annotation: Any = annotation.type + + return Parameter( + name=get_alt_name(param.annotation) or normalize_name(param.name), + signature=param, + resolved_type=annotation, + is_nullable=is_nullable(TypeHint(annotation)), + doc=get_doc(param.annotation), + ignore=get_ignore(param.annotation), + default_path=get_default_path(param.annotation), + conv=self.converter, + ) + + @property + def return_type(self) -> Any: + """Return the resolved return type of the wrapped function.""" + try: + r = self.type_hints["return"] + except KeyError: + # When no return type is specified, assume None. + return None + + if self.origin: + if is_self(r): + return self.origin + + if (el := list_of(r)) and is_self(el): + return list[self.origin] + + return r + + def bind_parent(self, parent: object): + return dataclasses.replace( + self, + origin=parent.__class__, + wrapped=getattr(parent, self.original_name), + ) + + def bind_arguments(self, *args, **kwargs): + """Bind the function with the given arguments.""" + try: + bound = self.signature.bind(*args, **kwargs) + bound.apply_defaults() + except TypeError as e: + msg = f"Unable to bind arguments: {e}" + raise UserError(msg) from e + return bound + + +@dataclasses.dataclass(slots=True) +class Constructor(Function[P, R]): + _wrapped_cls: type[R] = dataclasses.field(init=False) + + def __post_init__(self): + assert inspect.isclass(self.wrapped) + self._wrapped_cls = self.wrapped + self.wrapped = cast( + Func[P, R], + get_alt_constructor(self._wrapped_cls) or self._wrapped_cls, + ) + + self.original_name = "" + + def __set_name__(self, _: type, name: str): + self.original_name = name + + @cached_property + @override + def type_hints(self): + if self.wrapped is self._wrapped_cls: + # make sure to get type hints for __init__ instead of class + # because the latter will get it from the dataclass's fields + # instead of the constructor's arguments. + return get_type_hints(self._wrapped_cls.__init__) + return get_type_hints(self.wrapped) + + @override + def bind_parent(self, parent: object): + return self + + @overload + def __get__(self, instance: None, owner: None = None) -> Self: ... + + @overload + def __get__(self, instance: object, owner: None = None) -> Func[P, R]: ... + + def __get__(self, instance: object | None, owner: None = None) -> Func[P, R] | Self: + return self if instance is None else self.wrapped + + @property + @override + def return_type(self) -> type[R] | type[None]: + return self._wrapped_cls + + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: + return self.wrapped(*args, **kwargs) + + +@dataclasses.dataclass(slots=True) +class ObjectType(Generic[T]): + cls: type[T] + interface: bool = False + fields: dict[APIName, Field] = dataclasses.field(default_factory=dict) + functions: dict[APIName, Function] = dataclasses.field(default_factory=dict) + + def get_constructor(self, conv: JsonConverter | None = None): + if "" not in self.functions: + self.functions[""] = Constructor(self.cls) + if conv is not None: + self.functions[""].converter = conv + return self.functions[""] + + def get_bound_function(self, parent: object, name: str) -> Function: + assert self.cls is parent.__class__ + try: + fn = self.functions[name] + except KeyError as e: + msg = f"No function '{name}' in object '{self.cls.__name__}'" + raise FatalError(msg) from e + + return fn.bind_parent(parent) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_types.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_types.py new file mode 100644 index 0000000..a661f34 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_types.py @@ -0,0 +1,51 @@ +import dataclasses +import warnings +from typing import TypeAlias + +from dagger.client import base + +PythonName: TypeAlias = str +APIName: TypeAlias = str +ContextPath: TypeAlias = str + + +@dataclasses.dataclass(slots=True, frozen=True) +class FieldDefinition: + name: APIName | None + optional: bool = False + + +@dataclasses.dataclass(slots=True, frozen=True) +class FunctionDefinition: + name: APIName | None = None + doc: str | None = None + + +class Enum(str, base.Enum): + """A string based :py:class:`enum.Enum` with optional descriptions for the values. + + Example usage:: + + class Options(dagger.Enum): + ONE = "ONE", "The first value" + TWO = "TWO" # no description + + .. deprecated:: + Use "enum.Enum" instead, with docstrings for descriptions. + """ + + __slots__ = ("description",) + + def __new__(cls, value, description=None): + warnings.warn( + ( + "Class 'dagger.Enum' is deprecated: Use 'enum.Enum' instead, " + "with docstrings for descriptions." + ), + DeprecationWarning, + stacklevel=4, + ) + obj = str.__new__(cls, value) + obj._value_ = value + obj.description = description + return obj diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_utils.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_utils.py new file mode 100644 index 0000000..27af9dd --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/_utils.py @@ -0,0 +1,316 @@ +import ast +import builtins +import contextlib +import dataclasses +import enum +import functools +import importlib +import importlib.util +import inspect +import operator +import typing +from collections.abc import Callable, Coroutine +from typing import Any, TypeAlias, TypeVar, cast + +import anyio +import anyio.from_thread +import anyio.to_thread +import typing_extensions +from beartype.door import TypeHint, UnionTypeHint, is_subhint +from cattrs.cols import is_sequence +from graphql.pyutils import snake_to_camel + +from dagger.client.base import Type +from dagger.mod._arguments import DefaultPath, Ignore, Name +from dagger.mod._types import ContextPath + +asyncify = anyio.to_thread.run_sync +syncify = anyio.from_thread.run + +T = TypeVar("T") + +AwaitableOrValue: TypeAlias = Coroutine[Any, Any, T] | T + +if typing.TYPE_CHECKING: + from dagger.mod._module import Module + from dagger.mod._resolver import ObjectType + + +async def await_maybe(value: AwaitableOrValue[T]) -> T: + return await value if inspect.iscoroutine(value) else cast(T, value) + + +def to_pascal_case(s: str) -> str: + """Convert a string to PascalCase.""" + return snake_to_camel(s.replace("-", "_")) + + +def to_camel_case(s: str) -> str: + """Convert a string to camelCase.""" + return snake_to_camel(s.replace("-", "_"), upper=False) + + +def normalize_name(name: str) -> str: + """Remove the last underscore, used to avoid conflicts with reserved words.""" + if name.endswith("_") and name[-2] != "_" and not name.startswith("_"): + return name.removesuffix("_") + return name + + +def get_meta(obj: Any, match: type[T]) -> T | None: + """Get metadata from an annotated type.""" + if is_initvar(obj): + return get_meta(obj.type, match) + if not is_annotated(obj): + return None + return next( + (arg for arg in reversed(typing.get_args(obj)) if isinstance(arg, match)), + None, + ) + + +def get_doc(obj: Any) -> str | None: + """Get the last Doc() in an annotated type or the docstring of an object.""" + if annotated := get_meta(obj, typing_extensions.Doc): + return annotated.documentation + + # Avoid getting docs from builtins. + # We're only interested in things we decorate. + if inspect.getmodule(obj) == builtins or ( + not inspect.isclass(obj) and not inspect.isroutine(obj) + ): + return None + + # Don't look in base classes (otherwise just use inspect.get_doc). + try: + doc = obj.__doc__ + except AttributeError: + return None + if not isinstance(doc, str): + return None + + # By default, a dataclass's __doc__ will be the signature of the class, + # not None. + if ( + doc + and dataclasses.is_dataclass(obj) + and doc.startswith(f"{obj.__name__}(") + and doc.endswith(")") + ): + return None + + return inspect.cleandoc(doc) + + +def get_ignore(obj: Any) -> list[str] | None: + """Get the last Ignore() of an annotated type.""" + meta = get_meta(obj, Ignore) + return meta.patterns if meta else None + + +def get_default_path(obj: Any) -> ContextPath | None: + """Get the last DefaultPath() of an annotated type.""" + meta = get_meta(obj, DefaultPath) + return meta.from_context if meta else None + + +def get_alt_name(annotation: type) -> str | None: + """Get an alternative name in last Name() of an annotated type.""" + return annotated.name if (annotated := get_meta(annotation, Name)) else None + + +def is_union(th: TypeHint) -> bool: + """Check if the unsubscripted part of a type is a Union.""" + return isinstance(th, UnionTypeHint) + + +def is_nullable(th: TypeHint) -> bool: + """Check if the annotation is SomeType | None. + + Does not support Annotated types. Use only on types that have been + resolved with get_type_hints. + """ + return th.is_bearable(None) + + +def non_null(th: TypeHint) -> TypeHint: + """Remove None from a union. + + Does not support Annotated types. Use only on types that have been + resolved with get_type_hints. + """ + if TypeHint(None) not in th: + return th + + args = (x for x in th.args if x is not type(None)) + return TypeHint(functools.reduce(operator.or_, args)) + + +_T = TypeVar("_T", bound=type) +Obj_T = TypeVar("Obj_T", bound=Type) + + +def is_self(annotation: type) -> typing.TypeGuard[type]: + """Check if an annotatino is a Self type.""" + # Typing extensions should return typing.Self if it exists (Python 3.11+) + return annotation is typing_extensions.Self + + +def is_annotated(annotation: type) -> bool: + """Check if the given type is an annotated type.""" + return typing.get_origin(annotation) in ( + typing.Annotated, + typing_extensions.Annotated, + ) + + +def strip_annotations(t: _T) -> _T: + """Strip the annotations from a given type.""" + return strip_annotations(typing.get_args(t)[0]) if is_annotated(t) else t + + +def is_list_type(t: Any) -> typing.TypeGuard[typing.Sequence]: + """Check if an annotation represents a list.""" + return is_sequence(t) + + +def list_of(t: typing.Any) -> type | None: + """Retrieve a list's element type or None if not a list.""" + if not is_list_type(t): + return None + th = TypeHint(t) + try: + return th.args[0] + except IndexError: + msg = ( + "Expected sequence type to be subscripted " + f"with 1 subtype, got {len(th)}: {th.hint!r}" + ) + raise TypeError(msg) from None + + +def is_list_of(v: Any, t: _T) -> typing.TypeGuard[typing.Sequence[_T]]: + """Check if the annotation is a list of the given type.""" + return is_subhint(v, typing.Sequence[t]) + + +def is_object_list_type(t: Any): + """Check if the annotation is a list of an object client binding.""" + return is_list_of(t, Type) + + +def object_list_of(t: Any) -> type[Type] | None: + """Retrive a list's element type or None if not a list of objects.""" + if is_object_list_type(t) and (el := list_of(t)): + return cast(type[Type], el) + return None + + +def is_dagger_object_type(t: typing.Any) -> typing.TypeGuard[type[Type]]: + """Check if the annotation is an object client binding.""" + return is_subclass(t, Type) + + +def is_dagger_interface_type(t: typing.Any) -> typing.TypeGuard[type]: + """Check if the annotation is an interface definition.""" + obj = get_object_type(t) + return obj is not None and obj.interface and is_protocol(t) + + +def is_subclass(obj: type, bases) -> typing.TypeGuard[type]: + """A safe version of issubclass (won't raise).""" + try: + return issubclass(obj, bases) + except TypeError: + return False + + +def is_protocol(t: Any) -> typing.TypeGuard[type]: + """Check if the given type is a Protocol subclass.""" + return is_subclass(t, typing.Protocol) and getattr(t, "_is_protocol", False) + + +def is_initvar(annotation: type) -> typing.TypeGuard[dataclasses.InitVar]: + """Check if the given type is a dataclasses.InitVar.""" + return annotation is dataclasses.InitVar or type(annotation) is dataclasses.InitVar + + +def is_mod_object_type(cls) -> bool: + """Check if the given class was decorated with @object_type.""" + return hasattr(cls, "__dagger_object_type__") + + +def get_object_type(cls) -> "ObjectType | None": + """Return the decorated object_type metadata on a class.""" + return getattr(cls, "__dagger_object_type__", None) + + +def get_module(cls) -> "Module | None": + """Return the Module instance on a decorated object_type class.""" + return getattr(cls, "__dagger_module__", None) + + +def get_alt_constructor(cls: type[T]) -> Callable[..., T] | None: + """Get classmethod named `create` from object type.""" + if inspect.isclass(cls) and is_mod_object_type(cls): + fn = getattr(cls, "create", None) + if inspect.ismethod(fn) and fn.__self__ is cls: + return fn + return None + + +def get_parent_module_doc(obj: type) -> str | None: + """Get the docstring of the parent module.""" + spec = importlib.util.find_spec(obj.__module__) + if not spec or not spec.parent: + return None + mod = importlib.import_module(spec.parent) + return inspect.getdoc(mod) + + +def _extract_doc_from_next_stmt(class_body: list[ast.stmt], index: int) -> str | None: + """Extract docstring from the statement following the given index.""" + next_idx = index + 1 + if next_idx >= len(class_body): + return None + + next_stmt = class_body[next_idx] + if ( + isinstance(next_stmt, ast.Expr) + and isinstance(next_stmt.value, ast.Constant) + and isinstance(next_stmt.value.value, str) + ): + return next_stmt.value.value.strip() + return None + + +def extract_enum_member_doc(cls: type[enum.Enum]) -> dict[str, str]: + """Extract docstrings for enum members by parsing the AST.""" + member_docs: dict[str, str] = {} + + with contextlib.suppress(OSError, TypeError, SyntaxError): + source = inspect.getsource(cls) + tree = ast.parse(source) + + # Find the class definition + class_node = None + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef) and node.name == cls.__name__: + class_node = node + break + + if class_node is not None: + # Look for assignments followed by string literals + for i, stmt in enumerate(class_node.body): + if not isinstance(stmt, ast.Assign): + continue + + # Check if this is an enum member assignment + for target in stmt.targets: + if isinstance(target, ast.Name): + member_name = target.id + doc = _extract_doc_from_next_stmt(class_node.body, i) + if doc: + member_docs[member_name] = doc + + return member_docs diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/mod/cli.py b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/cli.py new file mode 100644 index 0000000..903aa54 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/mod/cli.py @@ -0,0 +1,95 @@ +"""Command line interface for the dagger extension runtime.""" + +import importlib +import importlib.metadata +import importlib.util +import logging +import os +import sys +import typing + +import rich.traceback +from rich.console import Console + +from dagger import telemetry +from dagger.log import configure_logging +from dagger.mod._exceptions import FatalError, UserError +from dagger.mod._module import Module + +ENTRY_POINT_NAME: typing.Final[str] = "main_object" +ENTRY_POINT_GROUP: typing.Final[str] = typing.cast(str, __package__) + +IMPORT_PKG = os.getenv("DAGGER_DEFAULT_PYTHON_PACKAGE", "main") +MAIN_OBJECT = os.getenv("DAGGER_MAIN_OBJECT", "Main") + +errors = Console(stderr=True, style="red") +logger = logging.getLogger(__name__) + + +def app(): + """Entrypoint for a Python Dagger module.""" + telemetry.initialize() + + # TODO: Create custom exception hook to control exit code. + rich.traceback.install( + console=errors, + show_locals=logger.isEnabledFor(logging.DEBUG), + suppress=[ + "asyncio", + "anyio", + ], + ) + try: + load_module()() + except FatalError as e: + logger.exception("Fatal error") + e.rich_print() + sys.exit(1) + finally: + telemetry.shutdown() + + +def load_module() -> Module: + """Load the dagger.Module instance via the main object entry point.""" + try: + cls: type = get_entry_point().load() + except (ModuleNotFoundError, AttributeError) as e: + # If the main module isn't found the user won't be able to set debug level. + # TODO: Allow setting debug level with a pyproject.toml setting. + if not logger.isEnabledFor(logging.DEBUG): + configure_logging(logging.DEBUG) + msg = ( + "Main object not found. You can configure it explicitly by adding " + "an entry point to your pyproject.toml file. For example:\n" + "\n" + f'[project.entry-points."{ENTRY_POINT_GROUP}"]\n' + f"{ENTRY_POINT_NAME} = '{IMPORT_PKG}:{MAIN_OBJECT}'\n" + ) + raise UserError(msg) from e + try: + return cls.__dagger_module__ + except AttributeError as e: + msg = "The main object must be a class decorated with @dagger.object_type" + raise UserError(msg) from e + + +def get_entry_point() -> importlib.metadata.EntryPoint: + """Get the entry point for the main object.""" + sel = importlib.metadata.entry_points( + group=ENTRY_POINT_GROUP, + name=ENTRY_POINT_NAME, + ) + if ep := next(iter(sel), None): + return ep + + import_pkg = IMPORT_PKG + + # Fallback for modules that still use the "main" package name. + if not importlib.util.find_spec(import_pkg): + import_pkg = "main" + + return importlib.metadata.EntryPoint( + group=ENTRY_POINT_GROUP, + name=ENTRY_POINT_NAME, + value=f"{import_pkg}:{MAIN_OBJECT}", + ) diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/py.typed b/content/en/docs/04/solution/ci/sdk/src/dagger/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/content/en/docs/04/solution/ci/sdk/src/dagger/telemetry.py b/content/en/docs/04/solution/ci/sdk/src/dagger/telemetry.py new file mode 100644 index 0000000..fcdde0b --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/src/dagger/telemetry.py @@ -0,0 +1,213 @@ +import logging +import os +from collections.abc import Callable +from typing import Final, Literal + +from opentelemetry import context, propagate, trace +from opentelemetry.environment_variables import ( + OTEL_LOGS_EXPORTER, + OTEL_METRICS_EXPORTER, + OTEL_PYTHON_TRACER_PROVIDER, + OTEL_TRACES_EXPORTER, +) +from opentelemetry.sdk import trace as sdktrace +from opentelemetry.sdk._configuration import _BaseConfigurator as _BaseSDKConfigurator +from opentelemetry.sdk._configuration import ( + _get_exporter_names, + _import_exporters, + _init_logging, + _init_metrics, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_INSECURE, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, + OTEL_EXPORTER_OTLP_LOGS_INSECURE, + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, + OTEL_EXPORTER_OTLP_METRICS_INSECURE, + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, + OTEL_EXPORTER_OTLP_TRACES_INSECURE, + OTEL_SDK_DISABLED, + OTEL_SERVICE_NAME, +) +from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanExporter +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace import get_tracer_provider, propagation + +__all__ = [ + "get_tracer", + "initialize", + "otel_configured", + "otel_enabled", + "shutdown", +] + +SERVICE_NAME: Final = "dagger-python-sdk" + +logger = logging.getLogger(__name__) + + +def initialize(): + """Configure telemetry.""" + _DaggerPropagationConfigurator().configure() + _DaggerOtelConfigurator().configure() + + +def get_tracer() -> trace.Tracer: + """Returns a tracer to use with Dagger.""" + initialize() + return trace.get_tracer( + "dagger.io/sdk.python", + schema_url=SpanAttributes.SCHEMA_URL, + ) + + +def shutdown(): + """Process all spans that have not yet been processed.""" + provider = get_tracer_provider() + + if isinstance(provider, sdktrace.TracerProvider): + provider.force_flush() + # shutdown is called automatically on exit, we just need the forced + # flush, but might as well shutdown now too + provider.shutdown() + + +def otel_configured() -> bool: + """Checks for OpenTelemetry configuration via OTEL_ environment variables.""" + return any(k for k in os.environ if k.startswith("OTEL_")) + + +def otel_enabled() -> bool: + """Checks whether OpenTelemetry instrumentation is not disabled.""" + return os.getenv(OTEL_SDK_DISABLED, "").strip().lower() != "true" + + +def live_traces_enabled() -> bool: + return os.getenv("OTEL_EXPORTER_OTLP_TRACES_LIVE") is not None + + +class _BaseConfigurator(_BaseSDKConfigurator): + """Base configurator singleton, that ensures configuration only happens once.""" + + _is_configured: bool = False + + def configure(self, **kwargs): + if self._is_configured: + return + + super().configure(**kwargs) + self._is_configured = True + + +class _DaggerPropagationConfigurator(_BaseConfigurator): + # NB: This configuration should be applied before any other telemetry + # code runs, to ensure the context has the right traceparent. + def _configure(self, **kwargs): + if parent := os.getenv("TRACEPARENT"): + if propagation.get_current_span().get_span_context().is_valid: + return + + logger.debug("Found TRACEPARENT", extra={"value": parent}) + ctx = propagate.extract({"traceparent": parent}) + context.attach(ctx) + + +class LiveSpanProcessor(sdktrace.SynchronousMultiSpanProcessor): + """Live span processor implementation. + + It's a SpanProcessor whose on_start calls on_end on the underlying + SpanProcessor in order to send live telemetry. + """ + + def __init__(self, exp: SpanExporter): + super().__init__() + self.add_span_processor(BatchSpanProcessor(exp, schedule_delay_millis=100)) + + def on_start(self, span: sdktrace.Span, parent_context=None) -> None: + return self.on_end(span) + + +def _init_tracing(exporters: dict[str, type[SpanExporter]]): + # By default this is a NoOpTracerProvider, unless OTEL_PYTHON_TRACER_PROVIDER + # is set, which is done in _prepare_env. + provider = get_tracer_provider() + + if isinstance(provider, sdktrace.TracerProvider): + for exporter_class in exporters.values(): + proc_cls = ( + LiveSpanProcessor if live_traces_enabled() else BatchSpanProcessor + ) + provider.add_span_processor(proc_cls(exporter_class())) + + +class _DaggerOtelConfigurator(_BaseConfigurator): + # NB: This is based on opentelemetry.sdk._configuration._OtelSDKConfigurator + # which is experimental. Instead of importing just the configurator, we're + # importing several private functions because we need more control over + # the initialization of tracing exporters but still want to reuse as + # much of the existing logic as possible. Need to keep an eye on upstream + # changes though. + def _configure(self, **kwargs): + if not otel_configured(): + logger.debug("Telemetry not configured") + return + + if not otel_enabled(): + logger.debug("Telemetry disabled") + return + + logger.debug("Initializing telemetry") + self._prepare_env() + self._initialize() + logger.debug("Telemetry initialized") + + def _prepare_env(self): + """Prepare environment variables for auto-configuring the SDK.""" + # When a Resource is created, it defaults to the following env var + # for the service name. + os.environ.setdefault(OTEL_SERVICE_NAME, SERVICE_NAME) + + # The default is a NoOpProvider. + os.environ.setdefault(OTEL_PYTHON_TRACER_PROVIDER, "sdk_tracer_provider") + + # TODO: The following env vars should be set by the shim rather than in the SDK. + + for exporter in ( + OTEL_TRACES_EXPORTER, + OTEL_LOGS_EXPORTER, + OTEL_METRICS_EXPORTER, + ): + os.environ.setdefault(exporter, "otlp") + + _vars = { + OTEL_EXPORTER_OTLP_ENDPOINT: OTEL_EXPORTER_OTLP_INSECURE, + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: OTEL_EXPORTER_OTLP_METRICS_INSECURE, + OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: OTEL_EXPORTER_OTLP_LOGS_INSECURE, + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: OTEL_EXPORTER_OTLP_TRACES_INSECURE, + } + for endpoint, insecure in _vars.items(): + if os.getenv(endpoint, "").startswith("http://"): + os.environ.setdefault(insecure, "true") + + def _initialize(self): + # NB: Fixed order, based on _import_exporters arguments. + initializers: dict[Literal["traces", "metrics", "logs"], Callable] = { + "traces": _init_tracing, + "metrics": _init_metrics, + "logs": _init_logging, + } + all_exporters = _import_exporters( + *(_get_exporter_names(t) for t in initializers) + ) + + for (kind, init), exporters in zip( + initializers.items(), all_exporters, strict=True + ): + logger.debug( + "Initializing %s telemetry with exporters: %s", + kind, + ", ".join(exporters) if exporters else "none", + ) + + init(exporters) diff --git a/content/en/docs/04/solution/ci/sdk/uv.lock b/content/en/docs/04/solution/ci/sdk/uv.lock new file mode 100644 index 0000000..3a43809 --- /dev/null +++ b/content/en/docs/04/solution/ci/sdk/uv.lock @@ -0,0 +1,1498 @@ +version = 1 +revision = 2 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[manifest] +members = [ + "codegen", + "dagger-io", +] + +[[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.12.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/2d/27e4347660723738b01daa3f5769d56170f232bf4695dd4613340da135bb/aiohttp-3.12.13-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5421af8f22a98f640261ee48aae3a37f0c41371e99412d55eaf2f8a46d5dad29", size = 702090, upload-time = "2025-06-14T15:12:58.938Z" }, + { url = "https://files.pythonhosted.org/packages/10/0b/4a8e0468ee8f2b9aff3c05f2c3a6be1dfc40b03f68a91b31041d798a9510/aiohttp-3.12.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fcda86f6cb318ba36ed8f1396a6a4a3fd8f856f84d426584392083d10da4de0", size = 478440, upload-time = "2025-06-14T15:13:02.981Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c8/2086df2f9a842b13feb92d071edf756be89250f404f10966b7bc28317f17/aiohttp-3.12.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cd71c9fb92aceb5a23c4c39d8ecc80389c178eba9feab77f19274843eb9412d", size = 466215, upload-time = "2025-06-14T15:13:04.817Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3d/d23e5bd978bc8012a65853959b13bd3b55c6e5afc172d89c26ad6624c52b/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ebf1aca12845066c963016655dac897651e1544f22a34c9b461ac3b4b1d3aa", size = 1648271, upload-time = "2025-06-14T15:13:06.532Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/e00122447bb137591c202786062f26dd383574c9f5157144127077d5733e/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:893a4639694c5b7edd4bdd8141be296042b6806e27cc1d794e585c43010cc294", size = 1622329, upload-time = "2025-06-14T15:13:08.394Z" }, + { url = "https://files.pythonhosted.org/packages/04/01/caef70be3ac38986969045f21f5fb802ce517b3f371f0615206bf8aa6423/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:663d8ee3ffb3494502ebcccb49078faddbb84c1d870f9c1dd5a29e85d1f747ce", size = 1694734, upload-time = "2025-06-14T15:13:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/3f/15/328b71fedecf69a9fd2306549b11c8966e420648a3938d75d3ed5bcb47f6/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0f8f6a85a0006ae2709aa4ce05749ba2cdcb4b43d6c21a16c8517c16593aabe", size = 1737049, upload-time = "2025-06-14T15:13:11.672Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7a/d85866a642158e1147c7da5f93ad66b07e5452a84ec4258e5f06b9071e92/aiohttp-3.12.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1582745eb63df267c92d8b61ca655a0ce62105ef62542c00a74590f306be8cb5", size = 1641715, upload-time = "2025-06-14T15:13:13.548Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/3588800d5d2f5f3e1cb6e7a72747d1abc1e67ba5048e8b845183259c2e9b/aiohttp-3.12.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d59227776ee2aa64226f7e086638baa645f4b044f2947dbf85c76ab11dcba073", size = 1581836, upload-time = "2025-06-14T15:13:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/c913332899a916d85781aa74572f60fd98127449b156ad9c19e23135b0e4/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06b07c418bde1c8e737d8fa67741072bd3f5b0fb66cf8c0655172188c17e5fa6", size = 1625685, upload-time = "2025-06-14T15:13:17.163Z" }, + { url = "https://files.pythonhosted.org/packages/4c/34/26cded195f3bff128d6a6d58d7a0be2ae7d001ea029e0fe9008dcdc6a009/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9445c1842680efac0f81d272fd8db7163acfcc2b1436e3f420f4c9a9c5a50795", size = 1636471, upload-time = "2025-06-14T15:13:19.086Z" }, + { url = "https://files.pythonhosted.org/packages/19/21/70629ca006820fccbcec07f3cd5966cbd966e2d853d6da55339af85555b9/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:09c4767af0b0b98c724f5d47f2bf33395c8986995b0a9dab0575ca81a554a8c0", size = 1611923, upload-time = "2025-06-14T15:13:20.997Z" }, + { url = "https://files.pythonhosted.org/packages/31/80/7fa3f3bebf533aa6ae6508b51ac0de9965e88f9654fa679cc1a29d335a79/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3854fbde7a465318ad8d3fc5bef8f059e6d0a87e71a0d3360bb56c0bf87b18a", size = 1691511, upload-time = "2025-06-14T15:13:22.54Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7a/359974653a3cdd3e9cee8ca10072a662c3c0eb46a359c6a1f667b0296e2f/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2332b4c361c05ecd381edb99e2a33733f3db906739a83a483974b3df70a51b40", size = 1714751, upload-time = "2025-06-14T15:13:24.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/24/0aa03d522171ce19064347afeefadb008be31ace0bbb7d44ceb055700a14/aiohttp-3.12.13-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1561db63fa1b658cd94325d303933553ea7d89ae09ff21cc3bcd41b8521fbbb6", size = 1643090, upload-time = "2025-06-14T15:13:26.231Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/7d4b0026a41e4b467e143221c51b279083b7044a4b104054f5c6464082ff/aiohttp-3.12.13-cp310-cp310-win32.whl", hash = "sha256:a0be857f0b35177ba09d7c472825d1b711d11c6d0e8a2052804e3b93166de1ad", size = 427526, upload-time = "2025-06-14T15:13:27.988Z" }, + { url = "https://files.pythonhosted.org/packages/17/de/34d998da1e7f0de86382160d039131e9b0af1962eebfe53dda2b61d250e7/aiohttp-3.12.13-cp310-cp310-win_amd64.whl", hash = "sha256:fcc30ad4fb5cb41a33953292d45f54ef4066746d625992aeac33b8c681173178", size = 450734, upload-time = "2025-06-14T15:13:29.394Z" }, + { url = "https://files.pythonhosted.org/packages/6a/65/5566b49553bf20ffed6041c665a5504fb047cefdef1b701407b8ce1a47c4/aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c", size = 709401, upload-time = "2025-06-14T15:13:30.774Z" }, + { url = "https://files.pythonhosted.org/packages/14/b5/48e4cc61b54850bdfafa8fe0b641ab35ad53d8e5a65ab22b310e0902fa42/aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358", size = 481669, upload-time = "2025-06-14T15:13:32.316Z" }, + { url = "https://files.pythonhosted.org/packages/04/4f/e3f95c8b2a20a0437d51d41d5ccc4a02970d8ad59352efb43ea2841bd08e/aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014", size = 469933, upload-time = "2025-06-14T15:13:34.104Z" }, + { url = "https://files.pythonhosted.org/packages/41/c9/c5269f3b6453b1cfbd2cfbb6a777d718c5f086a3727f576c51a468b03ae2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7", size = 1740128, upload-time = "2025-06-14T15:13:35.604Z" }, + { url = "https://files.pythonhosted.org/packages/6f/49/a3f76caa62773d33d0cfaa842bdf5789a78749dbfe697df38ab1badff369/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013", size = 1688796, upload-time = "2025-06-14T15:13:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e4/556fccc4576dc22bf18554b64cc873b1a3e5429a5bdb7bbef7f5d0bc7664/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47", size = 1787589, upload-time = "2025-06-14T15:13:38.745Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3d/d81b13ed48e1a46734f848e26d55a7391708421a80336e341d2aef3b6db2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a", size = 1826635, upload-time = "2025-06-14T15:13:40.733Z" }, + { url = "https://files.pythonhosted.org/packages/75/a5/472e25f347da88459188cdaadd1f108f6292f8a25e62d226e63f860486d1/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc", size = 1729095, upload-time = "2025-06-14T15:13:42.312Z" }, + { url = "https://files.pythonhosted.org/packages/b9/fe/322a78b9ac1725bfc59dfc301a5342e73d817592828e4445bd8f4ff83489/aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7", size = 1666170, upload-time = "2025-06-14T15:13:44.884Z" }, + { url = "https://files.pythonhosted.org/packages/7a/77/ec80912270e231d5e3839dbd6c065472b9920a159ec8a1895cf868c2708e/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b", size = 1714444, upload-time = "2025-06-14T15:13:46.401Z" }, + { url = "https://files.pythonhosted.org/packages/21/b2/fb5aedbcb2b58d4180e58500e7c23ff8593258c27c089abfbcc7db65bd40/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9", size = 1709604, upload-time = "2025-06-14T15:13:48.377Z" }, + { url = "https://files.pythonhosted.org/packages/e3/15/a94c05f7c4dc8904f80b6001ad6e07e035c58a8ebfcc15e6b5d58500c858/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a", size = 1689786, upload-time = "2025-06-14T15:13:50.401Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/0d2e618388f7a7a4441eed578b626bda9ec6b5361cd2954cfc5ab39aa170/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d", size = 1783389, upload-time = "2025-06-14T15:13:51.945Z" }, + { url = "https://files.pythonhosted.org/packages/a6/6b/6986d0c75996ef7e64ff7619b9b7449b1d1cbbe05c6755e65d92f1784fe9/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2", size = 1803853, upload-time = "2025-06-14T15:13:53.533Z" }, + { url = "https://files.pythonhosted.org/packages/21/65/cd37b38f6655d95dd07d496b6d2f3924f579c43fd64b0e32b547b9c24df5/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3", size = 1716909, upload-time = "2025-06-14T15:13:55.148Z" }, + { url = "https://files.pythonhosted.org/packages/fd/20/2de7012427dc116714c38ca564467f6143aec3d5eca3768848d62aa43e62/aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd", size = 427036, upload-time = "2025-06-14T15:13:57.076Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b6/98518bcc615ef998a64bef371178b9afc98ee25895b4f476c428fade2220/aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9", size = 451427, upload-time = "2025-06-14T15:13:58.505Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" }, + { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" }, + { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" }, + { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" }, + { url = "https://files.pythonhosted.org/packages/11/0f/db19abdf2d86aa1deec3c1e0e5ea46a587b97c07a16516b6438428b3a3f8/aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938", size = 694910, upload-time = "2025-06-14T15:14:30.604Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/0ab551e1b5d7f1339e2d6eb482456ccbe9025605b28eed2b1c0203aaaade/aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace", size = 472566, upload-time = "2025-06-14T15:14:32.275Z" }, + { url = "https://files.pythonhosted.org/packages/34/3f/6b7d336663337672d29b1f82d1f252ec1a040fe2d548f709d3f90fa2218a/aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb", size = 464856, upload-time = "2025-06-14T15:14:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/26/7f/32ca0f170496aa2ab9b812630fac0c2372c531b797e1deb3deb4cea904bd/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7", size = 1703683, upload-time = "2025-06-14T15:14:36.034Z" }, + { url = "https://files.pythonhosted.org/packages/ec/53/d5513624b33a811c0abea8461e30a732294112318276ce3dbf047dbd9d8b/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b", size = 1684946, upload-time = "2025-06-14T15:14:38Z" }, + { url = "https://files.pythonhosted.org/packages/37/72/4c237dd127827b0247dc138d3ebd49c2ded6114c6991bbe969058575f25f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177", size = 1737017, upload-time = "2025-06-14T15:14:39.951Z" }, + { url = "https://files.pythonhosted.org/packages/0d/67/8a7eb3afa01e9d0acc26e1ef847c1a9111f8b42b82955fcd9faeb84edeb4/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef", size = 1786390, upload-time = "2025-06-14T15:14:42.151Z" }, + { url = "https://files.pythonhosted.org/packages/48/19/0377df97dd0176ad23cd8cad4fd4232cfeadcec6c1b7f036315305c98e3f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103", size = 1708719, upload-time = "2025-06-14T15:14:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/61/97/ade1982a5c642b45f3622255173e40c3eed289c169f89d00eeac29a89906/aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da", size = 1622424, upload-time = "2025-06-14T15:14:45.945Z" }, + { url = "https://files.pythonhosted.org/packages/99/ab/00ad3eea004e1d07ccc406e44cfe2b8da5acb72f8c66aeeb11a096798868/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d", size = 1675447, upload-time = "2025-06-14T15:14:47.911Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fe/74e5ce8b2ccaba445fe0087abc201bfd7259431d92ae608f684fcac5d143/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041", size = 1707110, upload-time = "2025-06-14T15:14:50.334Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c4/39af17807f694f7a267bd8ab1fbacf16ad66740862192a6c8abac2bff813/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1", size = 1649706, upload-time = "2025-06-14T15:14:52.378Z" }, + { url = "https://files.pythonhosted.org/packages/38/e8/f5a0a5f44f19f171d8477059aa5f28a158d7d57fe1a46c553e231f698435/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1", size = 1725839, upload-time = "2025-06-14T15:14:54.617Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ac/81acc594c7f529ef4419d3866913f628cd4fa9cab17f7bf410a5c3c04c53/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911", size = 1759311, upload-time = "2025-06-14T15:14:56.597Z" }, + { url = "https://files.pythonhosted.org/packages/38/0d/aabe636bd25c6ab7b18825e5a97d40024da75152bec39aa6ac8b7a677630/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3", size = 1708202, upload-time = "2025-06-14T15:14:58.598Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ab/561ef2d8a223261683fb95a6283ad0d36cb66c87503f3a7dde7afe208bb2/aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd", size = 420794, upload-time = "2025-06-14T15:15:00.939Z" }, + { url = "https://files.pythonhosted.org/packages/9d/47/b11d0089875a23bff0abd3edb5516bcd454db3fefab8604f5e4b07bd6210/aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706", size = 446735, upload-time = "2025-06-14T15:15:02.858Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b5/6d55e80f6d8a08ce22b982eafa278d823b541c925f11ee774b0b9c43473d/aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54", size = 19424, upload-time = "2024-12-13T17:10:40.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/6a/bc7e17a3e87a2985d3e8f4da4cd0f481060eb78fb08596c42be62c90a4d9/aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5", size = 7597, upload-time = "2024-12-13T17:10:38.469Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[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 = "beartype" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/f9/21e5a9c731e14f08addd53c71fea2e70794e009de5b98e6a2c3d2f3015d6/beartype-0.21.0.tar.gz", hash = "sha256:f9a5078f5ce87261c2d22851d19b050b64f6a805439e8793aecf01ce660d3244", size = 1437066, upload-time = "2025-05-22T05:09:27.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/31/87045d1c66ee10a52486c9d2047bc69f00f2689f69401bb1e998afb4b205/beartype-0.21.0-py3-none-any.whl", hash = "sha256:b6a1bd56c72f31b0a496a36cc55df6e2f475db166ad07fa4acc7e74f4c7f34c0", size = 1191340, upload-time = "2025-05-22T05:09:24.606Z" }, +] + +[[package]] +name = "cattrs" +version = "25.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/2b/561d78f488dcc303da4639e02021311728fb7fda8006dd2835550cddd9ed/cattrs-25.1.1.tar.gz", hash = "sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c", size = 435016, upload-time = "2025-06-04T20:27:15.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/b0/215274ef0d835bbc1056392a367646648b6084e39d489099959aefcca2af/cattrs-25.1.1-py3-none-any.whl", hash = "sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064", size = 69386, upload-time = "2025-06-04T20:27:13.969Z" }, +] + +[[package]] +name = "certifi" +version = "2025.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, + { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, + { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, + { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, + { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, + { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, + { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, + { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, + { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, + { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, + { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, + { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, + { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, + { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, + { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[package]] +name = "codegen" +version = "0.0.0" +source = { editable = "codegen" } +dependencies = [ + { name = "graphql-core" }, +] + +[package.metadata] +requires-dist = [{ name = "graphql-core", specifier = ">=3.2.3" }] + +[[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 = "dagger-io" +source = { editable = "." } +dependencies = [ + { name = "anyio" }, + { name = "beartype" }, + { name = "cattrs" }, + { name = "gql", extra = ["httpx"] }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "platformdirs" }, + { name = "rich" }, + { name = "typing-extensions" }, +] + +[package.dev-dependencies] +dev = [ + { name = "aiohttp" }, + { name = "codegen" }, + { name = "mypy" }, + { name = "pytest" }, + { name = "pytest-httpx" }, + { name = "pytest-mock" }, + { name = "pytest-subprocess" }, + { name = "ruff" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-rtd-theme" }, +] + +[package.metadata] +requires-dist = [ + { name = "anyio", specifier = ">=3.6.2" }, + { name = "beartype", specifier = ">=0.18.2" }, + { name = "cattrs", specifier = ">=25.1.0" }, + { name = "gql", extras = ["httpx"], specifier = ">=3.5.0" }, + { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.23.0" }, + { name = "opentelemetry-sdk", specifier = ">=1.23.0" }, + { name = "platformdirs", specifier = ">=2.6.2" }, + { name = "rich", specifier = ">=10.11.0" }, + { name = "typing-extensions", specifier = ">=4.13.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "aiohttp", specifier = ">=3.9.3" }, + { name = "codegen", editable = "codegen" }, + { name = "mypy", specifier = ">=1.8.0" }, + { name = "pytest", specifier = ">=8.0.2" }, + { name = "pytest-httpx", specifier = ">=0.30.0" }, + { name = "pytest-mock", specifier = ">=3.12.0" }, + { name = "pytest-subprocess", specifier = ">=1.5.0" }, + { name = "ruff", specifier = ">=0.3.4" }, + { name = "sphinx", specifier = ">=7.2.6" }, + { name = "sphinx-rtd-theme", specifier = ">=2.0.0" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/36/0da0a49409f6b47cc2d060dc8c9040b897b5902a8a4e37d9bc1deb11f680/frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a", size = 81304, upload-time = "2025-06-09T22:59:46.226Z" }, + { url = "https://files.pythonhosted.org/packages/77/f0/77c11d13d39513b298e267b22eb6cb559c103d56f155aa9a49097221f0b6/frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61", size = 47735, upload-time = "2025-06-09T22:59:48.133Z" }, + { url = "https://files.pythonhosted.org/packages/37/12/9d07fa18971a44150593de56b2f2947c46604819976784bcf6ea0d5db43b/frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d", size = 46775, upload-time = "2025-06-09T22:59:49.564Z" }, + { url = "https://files.pythonhosted.org/packages/70/34/f73539227e06288fcd1f8a76853e755b2b48bca6747e99e283111c18bcd4/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e", size = 224644, upload-time = "2025-06-09T22:59:51.35Z" }, + { url = "https://files.pythonhosted.org/packages/fb/68/c1d9c2f4a6e438e14613bad0f2973567586610cc22dcb1e1241da71de9d3/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9", size = 222125, upload-time = "2025-06-09T22:59:52.884Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d0/98e8f9a515228d708344d7c6986752be3e3192d1795f748c24bcf154ad99/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c", size = 233455, upload-time = "2025-06-09T22:59:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/79/df/8a11bcec5600557f40338407d3e5bea80376ed1c01a6c0910fcfdc4b8993/frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981", size = 227339, upload-time = "2025-06-09T22:59:56.187Z" }, + { url = "https://files.pythonhosted.org/packages/50/82/41cb97d9c9a5ff94438c63cc343eb7980dac4187eb625a51bdfdb7707314/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615", size = 212969, upload-time = "2025-06-09T22:59:57.604Z" }, + { url = "https://files.pythonhosted.org/packages/13/47/f9179ee5ee4f55629e4f28c660b3fdf2775c8bfde8f9c53f2de2d93f52a9/frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50", size = 222862, upload-time = "2025-06-09T22:59:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/df81e41ec6b953902c8b7e3a83bee48b195cb0e5ec2eabae5d8330c78038/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa", size = 222492, upload-time = "2025-06-09T23:00:01.026Z" }, + { url = "https://files.pythonhosted.org/packages/84/17/30d6ea87fa95a9408245a948604b82c1a4b8b3e153cea596421a2aef2754/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577", size = 238250, upload-time = "2025-06-09T23:00:03.401Z" }, + { url = "https://files.pythonhosted.org/packages/8f/00/ecbeb51669e3c3df76cf2ddd66ae3e48345ec213a55e3887d216eb4fbab3/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59", size = 218720, upload-time = "2025-06-09T23:00:05.282Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c0/c224ce0e0eb31cc57f67742071bb470ba8246623c1823a7530be0e76164c/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e", size = 232585, upload-time = "2025-06-09T23:00:07.962Z" }, + { url = "https://files.pythonhosted.org/packages/55/3c/34cb694abf532f31f365106deebdeac9e45c19304d83cf7d51ebbb4ca4d1/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd", size = 234248, upload-time = "2025-06-09T23:00:09.428Z" }, + { url = "https://files.pythonhosted.org/packages/98/c0/2052d8b6cecda2e70bd81299e3512fa332abb6dcd2969b9c80dfcdddbf75/frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718", size = 221621, upload-time = "2025-06-09T23:00:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bf/7dcebae315436903b1d98ffb791a09d674c88480c158aa171958a3ac07f0/frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e", size = 39578, upload-time = "2025-06-09T23:00:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/8f/5f/f69818f017fa9a3d24d1ae39763e29b7f60a59e46d5f91b9c6b21622f4cd/frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464", size = 43830, upload-time = "2025-06-09T23:00:14.98Z" }, + { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, + { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, + { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, + { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, + { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, + { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, + { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, + { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, + { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, + { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, + { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, + { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, + { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, + { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, + { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, + { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, + { url = "https://files.pythonhosted.org/packages/24/90/6b2cebdabdbd50367273c20ff6b57a3dfa89bd0762de02c3a1eb42cb6462/frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee", size = 79791, upload-time = "2025-06-09T23:01:09.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/5b70b6a3325363293fe5fc3ae74cdcbc3e996c2a11dde2fd9f1fb0776d19/frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d", size = 47165, upload-time = "2025-06-09T23:01:10.653Z" }, + { url = "https://files.pythonhosted.org/packages/f4/25/a0895c99270ca6966110f4ad98e87e5662eab416a17e7fd53c364bf8b954/frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43", size = 45881, upload-time = "2025-06-09T23:01:12.296Z" }, + { url = "https://files.pythonhosted.org/packages/19/7c/71bb0bbe0832793c601fff68cd0cf6143753d0c667f9aec93d3c323f4b55/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d", size = 232409, upload-time = "2025-06-09T23:01:13.641Z" }, + { url = "https://files.pythonhosted.org/packages/c0/45/ed2798718910fe6eb3ba574082aaceff4528e6323f9a8570be0f7028d8e9/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee", size = 225132, upload-time = "2025-06-09T23:01:15.264Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e2/8417ae0f8eacb1d071d4950f32f229aa6bf68ab69aab797b72a07ea68d4f/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb", size = 237638, upload-time = "2025-06-09T23:01:16.752Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b7/2ace5450ce85f2af05a871b8c8719b341294775a0a6c5585d5e6170f2ce7/frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f", size = 233539, upload-time = "2025-06-09T23:01:18.202Z" }, + { url = "https://files.pythonhosted.org/packages/46/b9/6989292c5539553dba63f3c83dc4598186ab2888f67c0dc1d917e6887db6/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60", size = 215646, upload-time = "2025-06-09T23:01:19.649Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/bc8c5c99c7818293458fe745dab4fd5730ff49697ccc82b554eb69f16a24/frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00", size = 232233, upload-time = "2025-06-09T23:01:21.175Z" }, + { url = "https://files.pythonhosted.org/packages/59/52/460db4d7ba0811b9ccb85af996019f5d70831f2f5f255f7cc61f86199795/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b", size = 227996, upload-time = "2025-06-09T23:01:23.098Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/f4b39e904c03927b7ecf891804fd3b4df3db29b9e487c6418e37988d6e9d/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c", size = 242280, upload-time = "2025-06-09T23:01:24.808Z" }, + { url = "https://files.pythonhosted.org/packages/b8/33/3f8d6ced42f162d743e3517781566b8481322be321b486d9d262adf70bfb/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949", size = 217717, upload-time = "2025-06-09T23:01:26.28Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e8/ad683e75da6ccef50d0ab0c2b2324b32f84fc88ceee778ed79b8e2d2fe2e/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca", size = 236644, upload-time = "2025-06-09T23:01:27.887Z" }, + { url = "https://files.pythonhosted.org/packages/b2/14/8d19ccdd3799310722195a72ac94ddc677541fb4bef4091d8e7775752360/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b", size = 238879, upload-time = "2025-06-09T23:01:29.524Z" }, + { url = "https://files.pythonhosted.org/packages/ce/13/c12bf657494c2fd1079a48b2db49fa4196325909249a52d8f09bc9123fd7/frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e", size = 232502, upload-time = "2025-06-09T23:01:31.287Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8b/e7f9dfde869825489382bc0d512c15e96d3964180c9499efcec72e85db7e/frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1", size = 39169, upload-time = "2025-06-09T23:01:35.503Z" }, + { url = "https://files.pythonhosted.org/packages/35/89/a487a98d94205d85745080a37860ff5744b9820a2c9acbcdd9440bfddf98/frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba", size = 43219, upload-time = "2025-06-09T23:01:36.784Z" }, + { url = "https://files.pythonhosted.org/packages/56/d5/5c4cf2319a49eddd9dd7145e66c4866bdc6f3dbc67ca3d59685149c11e0d/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d", size = 84345, upload-time = "2025-06-09T23:01:38.295Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/ec2c1e1dc16b85bc9d526009961953df9cec8481b6886debb36ec9107799/frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d", size = 48880, upload-time = "2025-06-09T23:01:39.887Z" }, + { url = "https://files.pythonhosted.org/packages/69/86/f9596807b03de126e11e7d42ac91e3d0b19a6599c714a1989a4e85eeefc4/frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b", size = 48498, upload-time = "2025-06-09T23:01:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cb/df6de220f5036001005f2d726b789b2c0b65f2363b104bbc16f5be8084f8/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146", size = 292296, upload-time = "2025-06-09T23:01:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/de84c642f17c8f851a2905cee2dae401e5e0daca9b5ef121e120e19aa825/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74", size = 273103, upload-time = "2025-06-09T23:01:44.166Z" }, + { url = "https://files.pythonhosted.org/packages/88/3c/c840bfa474ba3fa13c772b93070893c6e9d5c0350885760376cbe3b6c1b3/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1", size = 292869, upload-time = "2025-06-09T23:01:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/a6/1c/3efa6e7d5a39a1d5ef0abeb51c48fb657765794a46cf124e5aca2c7a592c/frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1", size = 291467, upload-time = "2025-06-09T23:01:47.234Z" }, + { url = "https://files.pythonhosted.org/packages/4f/00/d5c5e09d4922c395e2f2f6b79b9a20dab4b67daaf78ab92e7729341f61f6/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384", size = 266028, upload-time = "2025-06-09T23:01:48.819Z" }, + { url = "https://files.pythonhosted.org/packages/4e/27/72765be905619dfde25a7f33813ac0341eb6b076abede17a2e3fbfade0cb/frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb", size = 284294, upload-time = "2025-06-09T23:01:50.394Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/c94103a23001b17808eb7dd1200c156bb69fb68e63fcf0693dde4cd6228c/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c", size = 281898, upload-time = "2025-06-09T23:01:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/a3e2c00c00f9e2a9db5653bca3fec306349e71aff14ae45ecc6d0951dd24/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65", size = 290465, upload-time = "2025-06-09T23:01:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/f89b7fbce8b0b0c095d82b008afd0590f71ccb3dee6eee41791cf8cd25fd/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3", size = 266385, upload-time = "2025-06-09T23:01:55.769Z" }, + { url = "https://files.pythonhosted.org/packages/cd/45/e365fdb554159462ca12df54bc59bfa7a9a273ecc21e99e72e597564d1ae/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657", size = 288771, upload-time = "2025-06-09T23:01:57.4Z" }, + { url = "https://files.pythonhosted.org/packages/00/11/47b6117002a0e904f004d70ec5194fe9144f117c33c851e3d51c765962d0/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104", size = 288206, upload-time = "2025-06-09T23:01:58.936Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/5f9f3c3fd7f7746082ec67bcdc204db72dad081f4f83a503d33220a92973/frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf", size = 282620, upload-time = "2025-06-09T23:02:00.493Z" }, + { url = "https://files.pythonhosted.org/packages/0b/31/8fbc5af2d183bff20f21aa743b4088eac4445d2bb1cdece449ae80e4e2d1/frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81", size = 43059, upload-time = "2025-06-09T23:02:02.072Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ed/41956f52105b8dbc26e457c5705340c67c8cc2b79f394b79bffc09d0e938/frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e", size = 47516, upload-time = "2025-06-09T23:02:03.779Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, +] + +[[package]] +name = "gql" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "backoff" }, + { name = "graphql-core" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/ed/44ffd30b06b3afc8274ee2f38c3c1b61fe4740bf03d92083e43d2c17ac77/gql-3.5.3.tar.gz", hash = "sha256:393b8c049d58e0d2f5461b9d738a2b5f904186a40395500b4a84dd092d56e42b", size = 180504, upload-time = "2025-05-20T12:34:08.954Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/50/2f4e99b216821ac921dbebf91c644ba95818f5d07857acadee17220221f3/gql-3.5.3-py2.py3-none-any.whl", hash = "sha256:e1fcbde2893fcafdd28114ece87ff47f1cc339a31db271fc4e1d528f5a1d4fbc", size = 74348, upload-time = "2025-05-20T12:34:07.687Z" }, +] + +[package.optional-dependencies] +httpx = [ + { name = "httpx" }, +] + +[[package]] +name = "graphql-core" +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" }, +] + +[[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 = "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 = "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 = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[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 = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, +] + +[[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 = "multidict" +version = "6.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b5/59f27b4ce9951a4bce56b88ba5ff5159486797ab18863f2b4c1c5e8465bd/multidict-6.5.0.tar.gz", hash = "sha256:942bd8002492ba819426a8d7aefde3189c1b87099cdf18aaaefefcf7f3f7b6d2", size = 98512, upload-time = "2025-06-17T14:15:56.556Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/88/f8354ef1cb1121234c3461ff3d11eac5f4fe115f00552d3376306275c9ab/multidict-6.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e118a202904623b1d2606d1c8614e14c9444b59d64454b0c355044058066469", size = 73858, upload-time = "2025-06-17T14:13:21.451Z" }, + { url = "https://files.pythonhosted.org/packages/49/04/634b49c7abe71bd1c61affaeaa0c2a46b6be8d599a07b495259615dbdfe0/multidict-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a42995bdcaff4e22cb1280ae7752c3ed3fbb398090c6991a2797a4a0e5ed16a9", size = 43186, upload-time = "2025-06-17T14:13:23.615Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ff/091ff4830ec8f96378578bfffa7f324a9dd16f60274cec861ae65ba10be3/multidict-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2261b538145723ca776e55208640fffd7ee78184d223f37c2b40b9edfe0e818a", size = 43031, upload-time = "2025-06-17T14:13:24.725Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/1b4137845f8b8dbc2332af54e2d7761c6a29c2c33c8d47a0c8c70676bac1/multidict-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e5b19f8cd67235fab3e195ca389490415d9fef5a315b1fa6f332925dc924262", size = 233588, upload-time = "2025-06-17T14:13:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/c3/77/cbe9a1f58c6d4f822663788e414637f256a872bc352cedbaf7717b62db58/multidict-6.5.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:177b081e4dec67c3320b16b3aa0babc178bbf758553085669382c7ec711e1ec8", size = 222714, upload-time = "2025-06-17T14:13:27.482Z" }, + { url = "https://files.pythonhosted.org/packages/6c/37/39e1142c2916973818515adc13bbdb68d3d8126935e3855200e059a79bab/multidict-6.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d30a2cc106a7d116b52ee046207614db42380b62e6b1dd2a50eba47c5ca5eb1", size = 242741, upload-time = "2025-06-17T14:13:28.92Z" }, + { url = "https://files.pythonhosted.org/packages/a3/aa/60c3ef0c87ccad3445bf01926a1b8235ee24c3dde483faef1079cc91706d/multidict-6.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a72933bc308d7a64de37f0d51795dbeaceebdfb75454f89035cdfc6a74cfd129", size = 235008, upload-time = "2025-06-17T14:13:30.587Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5e/f7e0fd5f5b8a7b9a75b0f5642ca6b6dde90116266920d8cf63b513f3908b/multidict-6.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d109e663d032280ef8ef62b50924b2e887d5ddf19e301844a6cb7e91a172a6", size = 226627, upload-time = "2025-06-17T14:13:31.831Z" }, + { url = "https://files.pythonhosted.org/packages/b7/74/1bc0a3c6a9105051f68a6991fe235d7358836e81058728c24d5bbdd017cb/multidict-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b555329c9894332401f03b9a87016f0b707b6fccd4706793ec43b4a639e75869", size = 228232, upload-time = "2025-06-17T14:13:33.402Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/37118291cdc31f4cc680d54047cdea9b520e9a724a643919f71f8c2a2aeb/multidict-6.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6994bad9d471ef2156f2b6850b51e20ee409c6b9deebc0e57be096be9faffdce", size = 246616, upload-time = "2025-06-17T14:13:34.964Z" }, + { url = "https://files.pythonhosted.org/packages/ff/89/e2c08d6bdb21a1a55be4285510d058ace5f5acabe6b57900432e863d4c70/multidict-6.5.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:b15f817276c96cde9060569023808eec966bd8da56a97e6aa8116f34ddab6534", size = 235007, upload-time = "2025-06-17T14:13:36.428Z" }, + { url = "https://files.pythonhosted.org/packages/89/1e/e39a98e8e1477ec7a871b3c17265658fbe6d617048059ae7fa5011b224f3/multidict-6.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b4bf507c991db535a935b2127cf057a58dbc688c9f309c72080795c63e796f58", size = 244824, upload-time = "2025-06-17T14:13:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ba/63e11edd45c31e708c5a1904aa7ac4de01e13135a04cfe96bc71eb359b85/multidict-6.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:60c3f8f13d443426c55f88cf3172547bbc600a86d57fd565458b9259239a6737", size = 257229, upload-time = "2025-06-17T14:13:39.554Z" }, + { url = "https://files.pythonhosted.org/packages/0f/00/bdcceb6af424936adfc8b92a79d3a95863585f380071393934f10a63f9e3/multidict-6.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a10227168a24420c158747fc201d4279aa9af1671f287371597e2b4f2ff21879", size = 247118, upload-time = "2025-06-17T14:13:40.795Z" }, + { url = "https://files.pythonhosted.org/packages/b6/a0/4aa79e991909cca36ca821a9ba5e8e81e4cd5b887c81f89ded994e0f49df/multidict-6.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e3b1425fe54ccfde66b8cfb25d02be34d5dfd2261a71561ffd887ef4088b4b69", size = 243948, upload-time = "2025-06-17T14:13:42.477Z" }, + { url = "https://files.pythonhosted.org/packages/21/8b/e45e19ce43afb31ff6b0fd5d5816b4fcc1fcc2f37e8a82aefae06c40c7a6/multidict-6.5.0-cp310-cp310-win32.whl", hash = "sha256:b4e47ef51237841d1087e1e1548071a6ef22e27ed0400c272174fa585277c4b4", size = 40433, upload-time = "2025-06-17T14:13:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6e/96e0ba4601343d9344e69503fca072ace19c35f7d4ca3d68401e59acdc8f/multidict-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:63b3b24fadc7067282c88fae5b2f366d5b3a7c15c021c2838de8c65a50eeefb4", size = 44423, upload-time = "2025-06-17T14:13:44.991Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/9befa919d7a390f13a5511a69282b7437782071160c566de6e0ebf712c9f/multidict-6.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:8b2d61afbafc679b7eaf08e9de4fa5d38bd5dc7a9c0a577c9f9588fb49f02dbb", size = 41481, upload-time = "2025-06-17T14:13:49.389Z" }, + { url = "https://files.pythonhosted.org/packages/75/ba/484f8e96ee58ec4fef42650eb9dbbedb24f9bc155780888398a4725d2270/multidict-6.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b4bf6bb15a05796a07a248084e3e46e032860c899c7a9b981030e61368dba95", size = 73283, upload-time = "2025-06-17T14:13:50.406Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/01d62ea6199d76934c87746695b3ed16aeedfdd564e8d89184577037baac/multidict-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46bb05d50219655c42a4b8fcda9c7ee658a09adbb719c48e65a20284e36328ea", size = 42937, upload-time = "2025-06-17T14:13:51.45Z" }, + { url = "https://files.pythonhosted.org/packages/da/cf/bb462d920f26d9e2e0aff8a78aeb06af1225b826e9a5468870c57591910a/multidict-6.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54f524d73f4d54e87e03c98f6af601af4777e4668a52b1bd2ae0a4d6fc7b392b", size = 42748, upload-time = "2025-06-17T14:13:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b1/d5c11ea0fdad68d3ed45f0e2527de6496d2fac8afe6b8ca6d407c20ad00f/multidict-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529b03600466480ecc502000d62e54f185a884ed4570dee90d9a273ee80e37b5", size = 236448, upload-time = "2025-06-17T14:13:53.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/69/c3ceb264994f5b338c812911a8d660084f37779daef298fc30bd817f75c7/multidict-6.5.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69ad681ad7c93a41ee7005cc83a144b5b34a3838bcf7261e2b5356057b0f78de", size = 228695, upload-time = "2025-06-17T14:13:54.775Z" }, + { url = "https://files.pythonhosted.org/packages/81/3d/c23dcc0d34a35ad29974184db2878021d28fe170ecb9192be6bfee73f1f2/multidict-6.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fe9fada8bc0839466b09fa3f6894f003137942984843ec0c3848846329a36ae", size = 247434, upload-time = "2025-06-17T14:13:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/06/b3/06cf7a049129ff52525a859277abb5648e61d7afae7fb7ed02e3806be34e/multidict-6.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f94c6ea6405fcf81baef1e459b209a78cda5442e61b5b7a57ede39d99b5204a0", size = 239431, upload-time = "2025-06-17T14:13:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/8a/72/b2fe2fafa23af0c6123aebe23b4cd23fdad01dfe7009bb85624e4636d0dd/multidict-6.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ca75ad8a39ed75f079a8931435a5b51ee4c45d9b32e1740f99969a5d1cc2ee", size = 231542, upload-time = "2025-06-17T14:13:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c9/a52ca0a342a02411a31b6af197a6428a5137d805293f10946eeab614ec06/multidict-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4c08f3a2a6cc42b414496017928d95898964fed84b1b2dace0c9ee763061f9", size = 233069, upload-time = "2025-06-17T14:13:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/a3328a3929b8e131e2678d5e65f552b0a6874fab62123e31f5a5625650b0/multidict-6.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:046a7540cfbb4d5dc846a1fd9843f3ba980c6523f2e0c5b8622b4a5c94138ae6", size = 250596, upload-time = "2025-06-17T14:14:01.178Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b8/aa3905a38a8287013aeb0a54c73f79ccd8b32d2f1d53e5934643a36502c2/multidict-6.5.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64306121171d988af77d74be0d8c73ee1a69cf6f96aea7fa6030c88f32a152dd", size = 237858, upload-time = "2025-06-17T14:14:03.232Z" }, + { url = "https://files.pythonhosted.org/packages/d3/eb/f11d5af028014f402e5dd01ece74533964fa4e7bfae4af4824506fa8c398/multidict-6.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b4ac1dd5eb0ecf6f7351d5a9137f30a83f7182209c5d37f61614dfdce5714853", size = 249175, upload-time = "2025-06-17T14:14:04.561Z" }, + { url = "https://files.pythonhosted.org/packages/ac/57/d451905a62e5ef489cb4f92e8190d34ac5329427512afd7f893121da4e96/multidict-6.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bab4a8337235365f4111a7011a1f028826ca683834ebd12de4b85e2844359c36", size = 259532, upload-time = "2025-06-17T14:14:05.798Z" }, + { url = "https://files.pythonhosted.org/packages/d3/90/ff82b5ac5cabe3c79c50cf62a62f3837905aa717e67b6b4b7872804f23c8/multidict-6.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a05b5604c5a75df14a63eeeca598d11b2c3745b9008539b70826ea044063a572", size = 250554, upload-time = "2025-06-17T14:14:07.382Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5a/0cabc50d4bc16e61d8b0a8a74499a1409fa7b4ef32970b7662a423781fc7/multidict-6.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67c4a640952371c9ca65b6a710598be246ef3be5ca83ed38c16a7660d3980877", size = 248159, upload-time = "2025-06-17T14:14:08.65Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1d/adeabae0771544f140d9f42ab2c46eaf54e793325999c36106078b7f6600/multidict-6.5.0-cp311-cp311-win32.whl", hash = "sha256:fdeae096ca36c12d8aca2640b8407a9d94e961372c68435bef14e31cce726138", size = 40357, upload-time = "2025-06-17T14:14:09.91Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/bbd85ae65c96de5c9910c332ee1f4b7be0bf0fb21563895167bcb6502a1f/multidict-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e2977ef8b7ce27723ee8c610d1bd1765da4f3fbe5a64f9bf1fd3b4770e31fbc0", size = 44432, upload-time = "2025-06-17T14:14:11.013Z" }, + { url = "https://files.pythonhosted.org/packages/96/af/f9052d9c4e65195b210da9f7afdea06d3b7592b3221cc0ef1b407f762faa/multidict-6.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:82d0cf0ea49bae43d9e8c3851e21954eff716259ff42da401b668744d1760bcb", size = 41408, upload-time = "2025-06-17T14:14:12.112Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fa/18f4950e00924f7e84c8195f4fc303295e14df23f713d64e778b8fa8b903/multidict-6.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1bb986c8ea9d49947bc325c51eced1ada6d8d9b4c5b15fd3fcdc3c93edef5a74", size = 73474, upload-time = "2025-06-17T14:14:13.528Z" }, + { url = "https://files.pythonhosted.org/packages/6c/66/0392a2a8948bccff57e4793c9dde3e5c088f01e8b7f8867ee58a2f187fc5/multidict-6.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:03c0923da300120830fc467e23805d63bbb4e98b94032bd863bc7797ea5fa653", size = 43741, upload-time = "2025-06-17T14:14:15.188Z" }, + { url = "https://files.pythonhosted.org/packages/98/3e/f48487c91b2a070566cfbab876d7e1ebe7deb0a8002e4e896a97998ae066/multidict-6.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4c78d5ec00fdd35c91680ab5cf58368faad4bd1a8721f87127326270248de9bc", size = 42143, upload-time = "2025-06-17T14:14:16.612Z" }, + { url = "https://files.pythonhosted.org/packages/3f/49/439c6cc1cd00365cf561bdd3579cc3fa1a0d38effb3a59b8d9562839197f/multidict-6.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadc3cb78be90a887f8f6b73945b840da44b4a483d1c9750459ae69687940c97", size = 239303, upload-time = "2025-06-17T14:14:17.707Z" }, + { url = "https://files.pythonhosted.org/packages/c4/24/491786269e90081cb536e4d7429508725bc92ece176d1204a4449de7c41c/multidict-6.5.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b02e1ca495d71e07e652e4cef91adae3bf7ae4493507a263f56e617de65dafc", size = 236913, upload-time = "2025-06-17T14:14:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/e8/76/bbe2558b820ebeca8a317ab034541790e8160ca4b1e450415383ac69b339/multidict-6.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7fe92a62326eef351668eec4e2dfc494927764a0840a1895cff16707fceffcd3", size = 250752, upload-time = "2025-06-17T14:14:20.297Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e3/3977f2c1123f553ceff9f53cd4de04be2c1912333c6fabbcd51531655476/multidict-6.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7673ee4f63879ecd526488deb1989041abcb101b2d30a9165e1e90c489f3f7fb", size = 243937, upload-time = "2025-06-17T14:14:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b8/7a6e9c13c79709cdd2f22ee849f058e6da76892d141a67acc0e6c30d845c/multidict-6.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa097ae2a29f573de7e2d86620cbdda5676d27772d4ed2669cfa9961a0d73955", size = 237419, upload-time = "2025-06-17T14:14:23.215Z" }, + { url = "https://files.pythonhosted.org/packages/84/9d/8557f5e88da71bc7e7a8ace1ada4c28197f3bfdc2dd6e51d3b88f2e16e8e/multidict-6.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:300da0fa4f8457d9c4bd579695496116563409e676ac79b5e4dca18e49d1c308", size = 237222, upload-time = "2025-06-17T14:14:24.516Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3b/8f023ad60e7969cb6bc0683738d0e1618f5ff5723d6d2d7818dc6df6ad3d/multidict-6.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9a19bd108c35877b57393243d392d024cfbfdefe759fd137abb98f6fc910b64c", size = 247861, upload-time = "2025-06-17T14:14:25.839Z" }, + { url = "https://files.pythonhosted.org/packages/af/1c/9cf5a099ce7e3189906cf5daa72c44ee962dcb4c1983659f3a6f8a7446ab/multidict-6.5.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f32a1777465a35c35ddbbd7fc1293077938a69402fcc59e40b2846d04a120dd", size = 243917, upload-time = "2025-06-17T14:14:27.164Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bb/88ee66ebeef56868044bac58feb1cc25658bff27b20e3cfc464edc181287/multidict-6.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9cc1e10c14ce8112d1e6d8971fe3cdbe13e314f68bea0e727429249d4a6ce164", size = 249214, upload-time = "2025-06-17T14:14:28.795Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/a90e88cc4a1309f33088ab1cdd5c0487718f49dfb82c5ffc845bb17c1973/multidict-6.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e95c5e07a06594bdc288117ca90e89156aee8cb2d7c330b920d9c3dd19c05414", size = 258682, upload-time = "2025-06-17T14:14:30.066Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d8/16dd69a6811920a31f4e06114ebe67b1cd922c8b05c9c82b050706d0b6fe/multidict-6.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40ff26f58323795f5cd2855e2718a1720a1123fb90df4553426f0efd76135462", size = 254254, upload-time = "2025-06-17T14:14:31.323Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a8/90193a5f5ca1bdbf92633d69a25a2ef9bcac7b412b8d48c84d01a2732518/multidict-6.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:76803a29fd71869a8b59c2118c9dcfb3b8f9c8723e2cce6baeb20705459505cf", size = 247741, upload-time = "2025-06-17T14:14:32.717Z" }, + { url = "https://files.pythonhosted.org/packages/cd/43/29c7a747153c05b41d1f67455426af39ed88d6de3f21c232b8f2724bde13/multidict-6.5.0-cp312-cp312-win32.whl", hash = "sha256:df7ecbc65a53a2ce1b3a0c82e6ad1a43dcfe7c6137733f9176a92516b9f5b851", size = 41049, upload-time = "2025-06-17T14:14:33.941Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e8/8f3fc32b7e901f3a2719764d64aeaf6ae77b4ba961f1c3a3cf3867766636/multidict-6.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ec1c3fbbb0b655a6540bce408f48b9a7474fd94ed657dcd2e890671fefa7743", size = 44700, upload-time = "2025-06-17T14:14:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/24/e4/e250806adc98d524d41e69c8d4a42bc3513464adb88cb96224df12928617/multidict-6.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:2d24a00d34808b22c1f15902899b9d82d0faeca9f56281641c791d8605eacd35", size = 41703, upload-time = "2025-06-17T14:14:36.168Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/092c4e9402b6d16de761cff88cb842a5c8cc50ccecaf9c4481ba53264b9e/multidict-6.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:53d92df1752df67a928fa7f884aa51edae6f1cf00eeb38cbcf318cf841c17456", size = 73486, upload-time = "2025-06-17T14:14:37.238Z" }, + { url = "https://files.pythonhosted.org/packages/08/f9/6f7ddb8213f5fdf4db48d1d640b78e8aef89b63a5de8a2313286db709250/multidict-6.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:680210de2c38eef17ce46b8df8bf2c1ece489261a14a6e43c997d49843a27c99", size = 43745, upload-time = "2025-06-17T14:14:38.32Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a7/b9be0163bfeee3bb08a77a1705e24eb7e651d594ea554107fac8a1ca6a4d/multidict-6.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e279259bcb936732bfa1a8eec82b5d2352b3df69d2fa90d25808cfc403cee90a", size = 42135, upload-time = "2025-06-17T14:14:39.897Z" }, + { url = "https://files.pythonhosted.org/packages/8e/30/93c8203f943a417bda3c573a34d5db0cf733afdfffb0ca78545c7716dbd8/multidict-6.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1c185fc1069781e3fc8b622c4331fb3b433979850392daa5efbb97f7f9959bb", size = 238585, upload-time = "2025-06-17T14:14:41.332Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fe/2582b56a1807604774f566eeef183b0d6b148f4b89d1612cd077567b2e1e/multidict-6.5.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6bb5f65ff91daf19ce97f48f63585e51595539a8a523258b34f7cef2ec7e0617", size = 236174, upload-time = "2025-06-17T14:14:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c4/d8b66d42d385bd4f974cbd1eaa8b265e6b8d297249009f312081d5ded5c7/multidict-6.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8646b4259450c59b9286db280dd57745897897284f6308edbdf437166d93855", size = 250145, upload-time = "2025-06-17T14:14:43.944Z" }, + { url = "https://files.pythonhosted.org/packages/bc/64/62feda5093ee852426aae3df86fab079f8bf1cdbe403e1078c94672ad3ec/multidict-6.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d245973d4ecc04eea0a8e5ebec7882cf515480036e1b48e65dffcfbdf86d00be", size = 243470, upload-time = "2025-06-17T14:14:45.343Z" }, + { url = "https://files.pythonhosted.org/packages/67/dc/9f6fa6e854625cf289c0e9f4464b40212a01f76b2f3edfe89b6779b4fb93/multidict-6.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a133e7ddc9bc7fb053733d0ff697ce78c7bf39b5aec4ac12857b6116324c8d75", size = 236968, upload-time = "2025-06-17T14:14:46.609Z" }, + { url = "https://files.pythonhosted.org/packages/46/ae/4b81c6e3745faee81a156f3f87402315bdccf04236f75c03e37be19c94ff/multidict-6.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80d696fa38d738fcebfd53eec4d2e3aeb86a67679fd5e53c325756682f152826", size = 236575, upload-time = "2025-06-17T14:14:47.929Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fa/4089d7642ea344226e1bfab60dd588761d4791754f8072e911836a39bedf/multidict-6.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:20d30c9410ac3908abbaa52ee5967a754c62142043cf2ba091e39681bd51d21a", size = 247632, upload-time = "2025-06-17T14:14:49.525Z" }, + { url = "https://files.pythonhosted.org/packages/16/ee/a353dac797de0f28fb7f078cc181c5f2eefe8dd16aa11a7100cbdc234037/multidict-6.5.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c65068cc026f217e815fa519d8e959a7188e94ec163ffa029c94ca3ef9d4a73", size = 243520, upload-time = "2025-06-17T14:14:50.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/560deb3d2d95822d6eb1bcb1f1cb728f8f0197ec25be7c936d5d6a5d133c/multidict-6.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e355ac668a8c3e49c2ca8daa4c92f0ad5b705d26da3d5af6f7d971e46c096da7", size = 248551, upload-time = "2025-06-17T14:14:52.229Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/ddf277e67c78205f6695f2a7639be459bca9cc353b962fd8085a492a262f/multidict-6.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08db204213d0375a91a381cae0677ab95dd8c67a465eb370549daf6dbbf8ba10", size = 258362, upload-time = "2025-06-17T14:14:53.934Z" }, + { url = "https://files.pythonhosted.org/packages/02/fc/d64ee1df9b87c5210f2d4c419cab07f28589c81b4e5711eda05a122d0614/multidict-6.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ffa58e3e215af8f6536dc837a990e456129857bb6fd546b3991be470abd9597a", size = 253862, upload-time = "2025-06-17T14:14:55.323Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/a2743c00d9e25f4826d3a77cc13d4746398872cf21c843eef96bb9945665/multidict-6.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3e86eb90015c6f21658dbd257bb8e6aa18bdb365b92dd1fba27ec04e58cdc31b", size = 247391, upload-time = "2025-06-17T14:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/9b/03/7773518db74c442904dbd349074f1e7f2a854cee4d9529fc59e623d3949e/multidict-6.5.0-cp313-cp313-win32.whl", hash = "sha256:f34a90fbd9959d0f857323bd3c52b3e6011ed48f78d7d7b9e04980b8a41da3af", size = 41115, upload-time = "2025-06-17T14:14:59.33Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9a/6fc51b1dc11a7baa944bc101a92167d8b0f5929d376a8c65168fc0d35917/multidict-6.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:fcb2aa79ac6aef8d5b709bbfc2fdb1d75210ba43038d70fbb595b35af470ce06", size = 44768, upload-time = "2025-06-17T14:15:00.427Z" }, + { url = "https://files.pythonhosted.org/packages/82/2d/0d010be24b663b3c16e3d3307bbba2de5ae8eec496f6027d5c0515b371a8/multidict-6.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:6dcee5e7e92060b4bb9bb6f01efcbb78c13d0e17d9bc6eec71660dd71dc7b0c2", size = 41770, upload-time = "2025-06-17T14:15:01.854Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d1/a71711a5f32f84b7b036e82182e3250b949a0ce70d51a2c6a4079e665449/multidict-6.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:cbbc88abea2388fde41dd574159dec2cda005cb61aa84950828610cb5010f21a", size = 80450, upload-time = "2025-06-17T14:15:02.968Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a2/953a9eede63a98fcec2c1a2c1a0d88de120056219931013b871884f51b43/multidict-6.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70b599f70ae6536e5976364d3c3cf36f40334708bd6cebdd1e2438395d5e7676", size = 46971, upload-time = "2025-06-17T14:15:04.149Z" }, + { url = "https://files.pythonhosted.org/packages/44/61/60250212953459edda2c729e1d85130912f23c67bd4f585546fe4bdb1578/multidict-6.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:828bab777aa8d29d59700018178061854e3a47727e0611cb9bec579d3882de3b", size = 45548, upload-time = "2025-06-17T14:15:05.666Z" }, + { url = "https://files.pythonhosted.org/packages/11/b6/e78ee82e96c495bc2582b303f68bed176b481c8d81a441fec07404fce2ca/multidict-6.5.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9695fc1462f17b131c111cf0856a22ff154b0480f86f539d24b2778571ff94d", size = 238545, upload-time = "2025-06-17T14:15:06.88Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0f/6132ca06670c8d7b374c3a4fd1ba896fc37fbb66b0de903f61db7d1020ec/multidict-6.5.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b5ac6ebaf5d9814b15f399337ebc6d3a7f4ce9331edd404e76c49a01620b68d", size = 229931, upload-time = "2025-06-17T14:15:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/c0/63/d9957c506e6df6b3e7a194f0eea62955c12875e454b978f18262a65d017b/multidict-6.5.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84a51e3baa77ded07be4766a9e41d977987b97e49884d4c94f6d30ab6acaee14", size = 248181, upload-time = "2025-06-17T14:15:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/43/3f/7d5490579640db5999a948e2c41d4a0efd91a75989bda3e0a03a79c92be2/multidict-6.5.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8de67f79314d24179e9b1869ed15e88d6ba5452a73fc9891ac142e0ee018b5d6", size = 241846, upload-time = "2025-06-17T14:15:11.596Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f7/252b1ce949ece52bba4c0de7aa2e3a3d5964e800bce71fb778c2e6c66f7c/multidict-6.5.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17f78a52c214481d30550ec18208e287dfc4736f0c0148208334b105fd9e0887", size = 232893, upload-time = "2025-06-17T14:15:12.946Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/0070bfd48c16afc26e056f2acce49e853c0d604a69c7124bc0bbdb1bcc0a/multidict-6.5.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2966d0099cb2e2039f9b0e73e7fd5eb9c85805681aa2a7f867f9d95b35356921", size = 228567, upload-time = "2025-06-17T14:15:14.267Z" }, + { url = "https://files.pythonhosted.org/packages/2a/31/90551c75322113ebf5fd9c5422e8641d6952f6edaf6b6c07fdc49b1bebdd/multidict-6.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:86fb42ed5ed1971c642cc52acc82491af97567534a8e381a8d50c02169c4e684", size = 246188, upload-time = "2025-06-17T14:15:15.985Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e2/aa4b02a55e7767ff292871023817fe4db83668d514dab7ccbce25eaf7659/multidict-6.5.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:4e990cbcb6382f9eae4ec720bcac6a1351509e6fc4a5bb70e4984b27973934e6", size = 235178, upload-time = "2025-06-17T14:15:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5c/f67e726717c4b138b166be1700e2b56e06fbbcb84643d15f9a9d7335ff41/multidict-6.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d99a59d64bb1f7f2117bec837d9e534c5aeb5dcedf4c2b16b9753ed28fdc20a3", size = 243422, upload-time = "2025-06-17T14:15:18.939Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/15fa318285e26a50aa3fa979bbcffb90f9b4d5ec58882d0590eda067d0da/multidict-6.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:e8ef15cc97c9890212e1caf90f0d63f6560e1e101cf83aeaf63a57556689fb34", size = 254898, upload-time = "2025-06-17T14:15:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3d/d6c6d1c2e9b61ca80313912d30bb90d4179335405e421ef0a164eac2c0f9/multidict-6.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:b8a09aec921b34bd8b9f842f0bcfd76c6a8c033dc5773511e15f2d517e7e1068", size = 247129, upload-time = "2025-06-17T14:15:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/29/15/1568258cf0090bfa78d44be66247cfdb16e27dfd935c8136a1e8632d3057/multidict-6.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ff07b504c23b67f2044533244c230808a1258b3493aaf3ea2a0785f70b7be461", size = 243841, upload-time = "2025-06-17T14:15:23.38Z" }, + { url = "https://files.pythonhosted.org/packages/65/57/64af5dbcfd61427056e840c8e520b502879d480f9632fbe210929fd87393/multidict-6.5.0-cp313-cp313t-win32.whl", hash = "sha256:9232a117341e7e979d210e41c04e18f1dc3a1d251268df6c818f5334301274e1", size = 46761, upload-time = "2025-06-17T14:15:24.733Z" }, + { url = "https://files.pythonhosted.org/packages/26/a8/cac7f7d61e188ff44f28e46cb98f9cc21762e671c96e031f06c84a60556e/multidict-6.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:44cb5c53fb2d4cbcee70a768d796052b75d89b827643788a75ea68189f0980a1", size = 52112, upload-time = "2025-06-17T14:15:25.906Z" }, + { url = "https://files.pythonhosted.org/packages/51/9f/076533feb1b5488d22936da98b9c217205cfbf9f56f7174e8c5c86d86fe6/multidict-6.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:51d33fafa82640c0217391d4ce895d32b7e84a832b8aee0dcc1b04d8981ec7f4", size = 44358, upload-time = "2025-06-17T14:15:27.117Z" }, + { url = "https://files.pythonhosted.org/packages/44/d8/45e8fc9892a7386d074941429e033adb4640e59ff0780d96a8cf46fe788e/multidict-6.5.0-py3-none-any.whl", hash = "sha256:5634b35f225977605385f56153bd95a7133faffc0ffe12ad26e10517537e8dfc", size = 12181, upload-time = "2025-06-17T14:15:55.156Z" }, +] + +[[package]] +name = "mypy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/69/92c7fa98112e4d9eb075a239caa4ef4649ad7d441545ccffbd5e34607cbb/mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab", size = 3324747, upload-time = "2025-06-16T16:51:35.145Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/12/2bf23a80fcef5edb75de9a1e295d778e0f46ea89eb8b115818b663eff42b/mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a", size = 10958644, upload-time = "2025-06-16T16:51:11.649Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/bfe47b3b278eacf348291742fd5e6613bbc4b3434b72ce9361896417cfe5/mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72", size = 10087033, upload-time = "2025-06-16T16:35:30.089Z" }, + { url = "https://files.pythonhosted.org/packages/21/de/40307c12fe25675a0776aaa2cdd2879cf30d99eec91b898de00228dc3ab5/mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea", size = 11875645, upload-time = "2025-06-16T16:35:48.49Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d8/85bdb59e4a98b7a31495bd8f1a4445d8ffc86cde4ab1f8c11d247c11aedc/mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574", size = 12616986, upload-time = "2025-06-16T16:48:39.526Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d0/bb25731158fa8f8ee9e068d3e94fcceb4971fedf1424248496292512afe9/mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d", size = 12878632, upload-time = "2025-06-16T16:36:08.195Z" }, + { url = "https://files.pythonhosted.org/packages/2d/11/822a9beb7a2b825c0cb06132ca0a5183f8327a5e23ef89717c9474ba0bc6/mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6", size = 9484391, upload-time = "2025-06-16T16:37:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/9a/61/ec1245aa1c325cb7a6c0f8570a2eee3bfc40fa90d19b1267f8e50b5c8645/mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc", size = 10890557, upload-time = "2025-06-16T16:37:21.421Z" }, + { url = "https://files.pythonhosted.org/packages/6b/bb/6eccc0ba0aa0c7a87df24e73f0ad34170514abd8162eb0c75fd7128171fb/mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782", size = 10012921, upload-time = "2025-06-16T16:51:28.659Z" }, + { url = "https://files.pythonhosted.org/packages/5f/80/b337a12e2006715f99f529e732c5f6a8c143bb58c92bb142d5ab380963a5/mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507", size = 11802887, upload-time = "2025-06-16T16:50:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/d9/59/f7af072d09793d581a745a25737c7c0a945760036b16aeb620f658a017af/mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca", size = 12531658, upload-time = "2025-06-16T16:33:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/82/c4/607672f2d6c0254b94a646cfc45ad589dd71b04aa1f3d642b840f7cce06c/mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4", size = 12732486, upload-time = "2025-06-16T16:37:03.301Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5e/136555ec1d80df877a707cebf9081bd3a9f397dedc1ab9750518d87489ec/mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6", size = 9479482, upload-time = "2025-06-16T16:47:37.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d6/39482e5fcc724c15bf6280ff5806548c7185e0c090712a3736ed4d07e8b7/mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d", size = 11066493, upload-time = "2025-06-16T16:47:01.683Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/26c347890efc6b757f4d5bb83f4a0cf5958b8cf49c938ac99b8b72b420a6/mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9", size = 10081687, upload-time = "2025-06-16T16:48:19.367Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/b5cb264c97b86914487d6a24bd8688c0172e37ec0f43e93b9691cae9468b/mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79", size = 11839723, upload-time = "2025-06-16T16:49:20.912Z" }, + { url = "https://files.pythonhosted.org/packages/15/f8/491997a9b8a554204f834ed4816bda813aefda31cf873bb099deee3c9a99/mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15", size = 12722980, upload-time = "2025-06-16T16:37:40.929Z" }, + { url = "https://files.pythonhosted.org/packages/df/f0/2bd41e174b5fd93bc9de9a28e4fb673113633b8a7f3a607fa4a73595e468/mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd", size = 12903328, upload-time = "2025-06-16T16:34:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/61/81/5572108a7bec2c46b8aff7e9b524f371fe6ab5efb534d38d6b37b5490da8/mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b", size = 9562321, upload-time = "2025-06-16T16:48:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/28/e3/96964af4a75a949e67df4b95318fe2b7427ac8189bbc3ef28f92a1c5bc56/mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438", size = 11063480, upload-time = "2025-06-16T16:47:56.205Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4d/cd1a42b8e5be278fab7010fb289d9307a63e07153f0ae1510a3d7b703193/mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536", size = 10090538, upload-time = "2025-06-16T16:46:43.92Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4f/c3c6b4b66374b5f68bab07c8cabd63a049ff69796b844bc759a0ca99bb2a/mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f", size = 11836839, upload-time = "2025-06-16T16:36:28.039Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7e/81ca3b074021ad9775e5cb97ebe0089c0f13684b066a750b7dc208438403/mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359", size = 12715634, upload-time = "2025-06-16T16:50:34.441Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/bdd40c8be346fa4c70edb4081d727a54d0a05382d84966869738cfa8a497/mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be", size = 12895584, upload-time = "2025-06-16T16:34:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/5a/fd/d486a0827a1c597b3b48b1bdef47228a6e9ee8102ab8c28f944cb83b65dc/mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee", size = 9573886, upload-time = "2025-06-16T16:36:43.589Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d3/53e684e78e07c1a2bf7105715e5edd09ce951fc3f47cf9ed095ec1b7a037/mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37", size = 2265923, upload-time = "2025-06-16T16:48:02.366Z" }, +] + +[[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 = "opentelemetry-api" +version = "1.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/94a8cb759e4e409022229418294e098ca7feca00eb3c467bb20cbd329bda/opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3", size = 64987, upload-time = "2025-06-10T08:55:19.818Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/f0/ff235936ee40db93360233b62da932d4fd9e8d103cd090c6bcb9afaf5f01/opentelemetry_exporter_otlp_proto_common-1.34.1.tar.gz", hash = "sha256:b59a20a927facd5eac06edaf87a07e49f9e4a13db487b7d8a52b37cb87710f8b", size = 20817, upload-time = "2025-06-10T08:55:22.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/e8/8b292a11cc8d8d87ec0c4089ae21b6a58af49ca2e51fa916435bc922fdc7/opentelemetry_exporter_otlp_proto_common-1.34.1-py3-none-any.whl", hash = "sha256:8e2019284bf24d3deebbb6c59c71e6eef3307cd88eff8c633e061abba33f7e87", size = 18834, upload-time = "2025-06-10T08:55:00.806Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.34.1" +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/19/8f/954bc725961cbe425a749d55c0ba1df46832a5999eae764d1a7349ac1c29/opentelemetry_exporter_otlp_proto_http-1.34.1.tar.gz", hash = "sha256:aaac36fdce46a8191e604dcf632e1f9380c7d5b356b27b3e0edb5610d9be28ad", size = 15351, upload-time = "2025-06-10T08:55:24.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/54/b05251c04e30c1ac70cf4a7c5653c085dfcf2c8b98af71661d6a252adc39/opentelemetry_exporter_otlp_proto_http-1.34.1-py3-none-any.whl", hash = "sha256:5251f00ca85872ce50d871f6d3cc89fe203b94c3c14c964bbdc3883366c705d8", size = 17744, upload-time = "2025-06-10T08:55:03.802Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/b3/c3158dd012463bb7c0eb7304a85a6f63baeeb5b4c93a53845cf89f848c7e/opentelemetry_proto-1.34.1.tar.gz", hash = "sha256:16286214e405c211fc774187f3e4bbb1351290b8dfb88e8948af209ce85b719e", size = 34344, upload-time = "2025-06-10T08:55:32.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/ab/4591bfa54e946350ce8b3f28e5c658fe9785e7cd11e9c11b1671a867822b/opentelemetry_proto-1.34.1-py3-none-any.whl", hash = "sha256:eb4bb5ac27f2562df2d6857fc557b3a481b5e298bc04f94cc68041f00cebcbd2", size = 55692, upload-time = "2025-06-10T08:55:14.904Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/41/fe20f9036433da8e0fcef568984da4c1d1c771fa072ecd1a4d98779dccdd/opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d", size = 159441, upload-time = "2025-06-10T08:55:33.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/1b/def4fe6aa73f483cabf4c748f4c25070d5f7604dcc8b52e962983491b29e/opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e", size = 118477, upload-time = "2025-06-10T08:55:16.02Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.55b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/f0/f33458486da911f47c4aa6db9bda308bb80f3236c111bf848bd870c16b16/opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3", size = 119829, upload-time = "2025-06-10T08:55:33.881Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/89/267b0af1b1d0ba828f0e60642b6a5116ac1fd917cde7fc02821627029bd1/opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed", size = 196223, upload-time = "2025-06-10T08:55:17.638Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[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.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/14/510deed325e262afeb8b360043c5d7c960da7d3ecd6d6f9496c9c56dc7f4/propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770", size = 73178, upload-time = "2025-06-09T22:53:40.126Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4e/ad52a7925ff01c1325653a730c7ec3175a23f948f08626a534133427dcff/propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3", size = 43133, upload-time = "2025-06-09T22:53:41.965Z" }, + { url = "https://files.pythonhosted.org/packages/63/7c/e9399ba5da7780871db4eac178e9c2e204c23dd3e7d32df202092a1ed400/propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3", size = 43039, upload-time = "2025-06-09T22:53:43.268Z" }, + { url = "https://files.pythonhosted.org/packages/22/e1/58da211eb8fdc6fc854002387d38f415a6ca5f5c67c1315b204a5d3e9d7a/propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e", size = 201903, upload-time = "2025-06-09T22:53:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0a/550ea0f52aac455cb90111c8bab995208443e46d925e51e2f6ebdf869525/propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220", size = 213362, upload-time = "2025-06-09T22:53:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/9893b7d878deda9bb69fcf54600b247fba7317761b7db11fede6e0f28bd0/propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb", size = 210525, upload-time = "2025-06-09T22:53:48.547Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bb/38fd08b278ca85cde36d848091ad2b45954bc5f15cce494bb300b9285831/propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614", size = 198283, upload-time = "2025-06-09T22:53:50.067Z" }, + { url = "https://files.pythonhosted.org/packages/78/8c/9fe55bd01d362bafb413dfe508c48753111a1e269737fa143ba85693592c/propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50", size = 191872, upload-time = "2025-06-09T22:53:51.438Z" }, + { url = "https://files.pythonhosted.org/packages/54/14/4701c33852937a22584e08abb531d654c8bcf7948a8f87ad0a4822394147/propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339", size = 199452, upload-time = "2025-06-09T22:53:53.229Z" }, + { url = "https://files.pythonhosted.org/packages/16/44/447f2253d859602095356007657ee535e0093215ea0b3d1d6a41d16e5201/propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0", size = 191567, upload-time = "2025-06-09T22:53:54.541Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b3/e4756258749bb2d3b46defcff606a2f47410bab82be5824a67e84015b267/propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2", size = 193015, upload-time = "2025-06-09T22:53:56.44Z" }, + { url = "https://files.pythonhosted.org/packages/1e/df/e6d3c7574233164b6330b9fd697beeac402afd367280e6dc377bb99b43d9/propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7", size = 204660, upload-time = "2025-06-09T22:53:57.839Z" }, + { url = "https://files.pythonhosted.org/packages/b2/53/e4d31dd5170b4a0e2e6b730f2385a96410633b4833dc25fe5dffd1f73294/propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b", size = 206105, upload-time = "2025-06-09T22:53:59.638Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fe/74d54cf9fbe2a20ff786e5f7afcfde446588f0cf15fb2daacfbc267b866c/propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c", size = 196980, upload-time = "2025-06-09T22:54:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/22/ec/c469c9d59dada8a7679625e0440b544fe72e99311a4679c279562051f6fc/propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70", size = 37679, upload-time = "2025-06-09T22:54:03.003Z" }, + { url = "https://files.pythonhosted.org/packages/38/35/07a471371ac89d418f8d0b699c75ea6dca2041fbda360823de21f6a9ce0a/propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9", size = 41459, upload-time = "2025-06-09T22:54:04.134Z" }, + { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, + { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, + { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, + { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, + { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, + { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, + { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, + { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, + { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, + { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, + { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d1/8c747fafa558c603c4ca19d8e20b288aa0c7cda74e9402f50f31eb65267e/propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945", size = 71286, upload-time = "2025-06-09T22:54:54.369Z" }, + { url = "https://files.pythonhosted.org/packages/61/99/d606cb7986b60d89c36de8a85d58764323b3a5ff07770a99d8e993b3fa73/propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252", size = 42425, upload-time = "2025-06-09T22:54:55.642Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/ef98f91bbb42b79e9bb82bdd348b255eb9d65f14dbbe3b1594644c4073f7/propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f", size = 41846, upload-time = "2025-06-09T22:54:57.246Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ad/3f0f9a705fb630d175146cd7b1d2bf5555c9beaed54e94132b21aac098a6/propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33", size = 208871, upload-time = "2025-06-09T22:54:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/3a/38/2085cda93d2c8b6ec3e92af2c89489a36a5886b712a34ab25de9fbca7992/propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e", size = 215720, upload-time = "2025-06-09T22:55:00.471Z" }, + { url = "https://files.pythonhosted.org/packages/61/c1/d72ea2dc83ac7f2c8e182786ab0fc2c7bd123a1ff9b7975bee671866fe5f/propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1", size = 215203, upload-time = "2025-06-09T22:55:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/af/81/b324c44ae60c56ef12007105f1460d5c304b0626ab0cc6b07c8f2a9aa0b8/propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3", size = 206365, upload-time = "2025-06-09T22:55:03.199Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/88549128bb89e66d2aff242488f62869014ae092db63ccea53c1cc75a81d/propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1", size = 196016, upload-time = "2025-06-09T22:55:04.518Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/3bdd14e737d145114a5eb83cb172903afba7242f67c5877f9909a20d948d/propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6", size = 205596, upload-time = "2025-06-09T22:55:05.942Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ca/2f4aa819c357d3107c3763d7ef42c03980f9ed5c48c82e01e25945d437c1/propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387", size = 200977, upload-time = "2025-06-09T22:55:07.792Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4a/e65276c7477533c59085251ae88505caf6831c0e85ff8b2e31ebcbb949b1/propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4", size = 197220, upload-time = "2025-06-09T22:55:09.173Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/fc7152e517cf5578278b242396ce4d4b36795423988ef39bb8cd5bf274c8/propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88", size = 210642, upload-time = "2025-06-09T22:55:10.62Z" }, + { url = "https://files.pythonhosted.org/packages/b9/80/abeb4a896d2767bf5f1ea7b92eb7be6a5330645bd7fb844049c0e4045d9d/propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206", size = 212789, upload-time = "2025-06-09T22:55:12.029Z" }, + { url = "https://files.pythonhosted.org/packages/b3/db/ea12a49aa7b2b6d68a5da8293dcf50068d48d088100ac016ad92a6a780e6/propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43", size = 205880, upload-time = "2025-06-09T22:55:13.45Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e5/9076a0bbbfb65d1198007059c65639dfd56266cf8e477a9707e4b1999ff4/propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02", size = 37220, upload-time = "2025-06-09T22:55:15.284Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f5/b369e026b09a26cd77aa88d8fffd69141d2ae00a2abaaf5380d2603f4b7f/propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05", size = 40678, upload-time = "2025-06-09T22:55:16.445Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/6ece377b55544941a08d03581c7bc400a3c8cd3c2865900a68d5de79e21f/propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b", size = 76560, upload-time = "2025-06-09T22:55:17.598Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/64a2bb16418740fa634b0e9c3d29edff1db07f56d3546ca2d86ddf0305e1/propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0", size = 44676, upload-time = "2025-06-09T22:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/f025e06ea51cb72c52fb87e9b395cced02786610b60a3ed51da8af017170/propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e", size = 44701, upload-time = "2025-06-09T22:55:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/a4/00/faa1b1b7c3b74fc277f8642f32a4c72ba1d7b2de36d7cdfb676db7f4303e/propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28", size = 276934, upload-time = "2025-06-09T22:55:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/74/ab/935beb6f1756e0476a4d5938ff44bf0d13a055fed880caf93859b4f1baf4/propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a", size = 278316, upload-time = "2025-06-09T22:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9d/994a5c1ce4389610838d1caec74bdf0e98b306c70314d46dbe4fcf21a3e2/propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c", size = 282619, upload-time = "2025-06-09T22:55:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/2b/00/a10afce3d1ed0287cef2e09506d3be9822513f2c1e96457ee369adb9a6cd/propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725", size = 265896, upload-time = "2025-06-09T22:55:26.049Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a8/2aa6716ffa566ca57c749edb909ad27884680887d68517e4be41b02299f3/propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892", size = 252111, upload-time = "2025-06-09T22:55:27.381Z" }, + { url = "https://files.pythonhosted.org/packages/36/4f/345ca9183b85ac29c8694b0941f7484bf419c7f0fea2d1e386b4f7893eed/propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44", size = 268334, upload-time = "2025-06-09T22:55:28.747Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ca/fcd54f78b59e3f97b3b9715501e3147f5340167733d27db423aa321e7148/propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe", size = 255026, upload-time = "2025-06-09T22:55:30.184Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8e6a6bbbd78ac89c30c225210a5c687790e532ba4088afb8c0445b77ef37/propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81", size = 250724, upload-time = "2025-06-09T22:55:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/0dd03616142baba28e8b2d14ce5df6631b4673850a3d4f9c0f9dd714a404/propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba", size = 268868, upload-time = "2025-06-09T22:55:33.209Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/2c12407a7e4fbacd94ddd32f3b1e3d5231e77c30ef7162b12a60e2dd5ce3/propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770", size = 271322, upload-time = "2025-06-09T22:55:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/35/91/9cb56efbb428b006bb85db28591e40b7736847b8331d43fe335acf95f6c8/propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330", size = 265778, upload-time = "2025-06-09T22:55:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/b0fe775a2bdd01e176b14b574be679d84fc83958335790f7c9a686c1f468/propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394", size = 41175, upload-time = "2025-06-09T22:55:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ff/47f08595e3d9b5e149c150f88d9714574f1a7cbd89fe2817158a952674bf/propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198", size = 44857, upload-time = "2025-06-09T22:55:39.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +] + +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/29/d09e70352e4e88c9c7a198d5645d7277811448d76c23b00345670f7c8a38/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84", size = 425226, upload-time = "2025-05-28T23:51:59.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/11/6e40e9fc5bba02988a214c07cf324595789ca7820160bfd1f8be96e48539/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079", size = 422963, upload-time = "2025-05-28T23:51:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/81/7f/73cefb093e1a2a7c3ffd839e6f9fcafb7a427d300c7f8aef9c64405d8ac6/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc", size = 434818, upload-time = "2025-05-28T23:51:44.297Z" }, + { url = "https://files.pythonhosted.org/packages/dd/73/10e1661c21f139f2c6ad9b23040ff36fee624310dc28fba20d33fdae124c/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671", size = 418091, upload-time = "2025-05-28T23:51:45.907Z" }, + { url = "https://files.pythonhosted.org/packages/6c/04/98f6f8cf5b07ab1294c13f34b4e69b3722bb609c5b701d6c169828f9f8aa/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015", size = 319824, upload-time = "2025-05-28T23:51:47.545Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/07c80521879c2d15f321465ac24c70efe2381378c00bf5e56a0f4fbac8cd/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61", size = 319942, upload-time = "2025-05-28T23:51:49.11Z" }, + { url = "https://files.pythonhosted.org/packages/7e/cc/7e77861000a0691aeea8f4566e5d3aa716f2b1dece4a24439437e41d3d25/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5", size = 172823, upload-time = "2025-05-28T23:51:58.157Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, +] + +[[package]] +name = "pytest-httpx" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/89/5b12b7b29e3d0af3a4b9c071ee92fa25a9017453731a38f08ba01c280f4c/pytest_httpx-0.35.0.tar.gz", hash = "sha256:d619ad5d2e67734abfbb224c3d9025d64795d4b8711116b1a13f72a251ae511f", size = 54146, upload-time = "2024-11-28T19:16:54.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/ed/026d467c1853dd83102411a78126b4842618e86c895f93528b0528c7a620/pytest_httpx-0.35.0-py3-none-any.whl", hash = "sha256:ee11a00ffcea94a5cbff47af2114d34c5b231c326902458deed73f9c459fd744", size = 19442, upload-time = "2024-11-28T19:16:52.787Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, +] + +[[package]] +name = "pytest-subprocess" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ae/3ad5c609a5088936608af12f42ad72567a877d3c64303500ebc3b7df0297/pytest_subprocess-1.5.3.tar.gz", hash = "sha256:c00b1140fb0211b3153e09500d770db10770baccbe6e05ee9c140036d1d811d5", size = 42282, upload-time = "2025-01-04T13:08:16.877Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/82/a038e8fdb86d5494a39b8730547ec79767731d02ecb556121e40c0892803/pytest_subprocess-1.5.3-py3-none-any.whl", hash = "sha256:b62580f5a84335fb9f2ec65d49e56a3c93f4722c148fe1771a002835d310a75b", size = 21759, upload-time = "2025-01-04T13:08:13.775Z" }, +] + +[[package]] +name = "requests" +version = "2.32.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "roman-numerals-py" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/90/5255432602c0b196a0da6720f6f76b93eb50baef46d3c9b0025e2f9acbf3/ruff-0.12.0.tar.gz", hash = "sha256:4d047db3662418d4a848a3fdbfaf17488b34b62f527ed6f10cb8afd78135bc5c", size = 4376101, upload-time = "2025-06-17T15:19:26.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/fd/b46bb20e14b11ff49dbc74c61de352e0dc07fb650189513631f6fb5fc69f/ruff-0.12.0-py3-none-linux_armv6l.whl", hash = "sha256:5652a9ecdb308a1754d96a68827755f28d5dfb416b06f60fd9e13f26191a8848", size = 10311554, upload-time = "2025-06-17T15:18:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d3/021dde5a988fa3e25d2468d1dadeea0ae89dc4bc67d0140c6e68818a12a1/ruff-0.12.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:05ed0c914fabc602fc1f3b42c53aa219e5736cb030cdd85640c32dbc73da74a6", size = 11118435, upload-time = "2025-06-17T15:18:49.064Z" }, + { url = "https://files.pythonhosted.org/packages/07/a2/01a5acf495265c667686ec418f19fd5c32bcc326d4c79ac28824aecd6a32/ruff-0.12.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:07a7aa9b69ac3fcfda3c507916d5d1bca10821fe3797d46bad10f2c6de1edda0", size = 10466010, upload-time = "2025-06-17T15:18:51.341Z" }, + { url = "https://files.pythonhosted.org/packages/4c/57/7caf31dd947d72e7aa06c60ecb19c135cad871a0a8a251723088132ce801/ruff-0.12.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7731c3eec50af71597243bace7ec6104616ca56dda2b99c89935fe926bdcd48", size = 10661366, upload-time = "2025-06-17T15:18:53.29Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/aa393b972a782b4bc9ea121e0e358a18981980856190d7d2b6187f63e03a/ruff-0.12.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:952d0630eae628250ab1c70a7fffb641b03e6b4a2d3f3ec6c1d19b4ab6c6c807", size = 10173492, upload-time = "2025-06-17T15:18:55.262Z" }, + { url = "https://files.pythonhosted.org/packages/d7/50/9349ee777614bc3062fc6b038503a59b2034d09dd259daf8192f56c06720/ruff-0.12.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c021f04ea06966b02614d442e94071781c424ab8e02ec7af2f037b4c1e01cc82", size = 11761739, upload-time = "2025-06-17T15:18:58.906Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/ad459de67c70ec112e2ba7206841c8f4eb340a03ee6a5cabc159fe558b8e/ruff-0.12.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d235618283718ee2fe14db07f954f9b2423700919dc688eacf3f8797a11315c", size = 12537098, upload-time = "2025-06-17T15:19:01.316Z" }, + { url = "https://files.pythonhosted.org/packages/ed/50/15ad9c80ebd3c4819f5bd8883e57329f538704ed57bac680d95cb6627527/ruff-0.12.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0758038f81beec8cc52ca22de9685b8ae7f7cc18c013ec2050012862cc9165", size = 12154122, upload-time = "2025-06-17T15:19:03.727Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/79b91e41bc8cc3e78ee95c87093c6cacfa275c786e53c9b11b9358026b3d/ruff-0.12.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139b3d28027987b78fc8d6cfb61165447bdf3740e650b7c480744873688808c2", size = 11363374, upload-time = "2025-06-17T15:19:05.875Z" }, + { url = "https://files.pythonhosted.org/packages/db/c3/82b292ff8a561850934549aa9dc39e2c4e783ab3c21debe55a495ddf7827/ruff-0.12.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68853e8517b17bba004152aebd9dd77d5213e503a5f2789395b25f26acac0da4", size = 11587647, upload-time = "2025-06-17T15:19:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/2b/42/d5760d742669f285909de1bbf50289baccb647b53e99b8a3b4f7ce1b2001/ruff-0.12.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3a9512af224b9ac4757f7010843771da6b2b0935a9e5e76bb407caa901a1a514", size = 10527284, upload-time = "2025-06-17T15:19:10.37Z" }, + { url = "https://files.pythonhosted.org/packages/19/f6/fcee9935f25a8a8bba4adbae62495c39ef281256693962c2159e8b284c5f/ruff-0.12.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b08df3d96db798e5beb488d4df03011874aff919a97dcc2dd8539bb2be5d6a88", size = 10158609, upload-time = "2025-06-17T15:19:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/37/fb/057febf0eea07b9384787bfe197e8b3384aa05faa0d6bd844b94ceb29945/ruff-0.12.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6a315992297a7435a66259073681bb0d8647a826b7a6de45c6934b2ca3a9ed51", size = 11141462, upload-time = "2025-06-17T15:19:15.195Z" }, + { url = "https://files.pythonhosted.org/packages/10/7c/1be8571011585914b9d23c95b15d07eec2d2303e94a03df58294bc9274d4/ruff-0.12.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e55e44e770e061f55a7dbc6e9aed47feea07731d809a3710feda2262d2d4d8a", size = 11641616, upload-time = "2025-06-17T15:19:17.6Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/b960ab4818f90ff59e571d03c3f992828d4683561095e80f9ef31f3d58b7/ruff-0.12.0-py3-none-win32.whl", hash = "sha256:7162a4c816f8d1555eb195c46ae0bd819834d2a3f18f98cc63819a7b46f474fb", size = 10525289, upload-time = "2025-06-17T15:19:19.688Z" }, + { url = "https://files.pythonhosted.org/packages/34/93/8b16034d493ef958a500f17cda3496c63a537ce9d5a6479feec9558f1695/ruff-0.12.0-py3-none-win_amd64.whl", hash = "sha256:d00b7a157b8fb6d3827b49d3324da34a1e3f93492c1f97b08e222ad7e9b291e0", size = 11598311, upload-time = "2025-06-17T15:19:21.785Z" }, + { url = "https://files.pythonhosted.org/packages/d0/33/4d3e79e4a84533d6cd526bfb42c020a23256ae5e4265d858bd1287831f7d/ruff-0.12.0-py3-none-win_arm64.whl", hash = "sha256:8cd24580405ad8c1cc64d61725bca091d6b6da7eb3d36f72cc605467069d7e8b", size = 10724946, upload-time = "2025-06-17T15:19:23.952Z" }, +] + +[[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 = "snowballstemmer" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11'" }, + { name = "babel", marker = "python_full_version < '3.11'" }, + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version < '3.11'" }, + { name = "imagesize", marker = "python_full_version < '3.11'" }, + { name = "jinja2", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "requests", marker = "python_full_version < '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11'" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.11'" }, + { name = "babel", marker = "python_full_version >= '3.11'" }, + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "docutils", marker = "python_full_version >= '3.11'" }, + { name = "imagesize", marker = "python_full_version >= '3.11'" }, + { name = "jinja2", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "requests", marker = "python_full_version >= '3.11'" }, + { name = "roman-numerals-py", marker = "python_full_version >= '3.11'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/44/c97faec644d29a5ceddd3020ae2edffa69e7d00054a8c7a6021e82f20335/sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85", size = 7620463, upload-time = "2024-11-13T11:06:04.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/77/46e3bac77b82b4df5bb5b61f2de98637724f246b4966cfc34bc5895d852a/sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13", size = 7655561, upload-time = "2024-11-13T11:06:02.094Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "yarl" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/65/7fed0d774abf47487c64be14e9223749468922817b5e8792b8a64792a1bb/yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4", size = 132910, upload-time = "2025-06-10T00:42:31.108Z" }, + { url = "https://files.pythonhosted.org/packages/8a/7b/988f55a52da99df9e56dc733b8e4e5a6ae2090081dc2754fc8fd34e60aa0/yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a", size = 90644, upload-time = "2025-06-10T00:42:33.851Z" }, + { url = "https://files.pythonhosted.org/packages/f7/de/30d98f03e95d30c7e3cc093759982d038c8833ec2451001d45ef4854edc1/yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed", size = 89322, upload-time = "2025-06-10T00:42:35.688Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7a/f2f314f5ebfe9200724b0b748de2186b927acb334cf964fd312eb86fc286/yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e", size = 323786, upload-time = "2025-06-10T00:42:37.817Z" }, + { url = "https://files.pythonhosted.org/packages/15/3f/718d26f189db96d993d14b984ce91de52e76309d0fd1d4296f34039856aa/yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73", size = 319627, upload-time = "2025-06-10T00:42:39.937Z" }, + { url = "https://files.pythonhosted.org/packages/a5/76/8fcfbf5fa2369157b9898962a4a7d96764b287b085b5b3d9ffae69cdefd1/yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e", size = 339149, upload-time = "2025-06-10T00:42:42.627Z" }, + { url = "https://files.pythonhosted.org/packages/3c/95/d7fc301cc4661785967acc04f54a4a42d5124905e27db27bb578aac49b5c/yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8", size = 333327, upload-time = "2025-06-10T00:42:44.842Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/e21269718349582eee81efc5c1c08ee71c816bfc1585b77d0ec3f58089eb/yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23", size = 326054, upload-time = "2025-06-10T00:42:47.149Z" }, + { url = "https://files.pythonhosted.org/packages/32/ae/8616d1f07853704523519f6131d21f092e567c5af93de7e3e94b38d7f065/yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70", size = 315035, upload-time = "2025-06-10T00:42:48.852Z" }, + { url = "https://files.pythonhosted.org/packages/48/aa/0ace06280861ef055855333707db5e49c6e3a08840a7ce62682259d0a6c0/yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb", size = 338962, upload-time = "2025-06-10T00:42:51.024Z" }, + { url = "https://files.pythonhosted.org/packages/20/52/1e9d0e6916f45a8fb50e6844f01cb34692455f1acd548606cbda8134cd1e/yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2", size = 335399, upload-time = "2025-06-10T00:42:53.007Z" }, + { url = "https://files.pythonhosted.org/packages/f2/65/60452df742952c630e82f394cd409de10610481d9043aa14c61bf846b7b1/yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30", size = 338649, upload-time = "2025-06-10T00:42:54.964Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f5/6cd4ff38dcde57a70f23719a838665ee17079640c77087404c3d34da6727/yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309", size = 358563, upload-time = "2025-06-10T00:42:57.28Z" }, + { url = "https://files.pythonhosted.org/packages/d1/90/c42eefd79d0d8222cb3227bdd51b640c0c1d0aa33fe4cc86c36eccba77d3/yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24", size = 357609, upload-time = "2025-06-10T00:42:59.055Z" }, + { url = "https://files.pythonhosted.org/packages/03/c8/cea6b232cb4617514232e0f8a718153a95b5d82b5290711b201545825532/yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13", size = 350224, upload-time = "2025-06-10T00:43:01.248Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/eaa0ab9712f1f3d01faf43cf6f1f7210ce4ea4a7e9b28b489a2261ca8db9/yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8", size = 81753, upload-time = "2025-06-10T00:43:03.486Z" }, + { url = "https://files.pythonhosted.org/packages/8f/34/e4abde70a9256465fe31c88ed02c3f8502b7b5dead693a4f350a06413f28/yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16", size = 86817, upload-time = "2025-06-10T00:43:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, + { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, + { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, + { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, + { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, + { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, + { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, + { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, + { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, + { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, + { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, + { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, + { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, + { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, + { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, + { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e1/2411b6d7f769a07687acee88a062af5833cf1966b7266f3d8dfb3d3dc7d3/yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a", size = 131811, upload-time = "2025-06-10T00:44:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/b2/27/584394e1cb76fb771371770eccad35de400e7b434ce3142c2dd27392c968/yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3", size = 90078, upload-time = "2025-06-10T00:44:20.635Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9a/3246ae92d4049099f52d9b0fe3486e3b500e29b7ea872d0f152966fc209d/yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7", size = 88748, upload-time = "2025-06-10T00:44:22.34Z" }, + { url = "https://files.pythonhosted.org/packages/a3/25/35afe384e31115a1a801fbcf84012d7a066d89035befae7c5d4284df1e03/yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691", size = 349595, upload-time = "2025-06-10T00:44:24.314Z" }, + { url = "https://files.pythonhosted.org/packages/28/2d/8aca6cb2cabc8f12efcb82749b9cefecbccfc7b0384e56cd71058ccee433/yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31", size = 342616, upload-time = "2025-06-10T00:44:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e9/1312633d16b31acf0098d30440ca855e3492d66623dafb8e25b03d00c3da/yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28", size = 361324, upload-time = "2025-06-10T00:44:27.915Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/688cc99463f12f7669eec7c8acc71ef56a1521b99eab7cd3abb75af887b0/yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653", size = 359676, upload-time = "2025-06-10T00:44:30.041Z" }, + { url = "https://files.pythonhosted.org/packages/af/44/46407d7f7a56e9a85a4c207724c9f2c545c060380718eea9088f222ba697/yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5", size = 352614, upload-time = "2025-06-10T00:44:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/31163295e82b8d5485d31d9cf7754d973d41915cadce070491778d9c9825/yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02", size = 336766, upload-time = "2025-06-10T00:44:34.494Z" }, + { url = "https://files.pythonhosted.org/packages/b4/8e/c41a5bc482121f51c083c4c2bcd16b9e01e1cf8729e380273a952513a21f/yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53", size = 364615, upload-time = "2025-06-10T00:44:36.856Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5b/61a3b054238d33d70ea06ebba7e58597891b71c699e247df35cc984ab393/yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc", size = 360982, upload-time = "2025-06-10T00:44:39.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/a3/6a72fb83f8d478cb201d14927bc8040af901811a88e0ff2da7842dd0ed19/yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04", size = 369792, upload-time = "2025-06-10T00:44:40.934Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/4cc3c36dfc7c077f8dedb561eb21f69e1e9f2456b91b593882b0b18c19dc/yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4", size = 382049, upload-time = "2025-06-10T00:44:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/19/3a/e54e2c4752160115183a66dc9ee75a153f81f3ab2ba4bf79c3c53b33de34/yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b", size = 384774, upload-time = "2025-06-10T00:44:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/200ae86dabfca89060ec6447649f219b4cbd94531e425e50d57e5f5ac330/yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1", size = 374252, upload-time = "2025-06-10T00:44:47.31Z" }, + { url = "https://files.pythonhosted.org/packages/83/75/11ee332f2f516b3d094e89448da73d557687f7d137d5a0f48c40ff211487/yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7", size = 81198, upload-time = "2025-06-10T00:44:49.164Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ba/39b1ecbf51620b40ab402b0fc817f0ff750f6d92712b44689c2c215be89d/yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c", size = 86346, upload-time = "2025-06-10T00:44:51.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/c7/669c52519dca4c95153c8ad96dd123c79f354a376346b198f438e56ffeb4/yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d", size = 138826, upload-time = "2025-06-10T00:44:52.883Z" }, + { url = "https://files.pythonhosted.org/packages/6a/42/fc0053719b44f6ad04a75d7f05e0e9674d45ef62f2d9ad2c1163e5c05827/yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf", size = 93217, upload-time = "2025-06-10T00:44:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7f/fa59c4c27e2a076bba0d959386e26eba77eb52ea4a0aac48e3515c186b4c/yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3", size = 92700, upload-time = "2025-06-10T00:44:56.784Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/062b2f48e7c93481e88eff97a6312dca15ea200e959f23e96d8ab898c5b8/yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d", size = 347644, upload-time = "2025-06-10T00:44:59.071Z" }, + { url = "https://files.pythonhosted.org/packages/89/47/78b7f40d13c8f62b499cc702fdf69e090455518ae544c00a3bf4afc9fc77/yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c", size = 323452, upload-time = "2025-06-10T00:45:01.605Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2b/490d3b2dc66f52987d4ee0d3090a147ea67732ce6b4d61e362c1846d0d32/yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1", size = 346378, upload-time = "2025-06-10T00:45:03.946Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/775da9c8a94ce925d1537f939a4f17d782efef1f973039d821cbe4bcc211/yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce", size = 353261, upload-time = "2025-06-10T00:45:05.992Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/0ed0922b47a4f5c6eb9065d5ff1e459747226ddce5c6a4c111e728c9f701/yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3", size = 335987, upload-time = "2025-06-10T00:45:08.227Z" }, + { url = "https://files.pythonhosted.org/packages/3e/49/bc728a7fe7d0e9336e2b78f0958a2d6b288ba89f25a1762407a222bf53c3/yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be", size = 329361, upload-time = "2025-06-10T00:45:10.11Z" }, + { url = "https://files.pythonhosted.org/packages/93/8f/b811b9d1f617c83c907e7082a76e2b92b655400e61730cd61a1f67178393/yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16", size = 346460, upload-time = "2025-06-10T00:45:12.055Z" }, + { url = "https://files.pythonhosted.org/packages/70/fd/af94f04f275f95da2c3b8b5e1d49e3e79f1ed8b6ceb0f1664cbd902773ff/yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513", size = 334486, upload-time = "2025-06-10T00:45:13.995Z" }, + { url = "https://files.pythonhosted.org/packages/84/65/04c62e82704e7dd0a9b3f61dbaa8447f8507655fd16c51da0637b39b2910/yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f", size = 342219, upload-time = "2025-06-10T00:45:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/459ca62eb958381b342d94ab9a4b6aec1ddec1f7057c487e926f03c06d30/yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390", size = 350693, upload-time = "2025-06-10T00:45:18.399Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/d393e82dd955ad20617abc546a8f1aee40534d599ff555ea053d0ec9bf03/yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458", size = 355803, upload-time = "2025-06-10T00:45:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ed/c5fb04869b99b717985e244fd93029c7a8e8febdfcffa06093e32d7d44e7/yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e", size = 341709, upload-time = "2025-06-10T00:45:23.221Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/725b8e73ac2a50e78a4534ac43c6addf5c1c2d65380dd48a9169cc6739a9/yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d", size = 86591, upload-time = "2025-06-10T00:45:25.793Z" }, + { url = "https://files.pythonhosted.org/packages/94/c3/b2e9f38bc3e11191981d57ea08cab2166e74ea770024a646617c9cddd9f6/yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f", size = 93003, upload-time = "2025-06-10T00:45:27.752Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] diff --git a/mod/.gitignore b/mod/.gitignore index 7ebabcc..773338b 100644 --- a/mod/.gitignore +++ b/mod/.gitignore @@ -2,3 +2,4 @@ /internal/dagger /internal/querybuilder /internal/telemetry +/.env diff --git a/mod/dagger.json b/mod/dagger.json index d0f9396..ee8bacb 100644 --- a/mod/dagger.json +++ b/mod/dagger.json @@ -1,6 +1,6 @@ { "name": "dagger-techlab-module", - "engineVersion": "v0.18.0", + "engineVersion": "v0.18.12", "sdk": { "source": "go" }, diff --git a/mod/go.mod b/mod/go.mod index 3563679..692ba4f 100644 --- a/mod/go.mod +++ b/mod/go.mod @@ -3,49 +3,50 @@ module dagger/mod go 1.23.0 require ( - github.com/99designs/gqlgen v0.17.70 - github.com/Khan/genqlient v0.8.0 - github.com/vektah/gqlparser/v2 v2.5.23 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + github.com/99designs/gqlgen v0.17.75 + github.com/Khan/genqlient v0.8.1 + github.com/vektah/gqlparser/v2 v2.5.28 + go.opentelemetry.io/otel v1.36.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 - go.opentelemetry.io/otel/log v0.8.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/sdk/log v0.8.0 - go.opentelemetry.io/otel/trace v1.34.0 - go.opentelemetry.io/proto/otlp v1.3.1 - golang.org/x/sync v0.12.0 - google.golang.org/grpc v1.71.0 + go.opentelemetry.io/otel/log v0.12.2 + go.opentelemetry.io/otel/sdk v1.36.0 + go.opentelemetry.io/otel/sdk/log v0.12.2 + go.opentelemetry.io/otel/trace v1.36.0 + go.opentelemetry.io/proto/otlp v1.6.0 + golang.org/x/sync v0.15.0 + google.golang.org/grpc v1.73.0 ) require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sosodev/duration v1.3.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 - go.opentelemetry.io/otel/sdk/metric v1.34.0 - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + go.opentelemetry.io/otel/metric v1.36.0 + go.opentelemetry.io/otel/sdk/metric v1.36.0 + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/protobuf v1.36.6 // indirect ) -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 -replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 -replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.12.2 -replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.12.2 diff --git a/mod/go.sum b/mod/go.sum index b02fec5..91a9903 100644 --- a/mod/go.sum +++ b/mod/go.sum @@ -1,11 +1,13 @@ -github.com/99designs/gqlgen v0.17.70 h1:xgLIgQuG+Q2L/AE9cW595CT7xCWCe/bpPIFGSfsGSGs= -github.com/99designs/gqlgen v0.17.70/go.mod h1:fvCiqQAu2VLhKXez2xFvLmE47QgAPf/KTPN5XQ4rsHQ= -github.com/Khan/genqlient v0.8.0 h1:Hd1a+E1CQHYbMEKakIkvBH3zW0PWEeiX6Hp1i2kP2WE= -github.com/Khan/genqlient v0.8.0/go.mod h1:hn70SpYjWteRGvxTwo0kfaqg4wxvndECGkfa1fdDdYI= +github.com/99designs/gqlgen v0.17.75 h1:GwHJsptXWLHeY7JO8b7YueUI4w9Pom6wJTICosDtQuI= +github.com/99designs/gqlgen v0.17.75/go.mod h1:p7gbTpdnHyl70hmSpM8XG8GiKwmCv+T5zkdY8U8bLog= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= +github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,12 +18,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= @@ -35,16 +37,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vektah/gqlparser/v2 v2.5.23 h1:PurJ9wpgEVB7tty1seRUwkIDa/QH5RzkzraiKIjKLfA= -github.com/vektah/gqlparser/v2 v2.5.23/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/vektah/gqlparser/v2 v2.5.28 h1:bIulcl3LF69ba6EiZVGD88y4MkM+Jxrf3P2MX8xLRkY= +github.com/vektah/gqlparser/v2 v2.5.28/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 h1:06ZeJRe5BnYXceSM9Vya83XXVaNGe3H1QqsvqRANQq8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2/go.mod h1:DvPtKE63knkDVP88qpatBj81JxN+w1bqfVbsbCbj1WY= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2 h1:tPLwQlXbJ8NSOfZc4OkgU5h2A38M4c9kfHSVc4PFQGs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.12.2/go.mod h1:QTnxBwT/1rBIgAG1goq6xMydfYOBKU6KTiYF4fp5zL8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= @@ -55,36 +57,38 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc= +go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/log v0.12.2 h1:yNoETvTByVKi7wHvYS6HMcZrN5hFLD7I++1xIZ/k6W0= +go.opentelemetry.io/otel/sdk/log v0.12.2/go.mod h1:DcpdmUXHJgSqN/dh+XMWa7Vf89u9ap0/AAk/XGLnEzY= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc h1:uqxdywfHqqCl6LmZzI3pUnXT1RGFYyUgxj0AkWPFxi0= +go.opentelemetry.io/otel/sdk/log/logtest v0.0.0-20250521073539-a85ae98dcedc/go.mod h1:TY/N/FT7dmFrP/r5ym3g0yysP1DefqGpAZr4f82P0dE= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI= +go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 h1:cJfm9zPbe1e873mHJzmQ1nwVEeRDU/T1wXDK2kUSU34= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=